Documentation > Language
In OpenMOLE, transitions represent the links between tasks.
The following examples illustrate several kinds of transitions available in OpenMOLE.
You will need to use a combination of theCombining several workflow parts with
In OpenMOLE, the representation of the workflow has been designed to be as linear as possible, but actually workflows are just as graph of task and transitions.
Sometime you cannot express complex workflows in such a linear manner.
Therefore you may want to use the
Basic transitions 🔗
Simple transition 🔗
A transition specifies a precedence relationship between two tasks. In the following example, the taskt1
produces a variable i
which travels along a transition to the task t2
.
t2
uses it in turn in order to compute its output. Simple transitions are marked by the operator --
between the tasks:
val d = Val[Double]
val e = Val[Double]
val t1 = ScalaTask("val d = 42.0") set ( outputs += d )
val t2 = ScalaTask("val e = d / 42") set ( inputs += d, outputs += e)
t1 -- (t2 hook DisplayHook())
Exploration transition 🔗
The Exploration transition links anExplorationTask
to another task.
It creates one new execution stream by sample point in the design of experiment of the ExplorationTask
.
For instance, the following workflow runs the task t1
10 times.
Exploration transitions use -<
to link the tasks.
// Declare the variable
val d = Val[Double]
// Define the Hello task
val t1 = ScalaTask("d = d * 2") set ( inputs += d, outputs += d )
//Define the exploration strategy
val exploration = ExplorationTask(d in (0.0 to 99.0 by 10.0))
exploration -< (t1 hook DisplayHook())
You can read more about Explorations and Samplings in the relevant section of the documentation.
Aggregating results from an exploration 🔗
We have seen how we can execute tasks for a set of values with the exploration transition-<
.
It is also possible to collect all the results produced by an exploration in order to compute global indicators with the aggregation transition noted >-
.
The following workflow sums over all the results computed by the t1
task in the exploration:
// Declare the variable
val d = Val[Double]
val t1 = ScalaTask("d = d * 2") set ( inputs += d, outputs += d )
val exploration = ExplorationTask( d in (0.0 to 99.0 by 10.0) )
val aggregate = ScalaTask("val d = input.d.sum") set (
inputs += d.toArray,
outputs += d
)
exploration -< t1 >- (aggregate hook DisplayHook())
It is very important to understand that this transition gathers input data from the dataflow.
This task has a single instance which is provided with a collection of inputs stored in variable d
.
In order for OpenMOLE to match the input data to this aggregation, we explicitly note the inputs as being arrays using the toArray
conversion.
Subsequent parallelism is preserved by marking the same d
collection again as an array.
This restores any subsequent parallelism by splitting the data among multiple instances of the next task in the workflow.
Combining transitions 🔗
Simple combination 🔗
In order to automate some processes we might want to run several task in sequence after an exploration transition. To achieve that, you can easily compose different transitions:val d = Val[Double]
val t1 = ScalaTask("d = d * 42") set ( inputs += d, outputs += d )
val t2 = ScalaTask("d = d + 100") set ( inputs += d, outputs += d)
val exploration = ExplorationTask( d in (0.0 to 99.0 by 10.0) )
exploration -< t1 -- (t2 hook display)
Tasks in parallel 🔗
In OpenMOLE you can also declare tasks to be independent from each other so they can be executed concurrently. Parallel tasks are to be put in brackets. For instance, in this examplet2
and t3
are executed concurrently:
val d = Val[Double]
val t1 = ScalaTask("d = d * 42") set ( inputs += d, outputs += d )
val t2 = ScalaTask("d = d + 100") set ( inputs += d, outputs += d)
val t3 = ScalaTask("d = d - 100") set ( inputs += d, outputs += d)
val exploration = ExplorationTask( d in (0.0 to 99.0 by 10.0) )
exploration -< t1 -- (t2 hook display, t3 hook display)
Conditions 🔗
When needed, it is possible to set a condition on a transition, so that the task after the transition is executed under this condition only. Conditional transitions are specified using thewhen
keyword.
Using a condition on the execution of a task 🔗
For instance we can add a condition on the transition towardst2
in the previous workflow:
val d = Val[Double]
val t1 = ScalaTask("d = d * 42") set ( inputs += d, outputs += d )
val t2 = ScalaTask("d = d + 100") set ( inputs += d, outputs += d)
val exploration = ExplorationTask( d in (0.0 to 99.0 by 10.0) )
exploration -< t1 -- (t2 hook display) when "d > 1000"
In this case the task t2
is executed only if the variable d
is greater than 1000.
Using a condition to define a resumable workflow 🔗
Sometimes you might want to be able to easily stop your workflow and relaunch it only on unfinished jobs. The following example explains how to relaunch your workflow only on unprocessed inputs or with new inputs. For instance if your task may fail or sometimes return empty or invalid files, you can benefit from this OpenMOLE construct.You will need to use a combination of the
when
condition and the Expression
keyword: the condition is expressed using an OpenMOLE Expression
.
Here is an example:
// define files and name used
val parameter = Val[Int]
val resultFile = Val[File]
// write the parameter in a file
val writeTask = ScalaTask("""
val resultFile = newFile();
resultFile.content = s"$parameter"
""") set (
(inputs, outputs) += parameter,
outputs += resultFile
)
// outputpath is the path where resultFile is stored
val outputPath = Expression[File](workDirectory / "results/results_${parameter}")
// the file coming from each job is copied in the results folder
val copy = CopyFileHook(resultFile, outputPath)
// a job is executed only if there is no matching resultFile or an empty resultFile
// once you've run this workflow once you can manually suppress some result files and run it again
DirectSampling(
sampling = parameter in (0 to 100),
evaluation = writeTask hook copy,
condition = !outputPath.exists || outputPath.isEmpty
)
Advanced concepts 🔗
Capsules and Slots 🔗
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 so calledCapsules
, which can have several input Slots
.
The Capsule section explains this in details.
Combining several workflow parts with &
In OpenMOLE, the representation of the workflow has been designed to be as linear as possible, but actually workflows are just as graph of task and transitions.
Sometime you cannot express complex workflows in such a linear manner.
Therefore you may want to use the &
operator to merge different part of a workflow.
While the linear representation is more compact, the @code{&} notation provides you with more freedom in the design of the transition graph (note that you can combine the two representations in order to get both compactness and flexibility).
The following example exposes two equivalent workflows, the first is using the linear representation and the second design using mostly the &
operator:
val d = Val[Double]
val t1 = ScalaTask("d = d * 42") set ( inputs += d, outputs += d )
val t2 = ScalaTask("d = d + 100") set ( inputs += d, outputs += d)
val t3 = ScalaTask("d = d - 100") set ( inputs += d, outputs += d)
val exploration = ExplorationTask( d in (0.0 to 99.0 by 10.0) )
exploration -< t1 -- (t2 hook display, (t3 hook display))
(exploration -< t1) & (t1 -- t2) & (t1 -- t3) & (t2 hook display) & (t3 hook display)
Loops 🔗
Loops are a direct application of the explicit definition ofCapsules
and Slots
to wrap tasks.
A task may possess multiple input Slots.
Slots
are useful to distinguish loops from synchronization points.
The execution of a task is started when all the incoming transitions belonging to the same input Slot
have been triggered.
See how several Slots define a loop in this workflow:
val i = Val[Int]
val t0 = ScalaTask("val i = 0") set (outputs += i)
val t1 = ScalaTask("i = i + 1") set ((inputs, outputs) += i)
(t0 -- (t1 hook display)) & (t1 -- Slot(t1) when "i < 100")