Advanced Capsule


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 Capsule. Each Capsule has one or several input Slots which transitions are plugged to. This code snippet explicitly encapsulates the task t1 in the Capsule c1:
val t1 = ScalaTask("1 + 1")
val c1 = Capsule(t1)

Capsules are the atomic element in the workflow which transitions are plugged to. Capsules also serve as an entry point on which Hooks, Sources and Execution Environments are specified.

When a task is directly linked to another without explicitly specifying a Capsule, a new capsule is created. It means that for several transitions to reach the same task, the Capsule should be created explicitly.

Capsules might own several input Slots to which transition 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:
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 Advanced capsule section

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.

For instance, variable i is transmitted to the hook without adding it explicitly in input and output of the task t2, in the following workflow:
val i = Val[Int]
val j = Val[Int]

val t1 = ScalaTask("val i = 42") set (outputs += i)

val t2 = ScalaTask("val j = i * 2") set (outputs += j)
val c2 = Capsule(t2, strain = true)

t1 -- (c2 hook ToStringHook(i, j))

This workflow displays {i=42, j=84}

Master capsule

OpenMOLE provides a very flexible workflow formalism. It even makes it possible to design workflows with a part that mimics a master / slave (also known as Map/Reduce) distribution scheme. This schemes involves many slave jobs computing partial results and a master gathering the whole result.

You can think of a steady state genetic algorithm of instance as an 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 Master Capsule along with an end-exploration transition.

The MasterCapsule is a special capsule that preserve a state from one execution to another. An execution of the MasterCapsule modifies this state and the next execution gets the state that has been modified last. To ensure soundness of the state only, the MasterCapsules are always executed locally and multiple executions of a given MasterCapsule are carried sequentially.

By using the 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 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 >|.

The following script orchestrates a master slave distribution scheme for a dummy problem. OpenMOLE launches 10 workers. Along these workers, the 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. The second argument of the MasterCapsule constructor is the data that should be persisted from one execution of the MasterCapsule to another.
val i = Val[Int]
val archive = Val[Array[Int]]

val exploration = ExplorationTask(i in (0 until 10))

val model = ScalaTask("i = i + 7") set (inputs += i, outputs += i)

val modelCapsule = Capsule(model)
val modelSlot1 = Slot(modelCapsule)
val modelSlot2 = Slot(modelCapsule)

val select =
  ScalaTask("archive = archive ++ (if(i % 3 == 0) Seq(i) else Seq())") set (
    (inputs, outputs) += (i, archive),
    archive := Array[Int]()

val selectCaps = MasterCapsule(select, archive)

val finalTask = EmptyTask()

val displayHook = ToStringHook()

val skel = exploration -< modelSlot1 -- (selectCaps hook displayHook)
val loop = selectCaps -- modelSlot2
val terminate = selectCaps >| (Capsule(finalTask, strain = true) hook DisplayHook("archive.size >= 10"))

skel & loop & terminate