Capsule

Suggest edits
Documentation > Language

Content:

1 - Definition
2 - Strainer capsule
3 - Master capsule


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 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 single capsule is created for this task and used each time the task in mentioned in the workflow.
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:
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 = 84") set (outputs += j)

t1 -- (Strain(t2) hook DisplayHook(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 distribution scheme. This scheme involves many slave jobs computing partial results and a master gathering the whole result.
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 Master Capsule along with an end-exploration transition.
The MasterCapsule is a special capsule that preserves 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 persist 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 select =
  ScalaTask("archive = archive ++ (if(i % 3 == 0) Seq(i) else Seq())") set (
    (inputs, outputs) += (i, archive),
    archive := Array[Int]()
  )

val finalTask = EmptyTask()

val displayHook = DisplayHook()

val skel = exploration -< model -- (Master(select, archive) hook displayHook)
val loop = select -- Slot(model)
val terminate = select >| (Strain(finalTask) hook DisplayHook("${archive.size}")) when "archive.size > 10"

skel & loop & terminate