Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-11-09 09:29:52

0001 """DTLZ2 Optimization with Ax Bayesian Optimizer using JobLib Runner.
0002 
0003 This example demonstrates the DAG Executor with Ax optimizer and JobLib scheduler
0004 running the DTLZ2 problem in two different workflow configurations.
0005 
0006 Key Differences from Default Showcase:
0007 - Uses JobLib scheduler for parallel job execution
0008 - Configures number of parallel workers (n_jobs)
0009 - Demonstrates local multi-core parallelism
0010 
0011 Configuration:
0012 - 10 Sobol initialization points
0013 - 10 Bayesian optimization iterations
0014 - Batch size of 3 (3 parallel evaluations per iteration)
0015 - Total evaluations: 10 + (10 * 3) = 40 points
0016 - JobLib backend: loky (default)
0017 - Parallel workers: -1 (all available CPUs)
0018 
0019 Project: AID2E v0.0.0 - AI assisted Detector Design for EIC
0020 """
0021 
0022 import numpy as np
0023 import json
0024 import sys
0025 from pathlib import Path
0026 REPO_ROOT = Path(__file__).resolve().parents[3]
0027 if str(REPO_ROOT) not in sys.path:
0028     sys.path.insert(0, str(REPO_ROOT))
0029 
0030 from aid2e.utilities.workflows import DAGExecutor
0031 from aid2e.optimizers.base import SearchSpace
0032 from aid2e.optimizers.ax import AxOptimizer, AxOptimizerConfig
0033 from aid2e.schedulers.JobLib.config import JobLibRunnerConfig
0034 
0035 
0036 from examples.dtlz2.python_api.dtlz2_custom_optimizer import (
0037     create_single_branch_workflow,
0038     create_separate_branches_workflow,
0039 )
0040 
0041 
0042 # =============================================================================
0043 # Optimization Functions
0044 # =============================================================================
0045 
0046 def run_case_1_single_branch():
0047     """Run Case 1: Single branch with Ax optimizer and JobLib scheduler."""
0048     print("\n" + "="*80)
0049     print("CASE 1: Single Branch with Ax Bayesian Optimizer + JobLib Scheduler")
0050     print("="*80)
0051     
0052     # Create workflow
0053     workflow = create_single_branch_workflow(
0054         name="dtlz2_ax_joblib_single_branch",
0055         description="DTLZ2 with Ax optimizer and JobLib scheduler - single branch",
0056     )
0057     print(f"\n✓ Workflow: {workflow.name}")
0058     print(f"  Description: {workflow.description}")
0059     print(f"  Branches: {len(workflow.branches)}")
0060     print(f"  Objectives: {[obj.name for obj in workflow.objectives]}")
0061     
0062     # Configure JobLib scheduler
0063     joblib_config = JobLibRunnerConfig(
0064         n_jobs=-1,  # Use all available CPUs
0065         backend="loky",  # Default backend (safe multiprocessing)
0066         timeout=300,  # 5 minute timeout per job
0067         verbose=0,  # Silent (increase for debugging)
0068     )
0069     
0070     print(f"\n✓ Scheduler: JobLib")
0071     print(f"  Workers: {joblib_config.n_jobs} (all CPUs)")
0072     print(f"  Backend: {joblib_config.backend}")
0073     print(f"  Timeout: {joblib_config.timeout}s per job")
0074     
0075     # Create executor with JobLib scheduler
0076     executor = DAGExecutor(
0077         workflow=workflow,
0078         base_output_dir="/tmp/dtlz2_ax_joblib_optimization/case1",
0079         log_level="WARNING",
0080         scheduler_config={
0081             "runner_type": "JobLibRunner",
0082             "config": joblib_config,
0083         },
0084     )
0085     
0086     # Create search space
0087     search_space = SearchSpace(
0088         parameters={
0089             "x1": {"type": "range", "value": 0.5, "bounds": [0.0, 1.0]},
0090             "x2": {"type": "range", "value": 0.5, "bounds": [0.0, 1.0]},
0091             "x3": {"type": "range", "value": 0.5, "bounds": [0.0, 1.0]},
0092         }
0093     )
0094     
0095     # Create Ax optimizer configuration
0096     ax_config = AxOptimizerConfig(
0097         initialization_strategy="sobol",
0098         n_initial_samples=10,
0099         batch_size=3,
0100         generator="BOTORCH_MODULAR",
0101         seed=42,
0102     )
0103     
0104     # Create Ax optimizer
0105     optimizer = AxOptimizer(
0106         search_space=search_space,
0107         config=ax_config,
0108         objective_names=["f1", "f2"],
0109         seed=42,
0110     )
0111     
0112     print(f"\n✓ Optimizer: Ax Bayesian Optimizer")
0113     print(f"  Initialization: {ax_config.initialization_strategy} ({ax_config.n_initial_samples} points)")
0114     print(f"  Generator: {ax_config.generator}")
0115     print(f"  Batch Size: {ax_config.batch_size}")
0116     print(f"  Total Iterations: 10 Bayesian iterations")
0117     
0118     # Optimization loop
0119     print(f"\n{'Iter':<6} {'Batch':<6} {'x1':<10} {'x2':<10} {'x3':<10} {'f1':<12} {'f2':<12} {'Phase':<15}")
0120     print("-" * 95)
0121     
0122     trial_index = 0
0123     
0124     # Phase 1: Sobol initialization (10 points in batches of 3)
0125     n_sobol_batches = int(np.ceil(ax_config.n_initial_samples / ax_config.batch_size))
0126     for batch in range(n_sobol_batches):
0127         # Get batch size (might be less than 3 for last batch)
0128         batch_size = min(ax_config.batch_size, ax_config.n_initial_samples - batch * ax_config.batch_size)
0129         
0130         # Suggest candidates
0131         candidates = optimizer.suggest_candidates(n_candidates=batch_size)
0132         
0133         # Evaluate each candidate (JobLib handles parallelism internally)
0134         for i, design_point in enumerate(candidates):
0135             objectives = executor.execute(design_point)
0136             optimizer.update_with_results(trial_index, design_point, objectives)
0137             
0138             print(f"{trial_index+1:<6} {batch+1:<6} {design_point['x1']:<10.4f} {design_point['x2']:<10.4f} "
0139                   f"{design_point['x3']:<10.4f} {objectives.get('f1', 0):<12.6f} "
0140                   f"{objectives.get('f2', 0):<12.6f} {'Sobol Init':<15}")
0141             
0142             trial_index += 1
0143     
0144     # Phase 2: Bayesian optimization (10 iterations x 3 points = 30 points)
0145     n_bayesian_iterations = 10
0146     for iteration in range(n_bayesian_iterations):
0147         # Suggest batch of candidates
0148         candidates = optimizer.suggest_candidates(n_candidates=ax_config.batch_size)
0149         
0150         # Evaluate each candidate
0151         for i, design_point in enumerate(candidates):
0152             objectives = executor.execute(design_point)
0153             optimizer.update_with_results(trial_index, design_point, objectives)
0154             
0155             print(f"{trial_index+1:<6} {iteration+1:<6} {design_point['x1']:<10.4f} {design_point['x2']:<10.4f} "
0156                   f"{design_point['x3']:<10.4f} {objectives.get('f1', 0):<12.6f} "
0157                   f"{objectives.get('f2', 0):<12.6f} {'Bayesian':<15}")
0158             
0159             trial_index += 1
0160     
0161     # Get Pareto front
0162     pareto_front = optimizer.get_pareto_front()
0163     
0164     print(f"\n✓ Optimization Complete!")
0165     print(f"  Total evaluations: {trial_index}")
0166     print(f"  Sobol initialization: {ax_config.n_initial_samples}")
0167     print(f"  Bayesian iterations: {n_bayesian_iterations}")
0168     print(f"  Pareto front points: {len(pareto_front)}")
0169     
0170     print(f"\nPareto Front (non-dominated points):")
0171     print(f"{'Trial':<8} {'x1':<10} {'x2':<10} {'x3':<10} {'f1':<12} {'f2':<12}")
0172     print("-" * 72)
0173     for trial in pareto_front[:10]:  # Show top 10
0174         dp = trial.parameters
0175         obj = trial.metrics if trial.metrics else {}
0176         print(f"{trial.index:<8} {dp.get('x1', 0):<10.4f} {dp.get('x2', 0):<10.4f} {dp.get('x3', 0):<10.4f} "
0177               f"{obj.get('f1', 0):<12.6f} {obj.get('f2', 0):<12.6f}")
0178     
0179     return optimizer, executor
0180 
0181 
0182 def run_case_2_separate_branches():
0183     """Run Case 2: Separate branches with Ax optimizer and JobLib scheduler."""
0184     print("\n" + "="*80)
0185     print("CASE 2: Separate Branches with Ax Bayesian Optimizer + JobLib Scheduler")
0186     print("="*80)
0187     
0188     # Create workflow
0189     workflow = create_separate_branches_workflow(
0190         name="dtlz2_ax_joblib_separate_branches",
0191         description="DTLZ2 with Ax optimizer and JobLib scheduler - separate branches",
0192     )
0193     print(f"\n✓ Workflow: {workflow.name}")
0194     print(f"  Description: {workflow.description}")
0195     print(f"  Branches: {len(workflow.branches)} ({[b.name for b in workflow.branches]})")
0196     print(f"  Objectives: {[obj.name for obj in workflow.objectives]}")
0197     
0198     # Configure JobLib scheduler
0199     joblib_config = JobLibRunnerConfig(
0200         n_jobs=-1,  # Use all available CPUs
0201         backend="loky",
0202         timeout=300,
0203         verbose=0,
0204     )
0205     
0206     print(f"\n✓ Scheduler: JobLib")
0207     print(f"  Workers: {joblib_config.n_jobs} (all CPUs)")
0208     print(f"  Backend: {joblib_config.backend}")
0209     print(f"  Timeout: {joblib_config.timeout}s per job")
0210     
0211     # Create executor with JobLib scheduler
0212     executor = DAGExecutor(
0213         workflow=workflow,
0214         base_output_dir="/tmp/dtlz2_ax_joblib_optimization/case2",
0215         log_level="WARNING",
0216         scheduler_config={
0217             "runner_type": "JobLibRunner",
0218             "config": joblib_config,
0219         },
0220     )
0221     
0222     # Create search space
0223     search_space = SearchSpace(
0224         parameters={
0225             "x1": {"type": "range", "value": 0.5, "bounds": [0.0, 1.0]},
0226             "x2": {"type": "range", "value": 0.5, "bounds": [0.0, 1.0]},
0227             "x3": {"type": "range", "value": 0.5, "bounds": [0.0, 1.0]},
0228         }
0229     )
0230     
0231     # Create Ax optimizer configuration (same as Case 1)
0232     ax_config = AxOptimizerConfig(
0233         initialization_strategy="sobol",
0234         n_initial_samples=10,
0235         batch_size=3,
0236         generator="BOTORCH_MODULAR",
0237         seed=42,  # Same seed for reproducibility
0238     )
0239     
0240     # Create Ax optimizer
0241     optimizer = AxOptimizer(
0242         search_space=search_space,
0243         config=ax_config,
0244         objective_names=["f1", "f2"],
0245         seed=42,
0246     )
0247     
0248     print(f"\n✓ Optimizer: Ax Bayesian Optimizer")
0249     print(f"  Initialization: {ax_config.initialization_strategy} ({ax_config.n_initial_samples} points)")
0250     print(f"  Generator: {ax_config.generator}")
0251     print(f"  Batch Size: {ax_config.batch_size}")
0252     print(f"  Total Iterations: 10 Bayesian iterations")
0253     
0254     # Optimization loop
0255     print(f"\n{'Iter':<6} {'Batch':<6} {'x1':<10} {'x2':<10} {'x3':<10} {'f1':<12} {'f2':<12} {'Phase':<15}")
0256     print("-" * 95)
0257     
0258     trial_index = 0
0259     
0260     # Phase 1: Sobol initialization
0261     n_sobol_batches = int(np.ceil(ax_config.n_initial_samples / ax_config.batch_size))
0262     for batch in range(n_sobol_batches):
0263         batch_size = min(ax_config.batch_size, ax_config.n_initial_samples - batch * ax_config.batch_size)
0264         candidates = optimizer.suggest_candidates(n_candidates=batch_size)
0265         
0266         for i, design_point in enumerate(candidates):
0267             objectives = executor.execute(design_point)
0268             optimizer.update_with_results(trial_index, design_point, objectives)
0269             
0270             print(f"{trial_index+1:<6} {batch+1:<6} {design_point['x1']:<10.4f} {design_point['x2']:<10.4f} "
0271                   f"{design_point['x3']:<10.4f} {objectives.get('f1', 0):<12.6f} "
0272                   f"{objectives.get('f2', 0):<12.6f} {'Sobol Init':<15}")
0273             
0274             trial_index += 1
0275     
0276     # Phase 2: Bayesian optimization
0277     n_bayesian_iterations = 10
0278     for iteration in range(n_bayesian_iterations):
0279         candidates = optimizer.suggest_candidates(n_candidates=ax_config.batch_size)
0280         
0281         for i, design_point in enumerate(candidates):
0282             objectives = executor.execute(design_point)
0283             optimizer.update_with_results(trial_index, design_point, objectives)
0284             
0285             print(f"{trial_index+1:<6} {iteration+1:<6} {design_point['x1']:<10.4f} {design_point['x2']:<10.4f} "
0286                   f"{design_point['x3']:<10.4f} {objectives.get('f1', 0):<12.6f} "
0287                   f"{objectives.get('f2', 0):<12.6f} {'Bayesian':<15}")
0288             
0289             trial_index += 1
0290     
0291     # Get Pareto front
0292     pareto_front = optimizer.get_pareto_front()
0293     
0294     print(f"\n✓ Optimization Complete!")
0295     print(f"  Total evaluations: {trial_index}")
0296     print(f"  Sobol initialization: {ax_config.n_initial_samples}")
0297     print(f"  Bayesian iterations: {n_bayesian_iterations}")
0298     print(f"  Pareto front points: {len(pareto_front)}")
0299     
0300     print(f"\nPareto Front (non-dominated points):")
0301     print(f"{'Trial':<8} {'x1':<10} {'x2':<10} {'x3':<10} {'f1':<12} {'f2':<12}")
0302     print("-" * 72)
0303     for trial in pareto_front[:10]:  # Show top 10
0304         dp = trial.parameters
0305         obj = trial.metrics if trial.metrics else {}
0306         print(f"{trial.index:<8} {dp.get('x1', 0):<10.4f} {dp.get('x2', 0):<10.4f} {dp.get('x3', 0):<10.4f} "
0307               f"{obj.get('f1', 0):<12.6f} {obj.get('f2', 0):<12.6f}")
0308     
0309     return optimizer, executor
0310 
0311 
0312 # =============================================================================
0313 # Main Entry Point
0314 # =============================================================================
0315 
0316 if __name__ == "__main__":
0317     print("\n" + "="*80)
0318     print("DTLZ2 Multi-Objective Bayesian Optimization with Ax + JobLib Scheduler")
0319     print("="*80)
0320     print("\nConfiguration:")
0321     print("  • 10 Sobol initialization points")
0322     print("  • 10 Bayesian optimization iterations")
0323     print("  • Batch size of 3 (parallel evaluations)")
0324     print("  • Total evaluations: 10 + (10 × 3) = 40 points")
0325     print("\nScheduler:")
0326     print("  • JobLib for local parallel execution")
0327     print("  • Backend: loky (safe multiprocessing)")
0328     print("  • Workers: all available CPUs")
0329     print("\nWorkflow Configurations:")
0330     print("  1. Single branch - both objectives in one Python function")
0331     print("  2. Separate branches - each objective in different branch")
0332     print("\nDTLZ2 Problem:")
0333     print("  Variables: x1, x2, x3 in [0, 1]")
0334     print("  Objectives: f1, f2 (minimize both)")
0335     print("  Optimal Pareto front: x1 in [0, 1], x2 = x3 = 0.5")
0336     
0337     try:
0338         # Run both cases
0339         optimizer1, executor1 = run_case_1_single_branch()
0340         optimizer2, executor2 = run_case_2_separate_branches()
0341         
0342         # Summary
0343         print("\n" + "="*80)
0344         print("COMPARISON SUMMARY")
0345         print("="*80)
0346         
0347         print("\nCase 1 (Single Branch):")
0348         print(f"  Workflow: {executor1.workflow.name}")
0349         print(f"  Branches: {len(executor1.workflow.branches)}")
0350         print(f"  Total evaluations: {len(optimizer1.get_trials())}")
0351         print(f"  Pareto front size: {len(optimizer1.get_pareto_front())}")
0352         print(f"  Output directory: {executor1.output_dir}")
0353         
0354         print("\nCase 2 (Separate Branches):")
0355         print(f"  Workflow: {executor2.workflow.name}")
0356         print(f"  Branches: {len(executor2.workflow.branches)}")
0357         print(f"  Total evaluations: {len(optimizer2.get_trials())}")
0358         print(f"  Pareto front size: {len(optimizer2.get_pareto_front())}")
0359         print(f"  Output directory: {executor2.output_dir}")
0360         
0361         print("\n" + "="*80)
0362         print("✅ Ax + JobLib Optimization Showcase Complete!")
0363         print("="*80)
0364         print("\nKey Results:")
0365         print("  • Both workflow configurations use identical Ax optimizer")
0366         print("  • Bayesian optimization with SAASBO surrogate model")
0367         print("  • qNEHVI acquisition for multi-objective optimization")
0368         print("  • Batch optimization with 3 parallel evaluations per iteration")
0369         print("  • Sobol initialization ensures good space exploration")
0370         print("  • JobLib scheduler provides local multi-core parallelism")
0371         
0372     except ImportError as e:
0373         print("\n" + "="*80)
0374         print("⚠️  ERROR: Missing Dependencies")
0375         print("="*80)
0376         print(f"\n{e}")
0377         print("\nTo run this showcase, install required packages:")
0378         print("  pip install ax-platform joblib")
0379         print("\nAlternatively, use the simple random optimizer showcase:")
0380         print("  python examples/dtlz2/python_api/dtlz2_custom_optimizer.py")