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
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
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
0063 joblib_config = JobLibRunnerConfig(
0064 n_jobs=-1,
0065 backend="loky",
0066 timeout=300,
0067 verbose=0,
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
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
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
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
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
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
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
0128 batch_size = min(ax_config.batch_size, ax_config.n_initial_samples - batch * ax_config.batch_size)
0129
0130
0131 candidates = optimizer.suggest_candidates(n_candidates=batch_size)
0132
0133
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
0145 n_bayesian_iterations = 10
0146 for iteration in range(n_bayesian_iterations):
0147
0148 candidates = optimizer.suggest_candidates(n_candidates=ax_config.batch_size)
0149
0150
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
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]:
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
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
0199 joblib_config = JobLibRunnerConfig(
0200 n_jobs=-1,
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
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
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
0232 ax_config = AxOptimizerConfig(
0233 initialization_strategy="sobol",
0234 n_initial_samples=10,
0235 batch_size=3,
0236 generator="BOTORCH_MODULAR",
0237 seed=42,
0238 )
0239
0240
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
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
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
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
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]:
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
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
0339 optimizer1, executor1 = run_case_1_single_branch()
0340 optimizer2, executor2 = run_case_2_separate_branches()
0341
0342
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")