File indexing completed on 2026-11-09 09:29:52
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
0009 Project: AID2E v0.0.0
0010 """
0011
0012 import sys
0013 from shlex import quote
0014 from pathlib import Path
0015
0016 REPO_ROOT = Path(__file__).resolve().parents[3]
0017 if str(REPO_ROOT) not in sys.path:
0018 sys.path.insert(0, str(REPO_ROOT))
0019
0020 from aid2e.utilities.workflows import (
0021 WorkflowDefinition,
0022 BranchDefinition,
0023 StageDefinition,
0024 JobDefinition,
0025 JobFactory,
0026 ParallelismPolicy,
0027 ArtifactSpec,
0028 JobContext,
0029 BashEvaluator,
0030 PythonEvaluator,
0031 )
0032 from aid2e.utilities.configurations.scheduler_config import SchedulerConfiguration
0033 from aid2e.utilities.configurations.objectives import ObjectiveDefinition
0034 from examples.dtlz2.dtlz2_evaluator import evaluate_both_objectives_wrapper
0035
0036
0037
0038
0039
0040
0041 def example_operators_direct():
0042 """Direct operator usage with JobContext."""
0043
0044
0045 work_dir = Path('/tmp/aid2e_operators')
0046 work_dir.mkdir(parents=True, exist_ok=True)
0047
0048
0049 context = JobContext(
0050 task_id='stage_prepare:task_1',
0051 job_id='task_1',
0052 stage_id='stage_prepare',
0053 workflow_id='dtlz2_eval',
0054 design_point={f'x{i}': 0.5 for i in range(1, 11)},
0055 execution_dir=str(work_dir)
0056 )
0057
0058
0059 bash_op = BashEvaluator(
0060 engine_id='prepare_params',
0061 bash_command='echo "Preparing with x1={{design_point.x1}}, x2={{design_point.x2}}" > params.txt'
0062 )
0063 result = bash_op.execute(context)
0064 print(f"BashEvaluator result: {result}")
0065
0066
0067 python_op = PythonEvaluator(
0068 engine_id='compute',
0069 python_callable=evaluate_both_objectives_wrapper,
0070 )
0071 result = python_op.execute(context)
0072 print(f"PythonEvaluator result: {result}")
0073 print(f"XCom objectives: {context.xcom_pull(context.task_id, 'objectives')}")
0074
0075
0076
0077
0078
0079
0080 def example_workflow_definition():
0081 """Define DTLZ2 command jobs with a stage-level Slurm override."""
0082
0083 workflow = WorkflowDefinition(
0084 name='dtlz2_evaluation',
0085 description='Evaluate DTLZ2 using a stage-specific scheduler',
0086
0087
0088 scheduler=SchedulerConfiguration(
0089 runner_type='JobLibRunner',
0090 parameters={'n_jobs': -1}
0091 ),
0092
0093 branches=[
0094 BranchDefinition(
0095 name='main',
0096 stages=[
0097
0098 StageDefinition(
0099 name='evaluate',
0100 jobs=[
0101 JobDefinition(
0102 name='dtlz2_eval',
0103 command=(
0104 '"{{python_executable}}" "{{repo_root}}/examples/dtlz2/dtlz2_evaluator.py" '
0105 '--design "{{design_file}}" '
0106 '--output "{{output_dir}}/objectives.json" --repeat 0'
0107 ),
0108 outputs=[
0109 ArtifactSpec(path='{{output_dir}}/objectives.json', format='json')
0110 ]
0111 )
0112 ],
0113 job_factory=JobFactory(
0114 type='range',
0115 params={'n': 4}
0116 ),
0117
0118 scheduler=SchedulerConfiguration(
0119 runner_type='SlurmRunner',
0120 parameters={
0121 'partition': 'gpu',
0122 'ntasks': 4,
0123 'cpus_per_task': 2,
0124 'mem': '16G',
0125 'time': '00:30:00'
0126 }
0127 ),
0128 parallelism=ParallelismPolicy(
0129 max_concurrent=4,
0130 retry_max=2,
0131 timeout_sec=300
0132 )
0133 ),
0134 ]
0135 )
0136 ],
0137
0138 objectives=[
0139 ObjectiveDefinition(name='f1', direction='minimize'),
0140 ObjectiveDefinition(name='f2', direction='minimize'),
0141 ]
0142 )
0143
0144 return workflow
0145
0146
0147
0148
0149
0150
0151 def example_workflow_with_containers():
0152 """Define a direct Docker job writing /tmp/aid2e_container_output/objectives.json.
0153
0154 Execution requires Docker and a ten-variable qualified design point.
0155 This example only constructs the workflow.
0156 """
0157
0158 output_dir = Path('/tmp/aid2e_container_output')
0159 command = (
0160 'python /app/examples/dtlz2/dtlz2_evaluator.py '
0161 + ' '.join(f'--x{i} "$X{i}"' for i in range(1, 11))
0162 + ' --output /output/objectives.json --repeat 0'
0163 )
0164 workflow = WorkflowDefinition(
0165 name='containerized_evaluation',
0166 description='Direct Docker execution; no scheduler configured',
0167 branches=[
0168 BranchDefinition(
0169 name='main',
0170 stages=[
0171 StageDefinition(
0172 name='evaluate_containers',
0173 jobs=[
0174 JobDefinition(
0175 name='dtlz2_container',
0176 command=command,
0177 payload={
0178 'evaluator_type': 'container',
0179 'image': 'python:3.11-slim',
0180 'container_command': ['/bin/sh', '-c', quote(command)],
0181 'volumes': {
0182 quote(str(REPO_ROOT)): '/app:ro',
0183 str(output_dir): '/output',
0184 },
0185 'environment': {
0186 f'X{i}': f'{{{{design_point.DTLZ2_variables.x{i}}}}}'
0187 for i in range(1, 11)
0188 },
0189 },
0190 resources={'memory': '4g', 'cpus': '2'},
0191 outputs=[
0192 ArtifactSpec(path=str(output_dir / 'objectives.json'), format='json')
0193 ],
0194 )
0195 ],
0196 )
0197 ]
0198 )
0199 ]
0200 )
0201
0202 return workflow
0203
0204
0205
0206
0207
0208
0209 def example_scheduler_resolution():
0210 """Demonstrate stage scheduler resolution hierarchy."""
0211
0212 workflow = WorkflowDefinition(
0213 name='scheduler_demo',
0214
0215
0216 scheduler=SchedulerConfiguration(
0217 runner_type='JobLibRunner',
0218 parameters={'n_jobs': 2}
0219 ),
0220
0221 branches=[
0222 BranchDefinition(
0223 name='branch1',
0224
0225 scheduler=SchedulerConfiguration(
0226 runner_type='JobLibRunner',
0227 parameters={'n_jobs': 4}
0228 ),
0229 stages=[
0230 StageDefinition(
0231 name='stage_uses_branch',
0232 jobs=[JobDefinition(name='job1', command='echo "hi"')],
0233 scheduler=None
0234 ),
0235 StageDefinition(
0236 name='stage_uses_own',
0237 jobs=[JobDefinition(name='job2', command='echo "hi"')],
0238
0239 scheduler=SchedulerConfiguration(
0240 runner_type='SlurmRunner',
0241 parameters={'partition': 'gpu'}
0242 )
0243 ),
0244 ]
0245 ),
0246 BranchDefinition(
0247 name='branch2',
0248 scheduler=None,
0249 stages=[
0250 StageDefinition(
0251 name='stage_uses_workflow',
0252 jobs=[JobDefinition(name='job3', command='echo "hi"')],
0253 scheduler=None
0254 ),
0255 ]
0256 )
0257 ]
0258 )
0259
0260
0261 print("\nScheduler Resolution:")
0262 print("-" * 60)
0263 print("stage_uses_branch:")
0264 print(" 1. stage.scheduler? No")
0265 print(" 2. branch.scheduler? Yes → JobLibRunner(n_jobs=4)")
0266 print()
0267 print("stage_uses_own:")
0268 print(" 1. stage.scheduler? Yes → SlurmRunner(partition=gpu)")
0269 print()
0270 print("stage_uses_workflow:")
0271 print(" 1. stage.scheduler? No")
0272 print(" 2. branch.scheduler? No")
0273 print(" 3. workflow.scheduler? Yes → JobLibRunner(n_jobs=2)")
0274
0275
0276
0277
0278
0279
0280 if __name__ == '__main__':
0281 print("=" * 70)
0282 print("Example 1: Direct Operator Usage")
0283 print("=" * 70)
0284 example_operators_direct()
0285
0286 print("\n" + "=" * 70)
0287 print("Example 2: Workflow Definition with Stage Schedulers")
0288 print("=" * 70)
0289 workflow = example_workflow_definition()
0290 print(f"Workflow: {workflow.name}")
0291 print(f"Stages: {[s.name for b in workflow.branches for s in b.stages]}")
0292 for branch in workflow.branches:
0293 for stage in branch.stages:
0294 scheduler_desc = stage.scheduler or "(inherited)"
0295 print(f" {stage.name}: scheduler = {scheduler_desc}")
0296
0297 print("\n" + "=" * 70)
0298 print("Example 3: Workflow with Containers")
0299 print("=" * 70)
0300 workflow = example_workflow_with_containers()
0301 print(f"Workflow: {workflow.name} (definition only; execution requires Docker)")
0302
0303 print("\n" + "=" * 70)
0304 print("Example 4: Scheduler Resolution")
0305 print("=" * 70)
0306 example_scheduler_resolution()