Back to home page

EIC code displayed by LXR

 
 

    


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 # Example 1: Using Operators Directly (Low-level)
0035 # ============================================================================
0036 
0037 def example_operators_direct():
0038     """Direct operator usage with JobContext."""
0039     
0040     # Create a task context
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     # Example 1: BashEvaluator
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     # Example 2: PythonEvaluator
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     # Example 3: ContainerEvaluator
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     # Note: Don't execute this without Docker; just show the command
0097     docker_cmd = container_op._build_docker_command(context)
0098     print(f"ContainerEvaluator command: {docker_cmd}")
0099 
0100 
0101 # ============================================================================
0102 # Example 2: Workflow Definition with Stage-specific Schedulers
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         # Global default scheduler
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                     # Stage 1: Prepare parameters (sequential, no special resources)
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                         # Uses global scheduler (JobLibRunner)
0139                         scheduler=None,
0140                         parallelism=ParallelismPolicy(
0141                             max_concurrent=1,
0142                             timeout_sec=60
0143                         )
0144                     ),
0145                     
0146                     # Stage 2: Parallel evaluation (uses STAGE-SPECIFIC scheduler!)
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}  # 4 parallel evaluations
0165                         ),
0166                         # ⭐ STAGE-SPECIFIC SCHEDULER (overrides global!)
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                     # Stage 3: Aggregate results (back to local scheduler)
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,  # Uses global scheduler
0201                         parallelism=ParallelismPolicy(
0202                             max_concurrent=1,
0203                             timeout_sec=120
0204                         )
0205                     )
0206                 ]
0207             )
0208         ],
0209         
0210         objectives=[
0211             # Objectives definition would go here
0212         ]
0213     )
0214     
0215     return workflow
0216 
0217 
0218 # ============================================================================
0219 # Example 3: Workflow with ContainerEvaluator
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                                     # When executed, job will be wrapped in ContainerEvaluator
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                         # Docker containers run on batch system
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 # Example 4: Optimizer Integration Pattern
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             # In real implementation:
0320             # 1. Extract stages from workflow.branches
0321             # 2. Build DagDefinition from stage dependencies
0322             # 3. Topological sort stages
0323             # 4. For each layer:
0324             #    - For each stage:
0325             #      - Get stage.scheduler (or inherit)
0326             #      - Expand jobs via job_factory
0327             #      - Execute each job with scheduler
0328             #      - Log checkpoints
0329             # 5. Compute objectives from final outputs
0330             # 6. Return objectives
0331             
0332             # Placeholder: return dummy objectives
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     # Usage in optimizer loop
0342     workflow = example_workflow_definition()
0343     logger = ExecutionLogger(output_dir='/tmp/logs')
0344     executor = DAGExecutor(workflow, logger)
0345     
0346     # Design points from optimizer
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     # Evaluate each design point
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 # Example 5: Stage Scheduler Resolution
0364 # ============================================================================
0365 
0366 def example_scheduler_resolution():
0367     """Demonstrate stage scheduler resolution hierarchy."""
0368     
0369     workflow = WorkflowDefinition(
0370         name='scheduler_demo',
0371         
0372         # Level 1: Global scheduler
0373         scheduler=SchedulerConfiguration(
0374             runner_type='JobLibRunner',
0375             joblib=JobLibRunnerConfig(n_jobs=2)
0376         ),
0377         
0378         branches=[
0379             BranchDefinition(
0380                 name='branch1',
0381                 # Level 2: Branch scheduler
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  # Uses branch scheduler (n_jobs=4)
0391                     ),
0392                     StageDefinition(
0393                         name='stage_uses_own',
0394                         jobs=[JobDefinition(name='job2', command='echo "hi"')],
0395                         # Level 3: Stage scheduler (MOST SPECIFIC)
0396                         scheduler=SchedulerConfiguration(
0397                             runner_type='SlurmRunner',
0398                             slurm={'partition': 'gpu'}
0399                         )
0400                     ),
0401                 ]
0402             ),
0403             BranchDefinition(
0404                 name='branch2',
0405                 scheduler=None,  # Uses global scheduler
0406                 stages=[
0407                     StageDefinition(
0408                         name='stage_uses_global',
0409                         jobs=[JobDefinition(name='job3', command='echo "hi"')],
0410                         scheduler=None  # Uses global scheduler (n_jobs=2)
0411                     ),
0412                 ]
0413             )
0414         ]
0415     )
0416     
0417     # Resolution for each stage:
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 # Main
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()