Commit 0bf0a113 authored by Hélène Arduin's avatar Hélène Arduin
Browse files

[Doc] update Language pages

parent a50f1093
......@@ -5,18 +5,19 @@
OpenMOLE implements mechanisms to conveniently make use of data stored as files in your experiments.
@br
@i{Nota Bene:} OpenMOLE makes very few distinctions between files and directories.
In this part, most mentions of a "file" are also valid for a "directory".
@br@br
@br
The files in OpenMOLE tasks have two main use cases:
@ul
@li{they can be provided to a task as @a("input files or resources", href := fileManagement.file + "#Inputfilesandresources"),}
@li{they can be produced by a task @a("output files", href := fileManagement.file + "#Outputfiles").}
@br
@img(src := Resource.img.model.fileMapping.file, width := "100%")
......@@ -25,7 +26,9 @@ The files in OpenMOLE tasks have two main use cases:
A task can be provided with input files and resources prior to its execution, in order to use theses files during execution.
The difference between input files and resources is that @b{input files are generally produced dynamically by another task} in the workflow, whereas @b{resources are preexisting files}.
@br
In OpenMOLE, there are two behaviors when it comes to manipulating input files and resources: one for the ScalaTask, and one for the other "external" tasks that are mainly used to embed code from other languages.
......@@ -35,10 +38,10 @@ In order to provide files to that kind of tasks (Python, R, NetLogo, ...), OpenM
@h6{Resources}
To provide a file as a resource to a task, it first needs to be uploaded to your working directory in the GUI (see @a("here", href := gui.file + "#FileManagement") for more info on how to do this).
It can then be fed to the task through the @code{resources} keyword inside the task: @code{resources += path/to/the/file}.
@br
Let's first consider a simple case in which an external task requires a file named @code{file.txt} to be executed:
@br@br
Let's first consider a simple case in which an external task requires a file named @code{file.txt} to be executed:
@openmole("""
// Define the task
......@@ -50,12 +53,8 @@ Let's first consider a simple case in which an external task requires a file nam
task
""")
@br
We can do the same with a directory present in your OpenMOLE working directory:
@br@br
@openmole("""
// Define the task
val task = SystemExecTask("ls mydirectory") set (
......@@ -66,14 +65,10 @@ We can do the same with a directory present in your OpenMOLE working directory:
task
""")
@br
It is also possible to provide a second argument to rename the file: @code{resources += (path/to/the/file, "new_name.txt")}.
In this case the directory in which the task is executed contains a file with the same content but a different name.
For instance:
@br@br
@openmole("""
// Define the task
val task = SystemExecTask("cat bettername.txt") set (
......@@ -84,8 +79,6 @@ For instance:
task
""")
@br
The @code{resources} keyword is useful for files existing before the workflow execution.
In other cases, you might want to produce a file in a task and provide it to a subsequent task.
......@@ -93,8 +86,6 @@ In other cases, you might want to produce a file in a task and provide it to a s
Input files are files that the workflow will interact with dynamically, mainly by creating them, or writing in them.
The @code{inputFiles} keyword is used for these files, assigning a file variable from the workflow to a name and linking it to the file object created: @code{inputFiles += (fileVariable, "filename.txt")}.
@br@br
@openmole(s"""
// Define a File variable
val f = Val[File]
......@@ -121,8 +112,6 @@ The @code{inputFiles} keyword is used for these files, assigning a file variable
Since the @code{ScalaTask} is able to directly access the file variables @code{Val[File]}, it is easier to provide it with files.
Files are just plain simple inputs, in the same manner as any other numerical inputs for instance.
@br@br
@openmole(s"""
// Define a File variable
val f = Val[File]
......@@ -141,13 +130,9 @@ Files are just plain simple inputs, in the same manner as any other numerical in
task
""")
@br
In the example above, the file is provided using a default argument.
You can of course produce it in another task.
@br@br
@openmole(s"""
// Define a File variable
val f = Val[File]
......@@ -171,8 +156,6 @@ You can of course produce it in another task.
producer -- task
""")
@br
Note that the type of variable @code{f} is @code{java.io.File}.
It can be provided as an argument to Java or Scala function calls as it is.
......@@ -190,8 +173,6 @@ The case of the @code{ScalaTask} is explained in a separate section.
The general mechanism to save a file for subsequent execution or in the work directory is to link the path of the produced file to a variable of type @code{Val[File]} using the @code{outputFiles} keyword.
@code{outputFiles} gets a file with a given name and assign it to an OpenMOLE variable once the execution of the task is completed: @code{outputFiles += ("filename.txt", fileVariable)}.
@br@br
@openmole("""
// Define a file variable
val f = Val[File]
......@@ -210,8 +191,6 @@ The general mechanism to save a file for subsequent execution or in the work dir
When using the @code{ScalaTask}, files are simple variables and are manipulated like any variable.
To output a file variable you can just set it as any usual output:
@br@br
@openmole(s"""
// Define a file variable
val f = Val[File]
......
......@@ -28,12 +28,8 @@ There is only one mandatory argument to specify, the kind of @code{output} you w
@li{@code{hook display} to display the results in the standard output, note that it is completely equivalent to writing @code{hook(display)} or @code{hook(output = display)}}
@li{@code{hook(workDirectory / "path/to/a/file.csv")} to save the results in a CSV file}
@br
Let's consider this simple workflow:
@br@br
@hl.openmole("""
// Define the variable i
val i = Val[Int]
......@@ -50,8 +46,6 @@ Let's consider this simple workflow:
)
""", name = "plug a hook")
@br
The @code{hook} is plugged to the end of the @code{hello} task in the @code{DirectSampling}, which means that every time @code{hello} finishes, the hook is executed.
Here it means that for each @code{i} value, the dataflow will be printed in even numbered files named @b{helloTask_0.csv}, @b{helloTask_2.csv}, etc., located in the @b{results} repository (which will be automatically created if it does not exist yet).
......@@ -73,8 +67,6 @@ The specific arguments of the default hooks for each method, when they exist, ar
Any string can be appended to a file using the hook @code{AppendToFileHook}.
The appended string can be a combination of variables from the data flow and plain text.
@br@br
@hl.openmole("""
val i = Val[Int]
......@@ -86,8 +78,6 @@ The appended string can be a combination of variables from the data flow and pla
@code{AppendToFileHook} can be used to write an entire file as well.
@br@br
@hl.openmole("""
val file = Val[File]
val i = Val[Int]
......@@ -95,8 +85,6 @@ The appended string can be a combination of variables from the data flow and pla
val h = AppendToFileHook(workDirectory / "path/to/a/file/or/dir${i}.csv", "${file.content}")
""", name = "append file to file hook")
@br
The path to the new file can be expanded using variables from the data flow (@code{i} here for instance).
The variables or expressions written between @b{${}} are evaluated and replaced with their value.
......@@ -105,16 +93,12 @@ The variables or expressions written between @b{${}} are evaluated and replaced
The hook @code{CSVHook} takes data from the data flow and appends it to a file formatted as CSV.
@br@br
@hl.openmole("""
val i = Val[Int]
val h = CSVHook(workDirectory / "path/to/a/file/or/dir${i}.csv")
""", name = "csv hook")
@br
Some additional optional parameters can be passed to the @code{CSVHook}:
@ul
@li{@code{values = Seq(i, j)} specifies which variables from the data flow should be written in the file. The default behaviour when this list is not specified is to dump all the variables from the dataflow to the file.}
......@@ -133,16 +117,12 @@ Some additional optional parameters can be passed to the @code{CSVHook}:
Some workflows may output two dimensional data, which can be understood as a matrix.
For this, the @code{MatrixHook} writes matrix-like data to a file.
@br@br
@hl.openmole("""
val matrix = Val[Array[Array[Double]]]
val h = MatrixHook("file.csv", matrix)
""")
@br
Output format will be a CSV file.
Data understood as matrix are one and two dimensional arrays of double, int and long.
......@@ -152,8 +132,6 @@ Data understood as matrix are one and two dimensional arrays of double, int and
The @code{CopyFileHook} makes it possible to copy a file or directory from the data flow to a given location on the machine running OpenMOLE.
@br@br
@hl.openmole("""
val file = Val[File]
val i = Val[Int]
......@@ -169,8 +147,6 @@ The @code{CopyFileHook} makes it possible to copy a file or directory from the d
To display a variable @code{i} from the workflow in the standard output, use the hook @code{DisplayHook(i)}:
@br@br
@hl.openmole("""
val i = Val[Int]
val j = Val[Int]
......@@ -178,8 +154,6 @@ To display a variable @code{i} from the workflow in the standard output, use the
val h = DisplayHook(i, j)
""", name = "to string hook")
@br
If no variable is specified in @code{DisplayHook()}, the whole data flow will be displayed.
......@@ -189,20 +163,18 @@ To display a string in the standard output, use the @code{DisplayHook("string")}
The string can be formed of plain text and/or variables.
You can think of the @code{DisplayHook} as an OpenMOLE equivalent to Scala's @code{println}.
@br@br
@hl.openmole("""
val i = Val[Int]
val h = DisplayHook("The value of i is ${i}.")
""", name = "display hook")
@h2{Variable restriction}
You may want to restrict the hooked variables to a subset, like a variable filter.
You can use the following notation in the hook function {@code{hook(yourHookHere, values = Seq(i,j))}}
@br
@hl.openmole("""
val i = Val[Int]
val j = Val[Int]
......@@ -216,19 +188,17 @@ You can use the following notation in the hook function {@code{hook(yourHookHere
task hook(workDirectory / "results/res.csv", values = Seq(j,k)) // only j and k are appended to the file
""", name = "variable restriction")
@h2{Conditional hooking}
You may want to filter outputs that are redirected to a hook, @i{i.e.} do conditional hooking.
You can use for that the @code{when} keyword, built from a hook and a condition:
@br@br
@hl.openmole("""
val i = Val[Int]
val display = DisplayHook("The value of i is ${i}.") when "i > 0"
""", name = "condition hook")
@br
Decorators exist for a simpler syntax: @code{ConditionHook(myhook,mycondition)} is equivalent to @code{myhook when mycondition} and @code{myhook condition mycondition} (where the condition can be given as a condition or a string).
Decorators exist for a simpler syntax: @code{ConditionHook(myHook,myCondition)} is equivalent to @code{myHook when myCondition} and @code{myHook condition myCondition} (where the condition can be given as a condition or a string).
......@@ -9,7 +9,7 @@ It supports all the Scala constructs, and additional operators and classes speci
OpenMOLE workflows expose explicit parallel aspect of the workload that can be delegated to distributed computing environments in a transparent manner.
The philosophy of OpenMOLE is test small (on your computer) and scale for free (on remote distributed computing environments).
@br@br
@br
A good way to get a first glimpse of what OpenMOLE can do is to read this @aa("research paper", href := Resource.paper.fgcs2013.file).
......@@ -17,24 +17,20 @@ A good way to get a first glimpse of what OpenMOLE can do is to read this @aa("r
@h2{OpenMOLE scripts}
The OpenMOLE scripts are stored in the GUI in @b{.oms} files.
The OpenMOLE scripts are stored in the GUI in @b{.oms} files.
One workflow can be split into several files.
@br
To be able to use the content of a @b{.oms} file (let's call it @code{file2.oms}) in another one (say @code{file1.oms}), located in the same directory, @code{file2.oms} needs to be imported in @code{file1.oms}.
The following line needs to be written at the beginning of @code{file1.oms}:
@br@br
@hl.openmoleNoTest("""
import _file_.file2._
""")
@br
To refer to a file located in a parent directory, use the @code{parent} keyword:
@br@br
@hl.openmoleNoTest("""
import _parent_._file_.file2._
""")
......@@ -69,7 +65,7 @@ Under the hood, it calls a method that is in charge of building the file.
@h3{Named parameters }
Function calls generally require the parameters to be provided in a predefined order.
In Scala you can get rid of this ordering constraint by using named parameters.
OpenMOLE scripts will often make use of this pattern: @hl.openmoleNoTest("val t = SomeClass(value1, value2, otherParam = otherValue)").
OpenMOLE scripts will often make use of this pattern: @code{val t = SomeClass(value1, value2, otherParam = otherValue)}.
Here it means that @code{value1} and @code{value2} are the values for the first two (unnamed) parameters, and that the parameter named @code{otherParam} is set to the value @code{otherValue}.
Unspecified parameters are set to their default value.
......@@ -81,7 +77,7 @@ What you have read so far should be sufficient in order to get started with Open
To begin with the OpenMOLE syntax you should have a look at the @a("Getting started", href := stepByStepIntro.file).
You may also want to look at the @a("Task documentation", href := plug.file), and more generally the @a("Documentation", href := documentation.file).
@br@br
@br
Scala is a very nice language, with an extensive and very well designed standard library.
To get more insights on this language, check these links:
......
@import org.openmole.site.tools._
@import org.openmole.site._
@import DocumentationPages._
Some useful functions are usable anywhere in OpenMOLE where you would use scala code. For instance you can use them in:
Some useful functions are usable anywhere in OpenMOLE where you would use Scala code.
For instance you can use them in:
@ul
@li
@a("ScalaTask", href := DocumentationPages.scala.file) code,
@a("ScalaTask", href := scala.file) code,
@li
string expanded by OpenMOLE (${scala code}),
@li
OpenMOLE scripts.
OpenMOLE scripts.
@h2{Data processing}
OpenMOLE provides a useful functions to aggregate data. Theses functions can be called on array and vectors. For instance:
OpenMOLE provides a useful functions to aggregate data.
Theses functions can be called on array and vectors.
For instance:
@hl.openmole("""
val pi = Val[Double]
......@@ -25,48 +33,65 @@ val average =
outputs += piAvg
)""")
This task takes place after an exploration and compute the average of many values of pi. The presently available functions are:
This task takes place after an exploration and compute the average of many values of pi.
The presently available functions are:
@ul
@li
@hl.code("""def median: Double"""), compute the median of the vector,
@code{def median: Double}, compute the median of the vector,
@li
@hl.code("""def medianAbsoluteDeviation: Double"""), compute the median absolute deviation of the vector,
@code{def medianAbsoluteDeviation: Double}, compute the median absolute deviation of the vector,
@li
@hl.code("""def average: Double"""), compute the average of the vector,
@code{def average: Double}, compute the average of the vector,
@li
@hl.code("""def meanSquaredError: Double"""), compute the mean square error of the vector,
@code{def meanSquaredError: Double}, compute the mean square error of the vector,
@li
@hl.code("""def rootMeanSquaredError: Double"""), compute the root of the mean square error of the vector.
@code{def rootMeanSquaredError: Double}, compute the root of the mean square error of the vector.
@h2{Data comparison}
OpenMOLE provides a useful functions to compare data series. This function can be called on array and vectors. For instance:
OpenMOLE provides useful functions to compare data series.
This function can be called on array and vectors.
For instance:
@ul
@li
@hl.code("""def absoluteDistance(s1: Seq[Double], s2: Seq[Double]): Double"""), compute the sum of the absolute distance between the respective elements of s1 and s2,
@code{def absoluteDistance(s1: Seq[Double], s2: Seq[Double]): Double}, compute the sum of the absolute distance between the respective elements of s1 and s2,
@li
@hl.code("""def squareDistance(s1: Seq[Double], s2: Seq[Double]): Double"""), compute the sum of the squared distance between the respective elements of s1 and s2.
@code{def squareDistance(s1: Seq[Double], s2: Seq[Double]): Double}, compute the sum of the squared distance between the respective elements of s1 and s2.
@li
@code("""def dynamicTimeWarpingDistance(s1: Seq[Double], s2: Seq[Double]): Double"""), compute the @a(href := "https://en.wikipedia.org/wiki/Dynamic_time_warping", "dynamic time warping distance") between s1 and s2.
@code("""def dynamicTimeWarpingDistance(s1: Seq[Double], s2: Seq[Double]): Double}, compute the @a(href := "https://en.wikipedia.org/wiki/Dynamic_time_warping", "dynamic time warping distance} between s1 and s2.
@h2{File creation}
It might be useful to create files and folders in ScalaTask code. To do that use one of the folowing functions:
It might be useful to create files and folders in ScalaTask code.
To do that, use one of the following functions:
@ul
@li
@hl.code("def newFile(prefix: String, suffix: String): File"), this function creates a new file in the OpenMOLE workspace. You may optionally provide a prefix and suffix for the file name. It would generally be called @hl.code("newFile()").
@code{def newFile(prefix: String, suffix: String): File}, this function creates a new file in the OpenMOLE workspace. You may optionally provide a prefix and suffix for the file name. It would generally be called @code{newFile()}.
@li
@hl.code("def newDir(prefix: String): File"), this function creates a new directory in the OpenMOLE workspace. You may optionally provide a prefix for the directory name. It would generally be called @hl.code("newDir()"). This function doesn't create the directory.
@code{def newDir(prefix: String): File}, this function creates a new directory in the OpenMOLE workspace. You may optionally provide a prefix for the directory name. It would generally be called @code{newDir()}. This function doesn't create the directory.
@li
@hl.code("def mkDir(prefix: String): File"), this function creates a new directory in the OpenMOLE workspace. You may optionally provide a prefix for the directory name. It would generally be called @hl.code("mkDir()"). This function creates the directory.
@code{def mkDir(prefix: String): File}, this function creates a new directory in the OpenMOLE workspace. You may optionally provide a prefix for the directory name. It would generally be called @code{mkDir()}. This function creates the directory.
@h2{Random number generator}
@p
In scala code you may use a properly initialised random generator by calling @hl.code("""random()"""). For instance you may call @hl.code("""random().nextInt""").
@p
It might sometimes be useful to create a new random number generator. To do that use @hl.code("def newRandom(seed: Long): Random"). The seed is optional. If it is not provided OpenMOLE will take care of the generator initialisation in a sound manner. It would generally be called @hl.code("newRNG()").
In Scala code you may use a properly initialised random generator by calling @code{random()}.
For instance you may call @code{random().nextInt}.
@br
It might sometimes be useful to create a new random number generator.
To do that use @code{def newRandom(seed: Long): Random}.
The seed is optional.
If it is not provided OpenMOLE will take care of the generator initialisation in a sound manner.
It would generally be called @code{newRNG()}.
/* TODO: Document this part
@h2{Technical functions}
......@@ -77,7 +102,3 @@ It might be useful to create files and folders in ScalaTask code. To do that use
def withThreadClassLoader[R](classLoader: ClassLoader)(f: ⇒ R) =
org.openmole.tool.thread.withThreadClassLoader(classLoader)(f)
*/
@import org.openmole.site.tools._
@import org.openmole.site._
@import DocumentationPages._
@h2{Definition}
Tasks are not directly linked to each-other by transitions. This has been made as transparent as possible, but two
other notions are involved behind the scenes. Tasks are encapsulated in a so called
@i{Capsule}. Each @i{Capsule} has one or several input @i{Slots} which
transitions are plugged to. This code snippet explicitly encapsulates the task @i{t1} in the Capsule @i{c1}:
@br @hl.openmole("""
Tasks are not directly linked to each-other by transitions.
This has been made as transparent as possible, but two other notions are involved behind the scenes.
Tasks are encapsulated in a so called @code{Capsule}.
Each @code{Capsule} has one or several input @code{Slots} which transitions are plugged to.
This code snippet explicitly encapsulates the task @code{t1} in the Capsule @code{c1}:
@hl.openmole("""
val t1 = ScalaTask("1 + 1")
val c1 = Capsule(t1)
""")
@p Capsules are the atomic element in the workflow which transitions are plugged to. Capsules also serve as an entry
point on which @a("Hooks", href := DocumentationPages.hook.file), @a("Sources", href := DocumentationPages.source.file) and
@a("Execution Environments", href := DocumentationPages.scale.file) are specified.
@p When a task is directly linked to another without explicitly specifying a Capsule, @b{a single capsule is created for this task and used each time the task in mentioned in the workflow}.
@p Capsules might own several input Slots in which transitions are plugged. Slots make it possible to specify
iterative workflows (with cycles) as well as synchronisation points between several parts of a workflow. The rule is
that the task encapsulated in the Capsule is executed each time all the transitions reaching a given input slot have
been triggered. To specify slots explicitly you should write:
@br @hl.openmole("""
""")
Capsules are the atomic element in the workflow which transitions are plugged to.
Capsules also serve as an entry point on which @aa("Hooks", href := hook.file), @aa("Sources", href := source.file) and @a("Execution Environments", href := scale.file) are specified.
When a task is directly linked to another without explicitly specifying a Capsule, @b{a single capsule is created for this task and used each time the task in mentioned in the workflow}.
@br
Capsules might own several input Slots in which transitions are plugged.
Slots make it possible to specify iterative workflows (with cycles) as well as synchronisation points between several parts of a workflow.
The rule is that the task encapsulated in the Capsule is executed each time all the transitions reaching a given input slot have been triggered.
To specify slots explicitly you should write:
@hl.openmole("""
val t1 = ScalaTask("1 + 1")
val c1 = Capsule(t1)
val s1 = Slot(c1)
""")
Other specific capsules are defined in OpenMOLE. They are described in the @aa("Advanced capsule", href := DocumentationPages.capsule.file) section
Other specific capsules are defined in OpenMOLE.
They are described in the @aa("Advanced capsule", href := capsule.file) section.
@h2{Strainer capsule}
In a general manner you are expected to specify the inputs and outputs of each task. Capsules' strainer mode transmits all the variables arriving through the input transition as if they were inputs and ouptuts of the task.
In a general manner you are expected to specify the inputs and outputs of each task.
Capsules' strainer mode transmits all the variables arriving through the input transition as if they were inputs and ouptuts of the task.
@p For instance, variable @i{i} is transmitted to the hook without adding it explicitly in input and output of the task @i{t2}, in the following workflow:
@br @hl.openmole("""
@br
For instance, variable @i{i} is transmitted to the hook without adding it explicitly in input and output of the task @i{t2}, in the following workflow:
@hl.openmole("""
val i = Val[Int]
val j = Val[Int]
......@@ -45,24 +59,45 @@ val t2 = ScalaTask("val j = 84") set (outputs += j)
t1 -- (Strain(t2) hook DisplayHook(i, j))
""")
@p This workflow displays @hl("{i=42, j=84}", "plain")
This workflow displays @code{{i=42, j=84}}
@h2{Master capsule}
OpenMOLE provides a very flexible workflow formalism. It even makes it possible to design workflows with a part that mimics a @b{master / slave} distribution scheme. This schemes involves many slave jobs computing partial results and a master gathering the whole result.
OpenMOLE provides a very flexible workflow formalism.
It even makes it possible to design workflows with a part that mimics a @b{master / slave} distribution scheme.
This scheme involves many slave jobs computing partial results and a master gathering the whole result.
@br
@p You can think of a steady state genetic algorithm of instance as a typical use case. This use case would see a global solution population maintained and a bunch of slave workers computing fitnesses in a distributed manner. Each time a worker ends, its result is used to update the global population and a new worker is launched. To achieve such a distribution scheme, one should use the @i{Master Capsule} along with an end-exploration transition.
You can think of a steady state genetic algorithm, for instance, as a typical use case.
This use case would see a global solution population maintained and a bunch of slave workers computing fitnesses in a distributed manner.
Each time a worker ends, its result is used to update the global population and a new worker is launched.
To achieve such a distribution scheme, one should use the @i{Master Capsule} along with an end-exploration transition.
@p The @i{MasterCapsule} is a special capsule that preserves a state from one execution to another. An execution of the @i{MasterCapsule} modifies this state and the next execution gets the state that has been modified last. To ensure soundness of the state only, the @i{MasterCapsule}s are always executed locally and multiple executions of a given @i{MasterCapsule} are carried sequentially.
@br
@p By using the @i{MasterCapsule}, a workflow can evolve a global archive, and compute new inputs to be evaluated from this archive. Even if it is not required, a @i{MasterCapsule} is generally executed in an exploration, in order to have several workers computing concurrently. This distribution scheme suggests that all the workers should be killed when the global archive has reached a suitable state. This is the aim of the end-exploration transition, which is noted @b{>|}.
The @code{MasterCapsule} is a special capsule that preserves a state from one execution to another.
An execution of the @code{MasterCapsule} modifies this state and the next execution gets the state that has been modified last.
To ensure soundness of the state only, the @code{MasterCapsule}s are always executed locally and multiple executions of a given @code{MasterCapsule} are carried sequentially.
@p The following script orchestrates a master slave distribution scheme for a dummy problem. OpenMOLE launches 10
workers. Along these workers, the @i{MasterCapsule} hosts the selection task. The selection task stores the numbers
that are multiple of 3 and relaunches a worker for the next value of @i{i}. The second argument of the
@i{MasterCapsule} constructor is the data that should be persisted from one execution of the @i{MasterCapsule} to
another.
@br @hl.openmole("""
@br
By using the @code{MasterCapsule}, a workflow can evolve a global archive, and compute new inputs to be evaluated from this archive.
Even if it is not required, a @code{MasterCapsule} is generally executed in an exploration, in order to have several workers computing concurrently.
This distribution scheme suggests that all the workers should be killed when the global archive has reached a suitable state.
This is the aim of the end-exploration transition, which is noted @code{>|}.
@br
The following script orchestrates a master slave distribution scheme for a dummy problem.
OpenMOLE launches 10 workers.
Along these workers, the @code{MasterCapsule} hosts the selection task.
The selection task stores the numbers that are multiple of 3 and relaunches a worker for the next value of @code{i}.
The second argument of the @code{MasterCapsule} constructor is the data that should persist from one execution of the @code{MasterCapsule} to another.
@hl.openmole("""
val i = Val[Int]