Back to home page

EIC code displayed by LXR

 
 

    


Warning, /AID2E-framework/docs/user-guide/workflows.md is written in an unsupported language. File is not indexed.

0001 # Workflows and Objectives
0002 
0003 This guide documents AID2E's workflow execution and objective specification system.
0004 
0005 ## Execution Model
0006 
0007 A workflow evaluates one design point. It contains branches, each branch contains stages, and each stage contains zero or more jobs. The current executor runs branches in configuration order. Within each branch, stages are treated as a sequential chain: every stage depends on the previous stage. Jobs expanded within one stage are submitted together and may run in parallel through the effective scheduler.
0008 
0009 A downstream stage begins only after all jobs in the preceding stage complete successfully. A failed scheduler stage stops the workflow and prevents later stages in that branch from running.
0010 
0011 The current workflow model does not expose explicit dependencies between workflow stages. Objective step plans have their own `depends_on` fields and are topologically sorted separately. Although an empty branch list is accepted by the model, the current executor does not create executable stages for an implicit branch; executable workflows should declare their branches and stages.
0012 
0013 For each stage, the executor:
0014 
0015 1. Expands the configured jobs.
0016 2. Resolves the stage scheduler.
0017 3. Executes all jobs directly or through that scheduler.
0018 4. Collects scheduler outputs and artifacts into the workflow XCom mapping.
0019 5. Executes the stage objective plan, when configured.
0020 
0021 After all branches complete, the executor collects the declared objectives and returns them to the optimization loop.
0022 
0023 ## Jobs and Job Factories
0024 
0025 ### Job Execution Types
0026 
0027 The job `payload.evaluator_type` selects the execution engine. When it is omitted, the job uses command execution.
0028 
0029 - **Command/Bash** executes `job.command`; direct execution uses `payload.env` for additional environment variables.
0030 - **Python** uses `evaluator_type: "python"` with `python_callable`. Direct execution expects a callable object and passes `JobContext` first, followed by `op_args` and `op_kwargs`. Scheduler-backed Python jobs forward `op_kwargs`; the workflow adapter currently rejects this evaluator type for Slurm.
0031 - **Container** uses `evaluator_type: "container"` with image, command, environment, volume, and resource settings for direct execution.
0032 - **Stack** uses `evaluator_type: "stack"` with a registered `stack_type` and one or more ordered layers.
0033 
0034 ### Job Factories
0035 
0036 A stage can execute its declared jobs directly or expand the first job as a template through `job_factory`. The current executor implements two factory types: `range` and `payloads`. Other names described by the model are not implemented and raise `ValueError`.
0037 
0038 The `range` factory creates `n` deep copies of the first job. Each copy is named `<template_name>_<index>` and receives `job_index` in its payload:
0039 
0040 ```yaml
0041 job_factory:
0042   type: "range"
0043   params:
0044     n: 4
0045 ```
0046 
0047 The `payloads` factory calls a Python function referenced as `module.path:function_name`. The function receives `stage_id`, `problem_config`, and `workflow_context` and must return a list of mappings. One deep-copied job is created for each mapping, and that mapping is merged into the template payload:
0048 
0049 ```yaml
0050 job_factory:
0051   type: "payloads"
0052   params:
0053     from: "workflow_utils:simulation_payloads"
0054 ```
0055 
0056 Because expansion deep-copies the template job, stack layer definitions are preserved for every generated job. Additional jobs declared after the first template are not expanded when a factory is present.
0057 
0058 ### Callable Entrypoints
0059 
0060 Payload factories and inline objective functions use `module.path:function_name`. The framework imports the module and resolves the named function. Factories receive the stage and workflow context; objective functions receive the design point and objective inputs and outputs. Python job callable support depends on the execution path; direct and JobLib jobs require a callable object.
0061 
0062 ## Scheduler Cascade
0063 
0064 ### Overview
0065 
0066 Stages inherit scheduler settings in this order:
0067 
0068 ```
0069 Cascade Precedence (highest to lowest):
0070   1. Stage-level scheduler (override)
0071   2. Branch-level scheduler (branch default)
0072   3. Workflow-level scheduler (workflow default)
0073   4. Global scheduler (global default)
0074 ```
0075 
0076 The top-level `scheduler` is used as the global default when present; otherwise, `workflows.global_scheduler` is used. An objective-level scheduler is reserved in the schema but raises an error in the current runtime. Scheduled objective work must be represented as a workflow stage.
0077 
0078 ### Example
0079 
0080 Illustrative scheduler overrides (add an optimizer and evaluator for a complete run):
0081 
0082 ```yaml
0083 problem:
0084   name: "DTLZ2"
0085   problem_type: "DTLZ2"
0086   design_parameters_file: "design.params"
0087   objectives:
0088     - name: "f1"
0089       direction: "minimize"
0090 
0091 # Global/workflow-level scheduler
0092 scheduler:
0093   runner_type: "JobLibRunner"
0094   parameters:
0095     n_jobs: 8
0096     backend: "loky"
0097 
0098 workflows:
0099   workflows:
0100     - name: "dtlz2_eval"
0101 
0102       # Workflow-level scheduler (overrides global)
0103       scheduler:
0104         runner_type: "JobLibRunner"
0105         parameters:
0106           n_jobs: 4
0107           backend: "threading"
0108 
0109       branches:
0110         - name: "main"
0111 
0112           # Branch-level scheduler (overrides workflow)
0113           scheduler:
0114             runner_type: "JobLibRunner"
0115             parameters:
0116               n_jobs: 2
0117               backend: "loky"
0118 
0119           stages:
0120             - name: "evaluate"
0121 
0122               # Stage-level scheduler (overrides branch)
0123               scheduler:
0124                 runner_type: "SlurmRunner"
0125                 parameters:
0126                   partition: "gpu"
0127                   nodes: 1
0128 
0129               jobs:
0130                 - name: "compute_objective"
0131                   command: "python eval.py"
0132 
0133 ```
0134 
0135 ### Precedence Resolution
0136 
0137 For `evaluate`, the stage-level `SlurmRunner` overrides the branch default. A stage without its own scheduler uses the branch's `JobLibRunner` with `n_jobs=2`.
0138 
0139 ## Stack Jobs and EpicStack
0140 
0141 The stack interface allows a workflow job to execute one or more ordered layers of a registered experimental software stack. The ePIC implementation is registered as `epic`.
0142 
0143 A stack workflow declares `stack_type`, while each stack job declares `evaluator_type: "stack"` and one or more `layers`. A job-level `stack_type` can override the workflow value. Missing or unknown stack types and missing layer configurations raise errors.
0144 
0145 This layer fragment requires the ePIC environment and design configuration:
0146 
0147 ```yaml
0148 workflows:
0149   workflows:
0150     - name: "epic_workflow"
0151       stack_type: "epic"
0152       branches:
0153         - name: "main"
0154           stages:
0155             - name: "sim_rec"
0156               jobs:
0157                 - name: "epic_sim_rec"
0158                   payload:
0159                     evaluator_type: "stack"
0160                   layers:
0161                     - layer: "sim"
0162                       inputs: []
0163                       outputs: ["sim.root"]
0164                       arguments: ["-N 100"]
0165                     - layer: "rec"
0166                       inputs: ["sim.root"]
0167                       outputs: ["reco.root"]
0168 ```
0169 
0170 Layers execute in their listed order within one job. Inputs, outputs, arguments, commands, and rules support templates resolved from the job context.
0171 
0172 ### EpicStack
0173 
0174 ```python
0175 from aid2e.utilities.epic_utils import EpicStack
0176 ```
0177 
0178 `EpicStack` provides four layers:
0179 
0180 - `geo` runs `checkOverlaps`. It requires one geometry input and one log output, and exits with code 9 unless the log reports zero illegal overlaps or extrusions.
0181 - `sim` runs `npsim` and formats generator, HepMC, macro, and output arguments.
0182 - `rec` runs `eicrecon` and formats reconstruction inputs and podio output arguments.
0183 - `ana` runs a user-provided analysis command and rule.
0184 
0185 Geometry is prepared once per trial before branches execute. In `no_build` mode, EpicStack copies `<epic_install>/share/epic` into the workflow output under `geometry/epic`, applies the design-point XML modifications, and uses that copy as `DETECTOR_PATH`. In `build` mode, it copies the configured geometry source, applies the modifications, and builds and installs that workflow copy.
0186 
0187 Generated ePIC driver scripts use `set -euo pipefail`, initialize the selected geometry, and run through `EIC_SINGULARITY_IMAGE` or `EIC_SHELL`. The workflow therefore requires a valid ePIC environment configuration and an ePIC design configuration. See the [dRICH example](https://github.com/aid2e/AID2E-framework/tree/main/examples/epic/drich) for a complete `geo`, `sim`/`rec`, and `ana` workflow, and the [BIC example](https://github.com/aid2e/AID2E-framework/tree/main/examples/epic/bic) for another ePIC detector workflow.
0188 
0189 The current full-config loader selects stack-specific workflow models when a workflow declares `stack_type`. A `WorkflowsConfiguration` should currently use one registered stack type; mixed stacks in one workflow collection are not yet supported reliably.
0190 
0191 ## Objective Plans
0192 
0193 ### Objective Collection
0194 
0195 Objectives can be returned in three ways:
0196 
0197 1. A job returns declared objective names directly in a mapping, an `objectives` mapping, or individual output keys.
0198 2. A stage-level objective plan runs after that stage's jobs and stores its extracted metrics in XCom.
0199 3. An objective-level or combined objective plan runs during final objective collection.
0200 
0201 Objective plans execute locally inside the trial's `DAGExecutor`, even when workflow jobs use a remote scheduler. Their `StepStage` objects are separate from workflow `StageDefinition` objects and do not use the scheduler cascade.
0202 
0203 - Inline steps receive `design_point`, `inputs`, `outputs`, `extra_args`, `xcom`, `work_dir`, `output_dir`, `problem_config`, and `workflow_context`. Trial metadata is available through `workflow_context`.
0204 - Script steps receive design-point and step-input JSON files, run the configured executable, and read its JSON output. Parameter and output paths are passed through command arguments and environment variables.
0205 - Dependencies determine execution order. Their results are passed to downstream steps through the input mapping. `produces_from_stage` selects the plan result.
0206 
0207 A stage-level plan must return all objectives declared for the workflow. A single-objective plan extracts its one `metrics_keys` entry, or defaults to the objective name. Optional uncertainty values use `<objective_name>_err`.
0208 
0209 In a full configuration, declare objectives under `problem.objectives` only; duplicated `workflows[].objectives` declarations are rejected.
0210 
0211 See [workflow notes](notes/workflows.md) for objective model classes, stack registration, and geometry preparation internals.
0212 
0213 Objective-level and combined-plan `scheduler` fields are reserved but unsupported by the executor. Use a workflow stage for scheduled objective work.
0214 
0215 ### Single-Step and Multi-Step Plans
0216 
0217 All objective plans use `steps`, with one stage or several dependent stages.
0218 
0219 Example (single script stage):
0220 
0221 ```python
0222 from aid2e.utilities.configurations.objectives import (
0223     ObjectiveDefinition, ObjectiveDirection, ObjectivePlanSpec,
0224     ScriptObjective, StepPlanSpec, StepStage,
0225 )
0226 
0227 ObjectiveDefinition(
0228     name="f1",
0229     direction=ObjectiveDirection.MINIMIZE,
0230     objective_plan=ObjectivePlanSpec(
0231         steps=StepPlanSpec(
0232             stages=[
0233                 StepStage(
0234                     name="f1_stage_0",
0235                     script=ScriptObjective(
0236                         path="scripts/dtlz2.py",
0237                         output_file="f1.json",
0238                     ),
0239                     produces_objective=True,
0240                 )
0241             ]
0242         )
0243     )
0244 )
0245 ```
0246 
0247 ## Combined Objectives
0248 
0249 ### Motivation
0250 
0251 Sometimes a single computation produces **multiple objective metrics**. For example:
0252 - A DTLZ2 evaluation script outputs both `f1` and `f2`
0253 - A surrogate model prediction outputs multiple target values
0254 - A simulation produces both efficiency and quality scores
0255 
0256 Instead of running the same plan twice, **combined objectives** allow one execution to produce multiple metrics.
0257 
0258 ### Usage in Workflows
0259 
0260 Add `combined_objectives` to a workflow. This illustrative fragment requires matching declarations in `problem.objectives` and a script that returns all three metrics:
0261 
0262 ```yaml
0263 workflows:
0264   workflows:
0265     - name: "dtlz2_multi"
0266 
0267       # Combined objectives: one plan produces multiple metrics
0268       combined_objectives:
0269         - name: "dtlz2_pareto"
0270 
0271           objective_plan:
0272             steps:
0273               stages:
0274                 - name: "evaluate_objectives"
0275                   script:
0276                     path: "scripts/evaluate_metrics.py"
0277                     output_file: "objectives.json"
0278                   produces_objective: true
0279 
0280           metrics:
0281             - name: "f1"
0282               direction: "minimize"
0283               metric_key: "f1"  # Extract {"f1": ...} from output
0284 
0285             - name: "f2"
0286               direction: "minimize"
0287               metric_key: "f2"  # Extract {"f2": ...} from output
0288 
0289             - name: "efficiency"
0290               direction: "maximize"
0291               metric_key: "efficiency"  # Extract {"efficiency": ...}
0292 ```
0293 
0294 ### Output Format
0295 
0296 The objective plan script should output a JSON file with the metric values:
0297 
0298 ```json
0299 {
0300   "f1": 0.45,
0301   "f2": 0.67,
0302   "efficiency": 0.92
0303 }
0304 ```
0305 
0306 Each key in this object becomes extractable via `metric_key` in `CombinedObjectiveMetric`.
0307 
0308 In full configurations, combined metrics should match `problem.objectives`, which supplies the optimizer's objective names and directions. `metric_key` controls how output keys map to those names.
0309 
0310 ## Step Plans
0311 
0312 ### Structure
0313 
0314 A step plan contains one or more stages, each with a distinct computation:
0315 
0316 ```python
0317 from aid2e.utilities.configurations.objectives import (
0318     ObjectiveDefinition, ObjectiveDirection, ObjectivePlanSpec,
0319     ScriptObjective, StepPlanSpec, StepStage,
0320 )
0321 
0322 ObjectiveDefinition(
0323     name="complex_eval",
0324     direction=ObjectiveDirection.MINIMIZE,
0325     objective_plan=ObjectivePlanSpec(
0326         steps=StepPlanSpec(
0327             stages=[
0328                 StepStage(
0329                     name="preprocess",
0330                     script=ScriptObjective(path="preprocess.py", output_file="prep.json"),
0331                     inputs={"design_file": "design_params.json"},
0332                     outputs={"prepared_file": "preprocessed.json"},
0333                     produces_objective=False,
0334                 ),
0335                 StepStage(
0336                     name="evaluate",
0337                     script=ScriptObjective(path="evaluate.py", output_file="eval.json"),
0338                     depends_on=["preprocess"],
0339                     inputs={"prepared_file": "preprocessed.json"},
0340                     outputs={"metrics_file": "objectives.json"},
0341                     produces_objective=True,  # This stage produces the final objective
0342                 ),
0343             ],
0344             produces_from_stage="evaluate",  # Which stage's output is the objective value
0345         )
0346     )
0347 )
0348 ```
0349 
0350 ### StepStage Fields
0351 
0352 - `name` (str): Stage name (must be unique within a plan)
0353 - `script` or `inline` (exactly one): The execution mode
0354   - `script`: Path to executable script
0355   - `inline`: Python function entrypoint
0356 - `inputs` (Dict[str, Any]): Named input bindings
0357 - `outputs` (Dict[str, Any]): Named output bindings
0358 - `extra_args` (Dict[str, Any]): Additional arguments to pass to the stage
0359 - `produces_objective` (bool): Whether this stage produces the objective value
0360 - `depends_on` (List[str]): Names of preceding stages this depends on
0361 
0362 ### Validation
0363 
0364 The model automatically validates:
0365 - **Mutual exclusivity**: Each stage has exactly one of `script` or `inline`
0366 - **Unique names**: All stage names are unique within a plan
0367 - **Single producer**: Select `produces_from_stage`, or mark exactly one stage with `produces_objective=True`
0368 - **Dependency satisfaction**: All dependencies reference existing stages
0369 
0370 At execution, steps are topologically sorted and cycles are rejected. Results from dependencies are supplied to downstream steps through their input mapping.
0371 
0372 ## Outputs and Failures
0373 
0374 For `aid2e optimize`, the configured output and work locations contain one directory per run:
0375 
0376 ```text
0377 <output_location>/<run-id>/
0378 |-- optimization_results.json
0379 |-- pareto_front.json
0380 `-- trials/
0381     `-- trial_<index>/
0382         |-- log/
0383         |-- <stage>/<job>/
0384         `-- geometry/epic/        
0385 
0386 <work_location>/<run-id>/
0387 `-- trials/
0388     `-- trial_<index>/
0389         |-- _scheduler/<stage>/
0390         |-- _objectives/<plan>/<step>/
0391         `-- <stage>/<job>/
0392 ```
0393 
0394 Each job receives a work directory at `<work_dir>/<stage>/<job>` and an output directory at `<output_dir>/<stage>/<job>`. The workflow also creates a shared `log` directory. Direct job return values are pushed to XCom. For scheduler jobs, collected outputs, standard output, standard error, and artifacts are stored in the workflow XCom mapping for downstream stages and objective collection.
0395 
0396 For command jobs, `job.outputs` describes scheduler-collected artifacts, with paths resolved against the runtime context. Collection depends on the runner; JobLib reads existing declared files as text. `StageDefinition.outputs` is not separately collected by the executor. Stack-layer `outputs` construct the layer commands; the current scheduler adapter does not automatically inventory them.
0397 
0398 Workflow checkpoints record stage and job completion or failure. See [run records](cli.md#run-records) for saved optimization results.
0399 
0400 The executor requires every declared objective. Missing metrics, invalid JSON, non-numeric values, nonzero job return codes, and failed scheduler stages are workflow errors. Direct objective collection currently visits XCom values in insertion order, so repeated objective keys can overwrite earlier values rather than raising an error; workflows with multiple producers should use an explicit aggregation plan.
0401 
0402 Workflow errors propagate to the optimization loop. The problem's `evaluation_config.trial_failure_policy` then determines whether the optimizer marks the trial as failed or completes it with configured `penalty_objectives`. `max_failed_trials` limits tolerated failed evaluations. The default policy is `fail`, the default `max_failed_trials` is `0`, and the `penalty` policy requires a configured value for every declared objective. Scheduler retry and timeout limitations are documented in the [scheduler guide](schedulers.md#parallelism-and-failure-behavior).