Versatile Workflow Manager
Project description
Motoko
Motoko is a workflow manager built on top of BlackDynamite, a Python-based tool for managing job submissions suited for HPC facilities in an automated and highly parallelized fashion.
Full documentation can be found on readthedocs
Installation
Install Motoko and dependencies:
pip install motoko
For installation in development mode:
pip install -e motoko_dir/
To run the local test workflow, do:
pytest
BlackDynamite Studies
More concretely, Motoko manages workflows of interconnected
BlackDynamite studies. A BlackDynamite study is a parametrized calculation
that is possibly run many times with different inputs.
BlackDynamite parameterizes a calculation using jobs and runs:
- A
jobis a calculation with a specific input. - A
runis an execution of a job. It carries metadata, the information needed to launch and track the calculation, and output quantities produced by the executable.
Several runs can belong to the same job, for example when the same input is repeated after a failure or evaluated with different software settings.
Key Concepts
Task Manager
A wrapper around one BlackDynamite study. A task is one pair of jobs and runs. The Motoko task manager creates such job/run pairs and can also select subsets of runs using constraints (e.g., selects only finished runs).
Workflow
The top-level object. It owns all task managers, and coordinates their creation and execution.
Orchestrator
A Python module attached to a Workflow. It registers asynchronous routines that the workflow executes following a dependency graph, or events triggering actions when a certain condition becomes true.
Workflow Directory Layout
A workflow directory contains motoko.yaml, an orchestrator Python file, and
one subdirectory per study:
workflow/
motoko.yaml
orchestrator.py
study1/
bd.yaml
launch.sh
doIt.py
study2/
bd.yaml
launch.sh
doIt.py
Each study directory contains three files: bd.yaml, containing information
about the parametric space; doIt.py, containing the code that runs the
calculations; and launch.sh, which launches doIt.py with a given job
submission manager (e.g. Slurm).
Once the workflow has been started, Motoko saves the workflow state in
workflow/.wf/. BlackDynamite saves each study's state in
workflow/study*/.bd/. Directories containing the produced output of each run
are stored in BD-study*-runs/.
motoko.yaml
motoko.yaml is the workflow input file. It declares the task-manager
directories and the Python function that will orchestrate them.
Minimal example:
task_managers:
task1:
task2:
orchestrator: orchestrator.main
Detailed example:
task_managers:
prepare:
solve:
host: zeo://cluster_name:8010
analyze:
aliases:
post: analyze
orchestrator: orchestrator.main
generator: slurmCoat
slurm_options:
- nodes=1
Fields:
task_managers: Required mapping. Each key is both the task-manager name and the relative directory name containing that task's BlackDynamite study. Empty values are allowed. A task manager may definehostto override the default local ZEO host.orchestrator: Required string inmodule.functionform. Motoko loadsmodule.pyfrom the workflow directory and callsfunction(workflow, **params).aliases: Optional mapping from alternative workflow attribute names to task-manager names. For example,post: analyzeallowsworkflow.postto access theanalyzetask manager.generator: Optional BlackDynamite launcher generator (bash, slurm, PBS).*_options: Optional launcher-specific option lists. Motoko derives the key from the generator name withgenerator.replace("Coat", "_options")and appends the listed values to the launcher command.
Python Orchestrator API
The orchestrator module usually exposes two functions:
from motoko.workflow import event
def populate_arg_parser(parser):
parser.add_argument("--inputs", "-i", type=float, required=True, nargs=2)
async def main(workflow, **params):
# orchestrate the workflow here
populate_arg_parser(parser) is optional but useful for workflow-specific CLI
arguments. Motoko calls it when launching the orchestrator function from the
command line (see below).
main(workflow, **params) registers routines and actions. It should be async.
It normally ends by setting workflow.finished = True when some condition is
met.
Asynchronous routines
Motoko orchestrators are asynchronous. This allows an orchestrator to express a
sequence of dependent routines directly in main.
Use this pattern when the workflow follows a dependency graph that can be written as a sequence of awaited task submissions and selections:
async def run_mult(workflow, inputs):
return await workflow.mult.createTask(x=inputs)
async def run_add(workflow, mult_runs):
created = []
for run, job in mult_runs:
created.extend(await workflow.add.createTask(x=run.y))
return created
async def main(workflow, **params):
mult_runs = await run_mult(workflow, params["inputs"])
add_runs = await run_add(workflow, mult_runs)
await workflow.norm.createTask(
mult_ids=[run.id for run, job in add_runs],
)
Figure: asynchronous routines called directly from main.
flowchart LR
inputs[CLI inputs] --> mult[run_mult]
mult --> add[run_add]
add --> norm[create norm task]
TaskManager.createTask(...) returns an awaitable RunList. Awaiting it
commits the transaction, then waits until all created runs reach the
FINISHED state and returns the created (run, job) pairs:
created = await workflow.add.createTask(x=1.5)
for run, job in created:
print(run.id, job.id)
TaskManager.select(...) also returns an awaitable selection. Awaiting a
selection polls until at least one run matches the supplied BlackDynamite
constraints:
finished = await workflow.mult.select("state = FINISHED")
Registering Actions
Use actions for event-driven workflows. An action registered with
add_action(...) is triggered each time its event condition is met. In that
case, Motoko polls the condition and calls the action function when the
condition fires:
workflow.add_action(event_name, task="__all__", event=..., f=...)
event_name: Human-readable name stored in workflow logs.task: Task manager to watch. Use"__all__"to evaluate the event against every task manager.event: Condition that decides whether the action fires. It may be a BlackDynamite constraint string, a list of constraint strings, arun, jobfunction, aworkflow, task_managerfunction, or a no-argument function.f: Callback to run when the event fires. If the event returns a run selection, Motoko passes it asruns=...; otherwise the callback receivesworkflow=...and the runtime parameters.
Example actions:
@event
async def spawn_init_tasks(workflow, **kwargs):
await workflow.mult.createTask(x=kwargs["inputs"])
@event
async def spawn_add_tasks(runs=None, workflow=None, **kwargs):
for run, job in runs:
created = await workflow.add.createTask(x=run.y)
run.state = "FORWARDED"
run.dependencies = [f"add.{r.id}" for r, j in created]
Constraint-based event:
workflow.add_action(
"mult_finished",
task="mult",
event=["runs.id < 3", "state = FINISHED"],
f=spawn_add_tasks,
)
Python condition event:
def ready_for_norm(workflow, task_manager):
if len(workflow.mult.select([])) != 2:
return False
if workflow.mult.select(["state != FORWARDED"]):
return False
return True
workflow.add_action("need_norm", event=ready_for_norm, f=spawn_norm_tasks)
Workflow API Reference
Workflow(filename): Loadmotoko.yaml, create task manager objects, and record workflow paths.workflow.create(validated=False): Reset.wf/and initialize all task-manager BlackDynamite studies.workflow.start_launcher_daemons(args=None): Start BlackDynamite launcher daemons for all or selected task managers.workflow.add_action(...): Register an event condition and callback.workflow.add_error_handler(event="state = FAILED", f=...): Register a fail-fast action for failed runs.workflow.execute(**params): Run the orchestrator and event polling loop.workflow.get_runs(["add.1", "norm.3"]): Resolve dependency references into persistent run objects grouped by task manager.workflow.vars: Persistent workflow variable namespace backed by.wf/wf.db.workflow.<task_manager>: Attribute access to task managers, for exampleworkflow.mult.
Task Manager API Reference
TaskManager.createTask(run_params=None, **job_params): Create one or more BlackDynamite jobs/runs.job_paramsare expanded through the study's BlackDynamite job schema and defaultjob_space.run_paramsare stored on each run. Returns an awaitableRunListof(run, job)pairs.await TaskManager.createTask(...): Wait until all created runs reachFINISHED, then return the created(run, job)pairs.TaskManager.select(constraints=None): Return a lazyTaskSelection. Constraints use BlackDynamite syntax, such as"state = FINISHED"or["runs.id < 3", "state = FINISHED"]. Whenworkflow.run_nameis set, Motoko automatically adds a matchingrun_nameconstraint.await TaskManager.select(...): Poll until the selection becomes non-empty.TaskSelection.all(...): Build an awaitable condition that waits until all selected runs satisfy one of the supplied constraint sets.
Launching a Motoko workflow
Motoko workflows can be run using the command line or within a Python script.
Command Line Usage
Create or reset the BlackDynamite studies for a workflow:
motoko create workflow_dir
Start launcher daemons from inside the workflow directory:
cd workflow_dir
motoko launcher
Run the orchestrator in the foreground:
motoko orchestrator start --run_name test --inputs 2.1 3.2
Run the orchestrator detached with zdaemon:
motoko orchestrator start --detach --run_name test --inputs 2.1 3.2
Inspect workflow state:
motoko info
motoko info --verbose
motoko info --bd_study mult
Stop daemons:
motoko orchestrator stop
motoko kill
Clean BlackDynamite runs:
motoko clean
motoko clean --delete
Python interface
The command line interface is a thin wrapper around the Python API. A workflow can also be created and executed directly from Python:
from motoko.workflow import Workflow
workflow = Workflow("motoko.yaml")
workflow.create()
workflow.start_launcher_daemons()
workflow.run_name = "test"
workflow.execute(inputs=[2.1, 3.1])
The Workflow constructor reads motoko.yaml, creates one TaskManager per
entry in task_managers, and exposes each manager as an attribute.
Tasks can also be created manually outside the orchestrator function:
workflow = Workflow("motoko.yaml")
workflow.run_name = "manual"
async def submit_tasks():
runs = await workflow.mult.createTask(x=[2.1, 3.1])
finished = workflow.mult.select("state = FINISHED")
return runs, finished
In a script, workflow.run_name must be set before creating tasks. Motoko
stores it on every created run and uses it to scope later selections, so
separate workflow executions do not accidentally interfere with one another's
runs.
Motoko loads orchestrator.py from the workflow directory and calls
main(workflow, **params). Parameters come from CLI arguments and are also
accepted by workflow.execute(**params) when running from Python.
For scripts that only need to add or inspect tasks, use the task managers directly:
workflow = Workflow("motoko.yaml")
workflow.run_name = "inspection"
selected = workflow.norm.select(["state = FINISHED"])
for run, job in selected:
print(run.id, run.state)
When using the Python API outside the CLI, the caller is responsible for starting and stopping BlackDynamite launcher daemons if tasks should execute automatically:
subprocess.call("motoko kill", shell=True, cwd=workflow_dir)
Troubleshooting
FATAL: not in a motoko directory (needs motoko.yaml): Several CLI commands expect to run from inside a workflow directory.- No runs execute: Confirm
motoko launcheris running for the relevant task managers. - Selections return old or unexpected runs: Use a distinct
--run_name. Motoko scopes selections by run name when it is set. - Workflow never finishes: Ensure one action eventually sets
workflow.finished = True. - ZEO cache warnings: Stop daemons with
motoko kill, recreate the workflow withmotoko create, and rerun launchers.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file motoko-0.1.4.tar.gz.
File metadata
- Download URL: motoko-0.1.4.tar.gz
- Upload date:
- Size: 205.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
347b37dd879c65d064571bc93e3e21ecdb7198652088c96cf9e5caec43ad0826
|
|
| MD5 |
d9b9e41bc15d3fa557304d3b211b13b6
|
|
| BLAKE2b-256 |
904a5cd2bd5eb717a480212b4ee6b85186b8eb52cb2eb7bb04fdec798995c715
|
File details
Details for the file motoko-0.1.4-py3-none-any.whl.
File metadata
- Download URL: motoko-0.1.4-py3-none-any.whl
- Upload date:
- Size: 24.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
654e9d785548ef8e0111c484b9224744e382297f5a4a77af89465f46e9a509ea
|
|
| MD5 |
79ab07d43ec38d95df8460bf717f733a
|
|
| BLAKE2b-256 |
c24af0d7d172c5d3c82e905a5c03fca9324caa290fec90c809e7f91a11bdee43
|