Warning, /AID2E-framework/docs/user-guide/workflow-improvements.md is written in an unsupported language. File is not indexed.
0001 # Workflow and Objective Improvements Guide
0002
0003 This guide documents the improvements made to AID2E's workflow and objective specification system.
0004
0005 ## Table of Contents
0006
0007 1. [Objective Plan Terminology](#objective-plan-terminology)
0008 2. [Scheduler Cascade](#scheduler-cascade)
0009 3. [Combined Objectives](#combined-objectives)
0010 4. [Multi-Step Plans](#multi-step-plans)
0011 5. [Migration Guide](#migration-guide)
0012 6. [Examples](#examples)
0013
0014 ---
0015
0016 ## Objective Plan Terminology
0017
0018 ### What Changed?
0019
0020 The term **"computation"** has been renamed to **"objective_plan"** throughout the codebase for clarity. An objective plan represents the executable specification of how to compute an objective value.
0021
0022 ### Key Classes
0023
0024 - **`ObjectiveDefinition`**: Top-level objective specification
0025 - `name` (str): Objective name (e.g., "f1", "f2")
0026 - `direction` (ObjectiveDirection): MINIMIZE or MAXIMIZE
0027 - `objective_plan` (ObjectivePlanSpec): How to compute this objective
0028 - `scheduler` (Optional[SchedulerConfiguration]): Objective-level scheduler default
0029 - `metrics_keys` (List[str]): Keys to extract from objective output
0030
0031 - **`ObjectivePlanSpec`**: Canonical specification for computing an objective
0032 - Uses `steps`: `StepPlanSpec` with one or more stages
0033 - Each stage defines either:
0034 - `script`: Path to executable script
0035 - `inline`: Entrypoint to Python function
0036
0037 ### Single-Step and Multi-Step Plans
0038
0039 By design, all objective plans use the same `steps` structure. A simple
0040 objective can define one stage, while more complex objectives can define
0041 multiple dependent stages. This provides:
0042 - Unified handling of simple and complex computations
0043 - Support for preprocessing, evaluation, and postprocessing stages
0044 - Clear separation of concerns
0045
0046 Example (single script stage):
0047
0048 ```python
0049 ObjectiveDefinition(
0050 name="f1",
0051 direction=ObjectiveDirection.MINIMIZE,
0052 objective_plan=ObjectivePlanSpec(
0053 steps=StepPlanSpec(
0054 stages=[
0055 StepStage(
0056 name="f1_stage_0",
0057 script=ScriptObjective(
0058 path="scripts/dtlz2.py",
0059 output_file="f1.json",
0060 ),
0061 produces_objective=True,
0062 )
0063 ]
0064 )
0065 )
0066 )
0067 ```
0068
0069 ---
0070
0071 ## Scheduler Cascade
0072
0073 ### Overview
0074
0075 Instead of a single global scheduler, AID2E now supports **scheduler cascading** at multiple levels:
0076
0077 ```
0078 Cascade Precedence (highest to lowest):
0079 1. Stage-level scheduler (override)
0080 2. Branch-level scheduler (branch default)
0081 3. Workflow-level scheduler (workflow default)
0082 4. Objective-level scheduler (objective default)
0083 5. Global scheduler (global default)
0084 ```
0085
0086 This allows fine-grained control while maintaining sensible defaults.
0087
0088 ### Cascade Resolution
0089
0090 Use the `resolve_scheduler_cascade()` utility to determine the effective scheduler:
0091
0092 ```python
0093 from aid2e.utilities.configurations import resolve_scheduler_cascade
0094
0095 effective_scheduler = resolve_scheduler_cascade(
0096 stage_scheduler=stage_config.scheduler,
0097 branch_scheduler=branch_config.scheduler,
0098 workflow_scheduler=workflow_config.scheduler,
0099 objective_scheduler=objective_config.scheduler,
0100 global_scheduler=global_config.scheduler,
0101 )
0102 ```
0103
0104 ### Example
0105
0106 YAML configuration with scheduler cascade:
0107
0108 ```yaml
0109 problem:
0110 name: "DTLZ2"
0111 type: "toy"
0112 design_space:
0113 path: "design.params"
0114
0115 # Global/workflow-level scheduler
0116 scheduler:
0117 runner_type: "JobLibRunner"
0118 parameters:
0119 n_jobs: 8
0120 backend: "loky"
0121
0122 workflows:
0123 - name: "dtlz2_eval"
0124
0125 # Workflow-level scheduler (overrides global)
0126 scheduler:
0127 runner_type: "JobLibRunner"
0128 parameters:
0129 n_jobs: 4
0130 backend: "threading"
0131
0132 branches:
0133 - name: "main"
0134
0135 # Branch-level scheduler (overrides workflow)
0136 scheduler:
0137 runner_type: "JobLibRunner"
0138 parameters:
0139 n_jobs: 2
0140 backend: "loky"
0141
0142 stages:
0143 - name: "evaluate"
0144
0145 # Stage-level scheduler (overrides branch)
0146 scheduler:
0147 runner_type: "SlurmRunner"
0148 parameters:
0149 partition: "gpu"
0150 nodes: 1
0151
0152 jobs:
0153 - name: "compute_objective"
0154 command: "python eval.py"
0155
0156 objectives:
0157 - name: "f1"
0158 direction: "minimize"
0159
0160 # Objective-level scheduler (applies to this objective)
0161 scheduler:
0162 runner_type: "JobLibRunner"
0163 parameters:
0164 n_jobs: 1
0165
0166 objective_plan:
0167 steps:
0168 stages:
0169 - name: "evaluate_f1"
0170 script:
0171 path: "scripts/dtlz2.py"
0172 output_file: "f1.json"
0173 produces_objective: true
0174 ```
0175
0176 ### Precedence Resolution
0177
0178 For the objective "f1" in the above example:
0179 1. Check objective-level scheduler → Found: `JobLibRunner` with `n_jobs=1`
0180 2. This is the effective scheduler for objective execution
0181
0182 For a stage without explicit scheduler:
0183 1. Check stage-level scheduler → Not found
0184 2. Check branch-level scheduler → Found: `JobLibRunner` with `n_jobs=2`
0185 3. This is the effective scheduler for the stage
0186
0187 ---
0188
0189 ## Combined Objectives
0190
0191 ### Motivation
0192
0193 Sometimes a single computation produces **multiple objective metrics**. For example:
0194 - A DTLZ2 evaluation script outputs both `f1` and `f2`
0195 - A surrogate model prediction outputs multiple target values
0196 - A simulation produces both efficiency and quality scores
0197
0198 Instead of running the same plan twice, **combined objectives** allow one execution to produce multiple metrics.
0199
0200 ### Key Classes
0201
0202 - **`CombinedObjectivePlan`**: Bundle of a plan with multiple metric definitions
0203 - `name` (str): Combined objective name
0204 - `objective_plan` (ObjectivePlanSpec): The plan to execute
0205 - `metrics` (List[CombinedObjectiveMetric]): Metrics extracted from the output
0206 - `scheduler` (Optional[SchedulerConfiguration]): Scheduler for this plan
0207
0208 - **`CombinedObjectiveMetric`**: A single metric from a combined plan
0209 - `name` (str): Metric name (e.g., "f1", "f2")
0210 - `direction` (ObjectiveDirection): MINIMIZE or MAXIMIZE
0211 - `metric_key` (str): Key to extract from plan output (e.g., "f1" from `{"f1": 0.5, "f2": 0.3}`)
0212
0213 ### Usage in Workflows
0214
0215 Add `combined_objectives` to a `WorkflowDefinition`:
0216
0217 ```yaml
0218 workflows:
0219 - name: "dtlz2_multi"
0220
0221 branches:
0222 - name: "main"
0223 stages:
0224 - name: "evaluate"
0225 jobs:
0226 - name: "dtlz2"
0227 command: "python scripts/dtlz2.py"
0228 outputs:
0229 - path: "objectives.json"
0230 format: "json"
0231
0232 # Combined objectives: one plan produces multiple metrics
0233 combined_objectives:
0234 - name: "dtlz2_pareto"
0235
0236 objective_plan:
0237 steps:
0238 stages:
0239 - name: "evaluate_objectives"
0240 script:
0241 path: "scripts/dtlz2.py"
0242 output_file: "objectives.json"
0243 produces_objective: true
0244
0245 metrics:
0246 - name: "f1"
0247 direction: "minimize"
0248 metric_key: "f1" # Extract {"f1": ...} from output
0249
0250 - name: "f2"
0251 direction: "minimize"
0252 metric_key: "f2" # Extract {"f2": ...} from output
0253
0254 - name: "efficiency"
0255 direction: "maximize"
0256 metric_key: "efficiency" # Extract {"efficiency": ...}
0257 ```
0258
0259 ### Output Format
0260
0261 The objective plan script should output a JSON/YAML file with the metric values:
0262
0263 ```json
0264 {
0265 "f1": 0.45,
0266 "f2": 0.67,
0267 "efficiency": 0.92
0268 }
0269 ```
0270
0271 Each key in this object becomes extractable via `metric_key` in `CombinedObjectiveMetric`.
0272
0273 ---
0274
0275 ## Step Plans
0276
0277 ### Structure
0278
0279 A step plan contains one or more stages, each with a distinct computation:
0280
0281 ```python
0282 ObjectiveDefinition(
0283 name="complex_eval",
0284 direction=ObjectiveDirection.MINIMIZE,
0285 objective_plan=ObjectivePlanSpec(
0286 steps=StepPlanSpec(
0287 stages=[
0288 StepStage(
0289 name="preprocess",
0290 script=ScriptObjective(path="preprocess.py", output_file="prep.json"),
0291 inputs=["design_params.json"],
0292 outputs=["preprocessed.json"],
0293 produces_objective=False,
0294 ),
0295 StepStage(
0296 name="evaluate",
0297 script=ScriptObjective(path="evaluate.py", output_file="eval.json"),
0298 inputs=["preprocessed.json"],
0299 outputs=["objectives.json"],
0300 produces_objective=True, # This stage produces the final objective
0301 ),
0302 ],
0303 produces_from_stage="evaluate", # Which stage's output is the objective value
0304 )
0305 )
0306 )
0307 ```
0308
0309 ### StepStage Fields
0310
0311 - `name` (str): Stage name (must be unique within a plan)
0312 - `script` or `inline` (exactly one): The execution mode
0313 - `script`: Path to executable script
0314 - `inline`: Python function entrypoint
0315 - `inputs` (List[str]): Input artifacts required
0316 - `outputs` (List[str]): Output artifacts produced
0317 - `extra_args` (Dict[str, Any]): Additional arguments to pass to the stage
0318 - `produces_objective` (bool): Whether this stage produces the objective value
0319 - `depends_on` (List[str]): Names of preceding stages this depends on
0320
0321 ### Validation
0322
0323 The model automatically validates:
0324 - **Mutual exclusivity**: Each stage has exactly one of `script` or `inline`
0325 - **Unique names**: All stage names are unique within a plan
0326 - **DAG integrity**: Dependencies form a valid DAG (no cycles)
0327 - **Single producer**: Exactly one stage marks `produces_objective=True`
0328 - **Dependency satisfaction**: All dependencies reference existing stages
0329
0330 ---
0331
0332 ## Migration Guide
0333
0334 ### Old Terminology → New Terminology
0335
0336 | Old Term | New Term | Notes |
0337 |----------|----------|-------|
0338 | "Computation" | "Objective Plan" | More descriptive; plan indicates it's an executable specification |
0339 | `multi_steps` | `steps` | Objective plans now use a neutral name for one or more stages |
0340
0341 ### Updating Your Configurations
0342
0343 #### Before (old terminology):
0344
0345 ```yaml
0346 objectives:
0347 - name: "f1"
0348 direction: "minimize"
0349 computation: # Old field name
0350 script:
0351 path: "scripts/dtlz2.py"
0352 output_file: "f1.json"
0353 ```
0354
0355 #### After (new terminology):
0356
0357 ```yaml
0358 objectives:
0359 - name: "f1"
0360 direction: "minimize"
0361 objective_plan: # New field name
0362 steps:
0363 stages:
0364 - name: "evaluate_f1"
0365 script:
0366 path: "scripts/dtlz2.py"
0367 output_file: "f1.json"
0368 produces_objective: true
0369 ```
0370
0371 ### Python Code Updates
0372
0373 ```python
0374 from aid2e.utilities.configurations import ObjectivePlanSpec, StepPlanSpec, StepStage
0375
0376 spec = ObjectivePlanSpec(
0377 steps=StepPlanSpec(
0378 stages=[
0379 StepStage(...),
0380 ],
0381 ),
0382 )
0383 ```
0384
0385 ---
0386
0387 ## Examples
0388
0389 ### Example 1: Single Objective with Scheduler Cascade
0390
0391 File: [`examples/complete/workflow_example_single_objective.yml`](../examples/complete/workflow_example_single_objective.yml)
0392
0393 Shows:
0394 - Single objective with `objective_plan`
0395 - Scheduler cascade from global → workflow → branch → stage
0396 - Job factory for parameter sweeps
0397 - Clear comments on precedence resolution
0398
0399 ### Example 2: Combined Objectives
0400
0401 File: [`examples/complete/workflow_example_combined_objectives.yml`](../examples/complete/workflow_example_combined_objectives.yml)
0402
0403 Shows:
0404 - One plan producing multiple metrics (f1, f2)
0405 - Metric extraction via `metric_key`
0406 - Combined objective in workflow
0407 - Optional scheduler override at combined objective level
0408
0409 ### Example 3: Multi-Step Objective Plan
0410
0411 ```python
0412 # Three-stage pipeline: preprocess → evaluate → aggregate
0413 ObjectivePlanSpec(
0414 steps=StepPlanSpec(
0415 stages=[
0416 StepStage(
0417 name="preprocess",
0418 script=ScriptObjective(path="preprocess.py"),
0419 inputs=["raw_design.json"],
0420 outputs=["design_preprocessed.json"],
0421 produces_objective=False,
0422 ),
0423 StepStage(
0424 name="evaluate",
0425 script=ScriptObjective(path="evaluate.py"),
0426 inputs=["design_preprocessed.json"],
0427 outputs=["raw_objectives.json"],
0428 produces_objective=False,
0429 ),
0430 StepStage(
0431 name="aggregate",
0432 inline=InlineObjective(entrypoint="my_module:aggregate_objectives"),
0433 inputs=["raw_objectives.json"],
0434 outputs=["final_objectives.json"],
0435 produces_objective=True,
0436 ),
0437 ],
0438 produces_from_stage="aggregate",
0439 )
0440 )
0441 ```
0442
0443 ---
0444
0445 ## Summary of Benefits
0446
0447 1. **Clarity**: "Objective plan" is more intuitive than "computation"
0448 2. **Flexibility**: Scheduler cascade allows both global consistency and local overrides
0449 3. **Efficiency**: Combined objectives avoid redundant computations
0450 4. **Composability**: Multi-step plans support complex workflows within a single objective
0451 5. **Backward Compatibility**: Old code continues to work with alias imports
0452
0453 ---
0454
0455 ## Questions & Support
0456
0457 For more information:
0458 - See [`docs/api-reference/`](../docs/api-reference/) for detailed API docs
0459 - Check [`tests/test_utilities/test_workflows/`](../tests/test_utilities/test_workflows/) for integration tests
0460 - Review [YAML examples](../examples/complete/) for real-world use cases