File indexing completed on 2026-08-12 08:24:55
0001 """Example: Multi-operator workflow with Airflow-like orchestration.
0002
0003 This example demonstrates:
0004 1. WorkflowDefinition with stages and schedulers
0005 2. Multiple operators (Bash, Python, Container)
0006 3. Stage-specific scheduler overrides
0007 4. Task data flow via XCom
0008 5. Design point evaluation loop
0009
0010 Project: AID2E v0.0.0
0011 """
0012
0013 import json
0014 from pathlib import Path
0015 from typing import Dict, Any
0016
0017 from aid2e.utilities.workflows import (
0018 WorkflowDefinition,
0019 BranchDefinition,
0020 StageDefinition,
0021 JobDefinition,
0022 JobFactory,
0023 ParallelismPolicy,
0024 ArtifactSpec,
0025 JobContext,
0026 BashEvaluator,
0027 PythonEvaluator,
0028 ContainerEvaluator,
0029 )
0030 from aid2e.utilities.configurations.scheduler_config import SchedulerConfiguration, JobLibRunnerConfig
0031
0032
0033
0034
0035
0036
0037 def example_operators_direct():
0038 """Direct operator usage with JobContext."""
0039
0040
0041 context = JobContext(
0042 job_id='task_1',
0043 stage_id='stage_prepare',
0044 workflow_id='dtlz2_eval',
0045 design_point={'x1': 0.5, 'x2': 0.7},
0046 execution_dir='/tmp/work'
0047 )
0048
0049
0050 bash_op = BashEvaluator(
0051 job_id='prepare_params',
0052 bash_command='echo "Preparing with x1={design_point.x1}, x2={design_point.x2}" > params.txt'
0053 )
0054 result = bash_op.execute(context)
0055 print(f"BashEvaluator result: {result}")
0056
0057
0058 def compute_metrics(context: JobContext, scale: float = 1.0) -> Dict[str, float]:
0059 """Compute metrics from design point."""
0060 x1 = context.design_point.get('x1', 0.0)
0061 x2 = context.design_point.get('x2', 0.0)
0062 metrics = {
0063 'f1': (x1 ** 2) * scale,
0064 'f2': ((x2 - 1.0) ** 2) * scale
0065 }
0066 return metrics
0067
0068 python_op = PythonEvaluator(
0069 job_id='compute',
0070 python_callable=compute_metrics,
0071 op_kwargs={'scale': 2.0}
0072 )
0073 result = python_op.execute(context)
0074 print(f"PythonEvaluator result: {result}")
0075
0076
0077 container_op = ContainerEvaluator(
0078 job_id='run_sim',
0079 image='dtlz2-simulator:1.0',
0080 command=['/app/dtlz2.sh'],
0081 environment={
0082 'INPUT_FILE': '/data/input.json',
0083 'OUTPUT_DIR': '/output',
0084 'X1': '{design_point.x1}',
0085 'X2': '{design_point.x2}'
0086 },
0087 volumes={
0088 '/host/data': '/data',
0089 '/host/output': '/output'
0090 },
0091 resources={
0092 'memory': '4g',
0093 'cpus': '2'
0094 }
0095 )
0096
0097 docker_cmd = container_op._build_docker_command(context)
0098 print(f"ContainerEvaluator command: {docker_cmd}")
0099
0100
0101
0102
0103
0104
0105 def example_workflow_definition():
0106 """Define a complete workflow with multiple stages and operators."""
0107
0108 workflow = WorkflowDefinition(
0109 name='dtlz2_evaluation',
0110 description='Evaluate DTLZ2 problem with multiple stages',
0111
0112
0113 scheduler=SchedulerConfiguration(
0114 runner_type='JobLibRunner',
0115 joblib=JobLibRunnerConfig(n_jobs=-1)
0116 ),
0117
0118 branches=[
0119 BranchDefinition(
0120 name='main',
0121 stages=[
0122
0123 StageDefinition(
0124 name='prepare',
0125 jobs=[
0126 JobDefinition(
0127 name='prepare_params',
0128 command='python scripts/prepare.py',
0129 payload={
0130 'input_design': '{design_point}',
0131 'output_file': '{work_dir}/params.json'
0132 },
0133 outputs=[
0134 ArtifactSpec(path='params.json', format='json')
0135 ]
0136 )
0137 ],
0138
0139 scheduler=None,
0140 parallelism=ParallelismPolicy(
0141 max_concurrent=1,
0142 timeout_sec=60
0143 )
0144 ),
0145
0146
0147 StageDefinition(
0148 name='evaluate',
0149 jobs=[
0150 JobDefinition(
0151 name='dtlz2_eval',
0152 command='python scripts/dtlz2_problem.py',
0153 payload={
0154 'params_file': '{work_dir}/params.json',
0155 'output_file': '{work_dir}/objectives_{job_id}.json'
0156 },
0157 outputs=[
0158 ArtifactSpec(path='objectives_*.json', format='json')
0159 ]
0160 )
0161 ],
0162 job_factory=JobFactory(
0163 type='range',
0164 params={'n': 4}
0165 ),
0166
0167 scheduler=SchedulerConfiguration(
0168 runner_type='SlurmRunner',
0169 slurm={
0170 'partition': 'gpu',
0171 'ntasks': 4,
0172 'cpus_per_task': 2,
0173 'mem_per_node': '16G',
0174 'time': '00:30:00'
0175 }
0176 ),
0177 parallelism=ParallelismPolicy(
0178 max_concurrent=4,
0179 retry_max=2,
0180 timeout_sec=300
0181 )
0182 ),
0183
0184
0185 StageDefinition(
0186 name='aggregate',
0187 jobs=[
0188 JobDefinition(
0189 name='aggregate_results',
0190 command='python scripts/aggregate.py',
0191 payload={
0192 'objectives_dir': '{work_dir}',
0193 'final_output': '{work_dir}/final_objectives.json'
0194 },
0195 outputs=[
0196 ArtifactSpec(path='final_objectives.json', format='json')
0197 ]
0198 )
0199 ],
0200 scheduler=None,
0201 parallelism=ParallelismPolicy(
0202 max_concurrent=1,
0203 timeout_sec=120
0204 )
0205 )
0206 ]
0207 )
0208 ],
0209
0210 objectives=[
0211
0212 ]
0213 )
0214
0215 return workflow
0216
0217
0218
0219
0220
0221
0222 def example_workflow_with_containers():
0223 """Workflow using Docker containers for evaluation."""
0224
0225 workflow = WorkflowDefinition(
0226 name='containerized_evaluation',
0227 description='Evaluate using Docker containers',
0228
0229 scheduler=SchedulerConfiguration(
0230 runner_type='JobLibRunner',
0231 joblib=JobLibRunnerConfig(n_jobs=4)
0232 ),
0233
0234 branches=[
0235 BranchDefinition(
0236 name='main',
0237 stages=[
0238 StageDefinition(
0239 name='evaluate_containers',
0240 jobs=[
0241 JobDefinition(
0242 name='physics_simulation',
0243 command='python /app/container_runner.py',
0244 payload={
0245
0246 'operator_type': 'container',
0247 'image': 'physics-sim:2.0',
0248 'volumes': {
0249 '/host/data': '/data',
0250 '/host/output': '/output'
0251 },
0252 'environment': {
0253 'X1': '{design_point.x1}',
0254 'X2': '{design_point.x2}',
0255 'OUTPUT_DIR': '/output'
0256 },
0257 'resources': {
0258 'memory': '8g',
0259 'cpus': '4'
0260 }
0261 },
0262 outputs=[
0263 ArtifactSpec(path='results.json', format='json')
0264 ]
0265 )
0266 ],
0267 job_factory=JobFactory(
0268 type='range',
0269 params={'n': 2}
0270 ),
0271
0272 scheduler=SchedulerConfiguration(
0273 runner_type='SlurmRunner',
0274 slurm={
0275 'partition': 'gpu',
0276 'gres': 'gpu:2',
0277 'mem': '16G'
0278 }
0279 ),
0280 parallelism=ParallelismPolicy(
0281 max_concurrent=2,
0282 timeout_sec=600
0283 )
0284 )
0285 ]
0286 )
0287 ]
0288 )
0289
0290 return workflow
0291
0292
0293
0294
0295
0296
0297 def example_optimizer_integration():
0298 """Pattern for integrating workflow execution with optimizer."""
0299
0300 from aid2e.utilities.workflows.execution_logger import ExecutionLogger
0301
0302 class DAGExecutor:
0303 """Simple DAG executor (placeholder for full implementation)."""
0304
0305 def __init__(self, workflow: WorkflowDefinition, execution_logger: ExecutionLogger):
0306 self.workflow = workflow
0307 self.logger = execution_logger
0308
0309 def execute(self, design_point: Dict[str, Any]) -> Dict[str, float]:
0310 """Execute workflow for one design point.
0311
0312 Returns:
0313 objectives: {objective_name: value}
0314 """
0315 print(f"\n{'='*60}")
0316 print(f"Evaluating design point: {design_point}")
0317 print(f"{'='*60}")
0318
0319
0320
0321
0322
0323
0324
0325
0326
0327
0328
0329
0330
0331
0332
0333 objectives = {
0334 'f1': design_point.get('x1', 0.0) ** 2,
0335 'f2': (design_point.get('x2', 0.0) - 1.0) ** 2
0336 }
0337
0338 print(f"Objectives: {objectives}")
0339 return objectives
0340
0341
0342 workflow = example_workflow_definition()
0343 logger = ExecutionLogger(output_dir='/tmp/logs')
0344 executor = DAGExecutor(workflow, logger)
0345
0346
0347 design_points = [
0348 {'x1': 0.2, 'x2': 0.3},
0349 {'x1': 0.5, 'x2': 0.7},
0350 {'x1': 0.8, 'x2': 0.9},
0351 ]
0352
0353
0354 objectives_list = []
0355 for design_point in design_points:
0356 objectives = executor.execute(design_point)
0357 objectives_list.append(objectives)
0358
0359 print(f"\nAll objectives: {objectives_list}")
0360
0361
0362
0363
0364
0365
0366 def example_scheduler_resolution():
0367 """Demonstrate stage scheduler resolution hierarchy."""
0368
0369 workflow = WorkflowDefinition(
0370 name='scheduler_demo',
0371
0372
0373 scheduler=SchedulerConfiguration(
0374 runner_type='JobLibRunner',
0375 joblib=JobLibRunnerConfig(n_jobs=2)
0376 ),
0377
0378 branches=[
0379 BranchDefinition(
0380 name='branch1',
0381
0382 scheduler=SchedulerConfiguration(
0383 runner_type='JobLibRunner',
0384 joblib=JobLibRunnerConfig(n_jobs=4)
0385 ),
0386 stages=[
0387 StageDefinition(
0388 name='stage_uses_branch',
0389 jobs=[JobDefinition(name='job1', command='echo "hi"')],
0390 scheduler=None
0391 ),
0392 StageDefinition(
0393 name='stage_uses_own',
0394 jobs=[JobDefinition(name='job2', command='echo "hi"')],
0395
0396 scheduler=SchedulerConfiguration(
0397 runner_type='SlurmRunner',
0398 slurm={'partition': 'gpu'}
0399 )
0400 ),
0401 ]
0402 ),
0403 BranchDefinition(
0404 name='branch2',
0405 scheduler=None,
0406 stages=[
0407 StageDefinition(
0408 name='stage_uses_global',
0409 jobs=[JobDefinition(name='job3', command='echo "hi"')],
0410 scheduler=None
0411 ),
0412 ]
0413 )
0414 ]
0415 )
0416
0417
0418 print("\nScheduler Resolution:")
0419 print("-" * 60)
0420 print("stage_uses_branch:")
0421 print(" 1. stage.scheduler? No")
0422 print(" 2. branch.scheduler? Yes → JobLibRunner(n_jobs=4)")
0423 print()
0424 print("stage_uses_own:")
0425 print(" 1. stage.scheduler? Yes → SlurmRunner(partition=gpu)")
0426 print()
0427 print("stage_uses_global:")
0428 print(" 1. stage.scheduler? No")
0429 print(" 2. branch.scheduler? No")
0430 print(" 3. workflow.scheduler? Yes → JobLibRunner(n_jobs=2)")
0431
0432
0433
0434
0435
0436
0437 if __name__ == '__main__':
0438 print("=" * 70)
0439 print("Example 1: Direct Operator Usage")
0440 print("=" * 70)
0441 example_operators_direct()
0442
0443 print("\n" + "=" * 70)
0444 print("Example 2: Workflow Definition with Stage Schedulers")
0445 print("=" * 70)
0446 workflow = example_workflow_definition()
0447 print(f"Workflow: {workflow.name}")
0448 print(f"Stages: {[s.name for b in workflow.branches for s in b.stages]}")
0449 for branch in workflow.branches:
0450 for stage in branch.stages:
0451 scheduler_desc = stage.scheduler or "(inherited)"
0452 print(f" {stage.name}: scheduler = {scheduler_desc}")
0453
0454 print("\n" + "=" * 70)
0455 print("Example 3: Workflow with Containers")
0456 print("=" * 70)
0457 workflow = example_workflow_with_containers()
0458 print(f"Workflow: {workflow.name}")
0459
0460 print("\n" + "=" * 70)
0461 print("Example 4: Optimizer Integration")
0462 print("=" * 70)
0463 example_optimizer_integration()
0464
0465 print("\n" + "=" * 70)
0466 print("Example 5: Scheduler Resolution")
0467 print("=" * 70)
0468 example_scheduler_resolution()