File indexing completed on 2026-08-12 08:24:55
0001 """DTLZ2 Optimization with Ax Bayesian Optimizer using PanDAiDDS Runner.
0002
0003 This example demonstrates the DAG Executor with Ax optimizer and PanDAiDDS scheduler
0004 running the DTLZ2 problem in two different workflow configurations.
0005
0006 Key Differences from Default Showcase:
0007 - Uses PanDAiDDS scheduler for distributed grid job execution
0008 - Configures PanDA cloud, queue, and resource requirements
0009 - Job names auto-generated as 'user.<username>' (username from system or PANDA_USERNAME env)
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 - PanDAiDDS cloud: US
0017 - PanDAiDDS queue: BNL_PanDA_1
0018
0019 Environment Variables:
0020 - PANDA_USERNAME: Override system username for PanDA job names (optional)
0021
0022 Project: AID2E v0.0.0 - AI assisted Detector Design for EIC
0023 """
0024
0025 import numpy as np
0026 import json
0027 import logging
0028 from pathlib import Path
0029 from typing import Dict, Any, List
0030
0031 from aid2e.utilities.workflows import (
0032 DAGExecutor,
0033 WorkflowDefinition,
0034 BranchDefinition,
0035 StageDefinition,
0036 JobDefinition,
0037 JobContext,
0038 )
0039
0040
0041 from examples.evaluators.dtlz2 import (
0042 dtlz2_both_objectives,
0043 dtlz2_f1_only,
0044 dtlz2_f2_only,
0045 evaluate_both_objectives_wrapper,
0046 evaluate_f1_wrapper,
0047 evaluate_f2_wrapper,
0048 )
0049 from aid2e.utilities.configurations.objectives import (
0050 ObjectiveDefinition,
0051 ObjectiveDirection,
0052 )
0053 from aid2e.optimizers.base import SearchSpace
0054 from aid2e.optimizers.ax import AxOptimizer, AxOptimizerConfig
0055 from aid2e.schedulers.PanDAiDDS.config import PanDAiDDSRunnerConfig
0056
0057
0058
0059
0060
0061
0062
0063
0064
0065
0066
0067
0068
0069 def create_single_branch_workflow() -> WorkflowDefinition:
0070 """Create workflow with single branch computing both objectives."""
0071 compute_job = JobDefinition(
0072 name="compute_objectives",
0073 command="python",
0074 payload={
0075 "evaluator_type": "python",
0076 "python_callable": evaluate_both_objectives_wrapper,
0077 "op_args": (),
0078 "op_kwargs": {},
0079 },
0080 )
0081
0082 eval_stage = StageDefinition(name="evaluate", jobs=[compute_job])
0083 main_branch = BranchDefinition(name="main", stages=[eval_stage])
0084
0085 workflow = WorkflowDefinition(
0086 name="dtlz2_ax_panda_single_branch",
0087 description="DTLZ2 with Ax optimizer and PanDAiDDS scheduler - single branch",
0088 branches=[main_branch],
0089 objectives=[
0090 ObjectiveDefinition(name="f1", direction=ObjectiveDirection.MINIMIZE),
0091 ObjectiveDefinition(name="f2", direction=ObjectiveDirection.MINIMIZE),
0092 ],
0093 )
0094
0095 return workflow
0096
0097
0098 def create_separate_branches_workflow() -> WorkflowDefinition:
0099 """Create workflow with separate branches for each objective."""
0100
0101 f1_job = JobDefinition(
0102 name="compute_f1",
0103 command="python",
0104 payload={
0105 "evaluator_type": "python",
0106 "python_callable": evaluate_f1_wrapper,
0107 "op_args": (),
0108 "op_kwargs": {},
0109 },
0110 )
0111 f1_stage = StageDefinition(name="evaluate_f1", jobs=[f1_job])
0112 f1_branch = BranchDefinition(name="f1_branch", stages=[f1_stage])
0113
0114
0115 f2_job = JobDefinition(
0116 name="compute_f2",
0117 command="python",
0118 payload={
0119 "evaluator_type": "python",
0120 "python_callable": evaluate_f2_wrapper,
0121 "op_args": (),
0122 "op_kwargs": {},
0123 },
0124 )
0125 f2_stage = StageDefinition(name="evaluate_f2", jobs=[f2_job])
0126 f2_branch = BranchDefinition(name="f2_branch", stages=[f2_stage])
0127
0128 workflow = WorkflowDefinition(
0129 name="dtlz2_ax_panda_separate_branches",
0130 description="DTLZ2 with Ax optimizer and PanDAiDDS scheduler - separate branches",
0131 branches=[f1_branch, f2_branch],
0132 objectives=[
0133 ObjectiveDefinition(name="f1", direction=ObjectiveDirection.MINIMIZE),
0134 ObjectiveDefinition(name="f2", direction=ObjectiveDirection.MINIMIZE),
0135 ],
0136 )
0137
0138 return workflow
0139
0140
0141
0142
0143
0144
0145 def run_case_1_single_branch():
0146 """Run Case 1: Single branch with Ax optimizer and PanDAiDDS scheduler."""
0147 print("\n" + "="*80)
0148 print("CASE 1: Single Branch with Ax Bayesian Optimizer + PanDAiDDS Scheduler")
0149 print("="*80)
0150
0151
0152 workflow = create_single_branch_workflow()
0153 print(f"\n✓ Workflow: {workflow.name}")
0154 print(f" Description: {workflow.description}")
0155 print(f" Branches: {len(workflow.branches)}")
0156 print(f" Objectives: {[obj.name for obj in workflow.objectives]}")
0157
0158
0159
0160
0161
0162 panda_config = PanDAiDDSRunnerConfig(
0163
0164 cloud="US",
0165 queue="BNL_PanDA_1",
0166 max_walltime=3600,
0167 core_count=1,
0168 total_memory=4000,
0169 enable_separate_log=True,
0170 init_env="source setup_aid2e.sh && bash install_aid2e_dependencies.sh; ",
0171 job_dir=str(Path.cwd() / "panda_jobs" / "case1"),
0172 post_script="rm -fr .src .venv .local src examples ",
0173 )
0174
0175 print(f"\n✓ Scheduler: PanDAiDDS")
0176 print(f" Workers: {panda_config.core_count} ")
0177 print(f" Backend: {panda_config.queue}")
0178 print(f" Timeout: {panda_config.max_walltime}s per job")
0179
0180
0181 executor = DAGExecutor(
0182 workflow=workflow,
0183 base_output_dir="/tmp/dtlz2_ax_panda_optimization/case1",
0184 log_level="WARNING",
0185 scheduler_config={
0186 "runner_type": "PanDAiDDSRunner",
0187 "config": panda_config,
0188 },
0189 )
0190
0191
0192 search_space = SearchSpace(
0193 parameters={
0194 "x1": {"type": "range", "bounds": [0.0, 1.0]},
0195 "x2": {"type": "range", "bounds": [0.0, 1.0]},
0196 "x3": {"type": "range", "bounds": [0.0, 1.0]},
0197 }
0198 )
0199
0200
0201 ax_config = AxOptimizerConfig(
0202 initialization_strategy="sobol",
0203 n_initial_samples=10,
0204 batch_size=3,
0205 generator="BOTORCH_MODULAR",
0206 seed=42,
0207 )
0208
0209
0210 optimizer = AxOptimizer(
0211 search_space=search_space,
0212 config=ax_config,
0213 objective_names=["f1", "f2"],
0214 seed=42,
0215 )
0216
0217 print(f"\n✓ Optimizer: Ax Bayesian Optimizer")
0218 print(f" Initialization: {ax_config.initialization_strategy} ({ax_config.n_initial_samples} points)")
0219 print(f" Generator: {ax_config.generator}")
0220 print(f" Batch Size: {ax_config.batch_size}")
0221 print(f" Total Iterations: 10 Bayesian iterations")
0222
0223
0224 print(f"\n{'Iter':<6} {'Batch':<6} {'x1':<10} {'x2':<10} {'x3':<10} {'f1':<12} {'f2':<12} {'Phase':<15}")
0225 print("-" * 95)
0226
0227 trial_index = 0
0228
0229
0230 n_sobol_batches = int(np.ceil(ax_config.n_initial_samples / ax_config.batch_size))
0231 for batch in range(n_sobol_batches):
0232
0233 batch_size = min(ax_config.batch_size, ax_config.n_initial_samples - batch * ax_config.batch_size)
0234
0235
0236 candidates = optimizer.suggest_candidates(n_candidates=batch_size)
0237
0238
0239 for i, design_point in enumerate(candidates):
0240 objectives = executor.execute(design_point)
0241 optimizer.update_with_results(trial_index, design_point, objectives)
0242
0243 print(f"{trial_index+1:<6} {batch+1:<6} {design_point['x1']:<10.4f} {design_point['x2']:<10.4f} "
0244 f"{design_point['x3']:<10.4f} {objectives.get('f1', 0):<12.6f} "
0245 f"{objectives.get('f2', 0):<12.6f} {'Sobol Init':<15}")
0246
0247 trial_index += 1
0248
0249
0250 n_bayesian_iterations = 10
0251 for iteration in range(n_bayesian_iterations):
0252
0253 candidates = optimizer.suggest_candidates(n_candidates=ax_config.batch_size)
0254
0255
0256 for i, design_point in enumerate(candidates):
0257 objectives = executor.execute(design_point)
0258 optimizer.update_with_results(trial_index, design_point, objectives)
0259
0260 print(f"{trial_index+1:<6} {iteration+1:<6} {design_point['x1']:<10.4f} {design_point['x2']:<10.4f} "
0261 f"{design_point['x3']:<10.4f} {objectives.get('f1', 0):<12.6f} "
0262 f"{objectives.get('f2', 0):<12.6f} {'Bayesian':<15}")
0263
0264 trial_index += 1
0265
0266
0267 pareto_front = optimizer.get_pareto_front()
0268
0269 print(f"\n✓ Optimization Complete!")
0270 print(f" Total evaluations: {trial_index}")
0271 print(f" Sobol initialization: {ax_config.n_initial_samples}")
0272 print(f" Bayesian iterations: {n_bayesian_iterations}")
0273 print(f" Pareto front points: {len(pareto_front)}")
0274
0275 print(f"\nPareto Front (non-dominated points):")
0276 print(f"{'Trial':<8} {'x1':<10} {'x2':<10} {'x3':<10} {'f1':<12} {'f2':<12}")
0277 print("-" * 72)
0278 for trial in pareto_front[:10]:
0279 dp = trial.parameters
0280 obj = trial.metrics if trial.metrics else {}
0281 print(f"{trial.index:<8} {dp.get('x1', 0):<10.4f} {dp.get('x2', 0):<10.4f} {dp.get('x3', 0):<10.4f} "
0282 f"{obj.get('f1', 0):<12.6f} {obj.get('f2', 0):<12.6f}")
0283
0284 return optimizer, executor
0285
0286
0287 def run_case_2_separate_branches():
0288 """Run Case 2: Separate branches with Ax optimizer and PanDAiDDS scheduler."""
0289 print("\n" + "="*80)
0290 print("CASE 2: Separate Branches with Ax Bayesian Optimizer + PanDAiDDS Scheduler")
0291 print("="*80)
0292
0293
0294 workflow = create_separate_branches_workflow()
0295 print(f"\n✓ Workflow: {workflow.name}")
0296 print(f" Description: {workflow.description}")
0297 print(f" Branches: {len(workflow.branches)} ({[b.name for b in workflow.branches]})")
0298 print(f" Objectives: {[obj.name for obj in workflow.objectives]}")
0299
0300
0301
0302
0303
0304 panda_config = PanDAiDDSRunnerConfig(
0305
0306 cloud="US",
0307 queue="BNL_PanDA_1",
0308 max_walltime=3600,
0309 core_count=1,
0310 total_memory=2000,
0311 enable_separate_log=True,
0312 job_dir=str(Path.cwd() / "panda_jobs" / "case2"),
0313 post_script="rm -fr .src .venv .local src examples ",
0314 )
0315
0316 print(f"\n✓ Scheduler: PanDAiDDS")
0317 print(f" Workers: {panda_config.core_count} ")
0318 print(f" Backend: {panda_config.queue}")
0319 print(f" Timeout: {panda_config.max_walltime}s per job")
0320
0321
0322 executor = DAGExecutor(
0323 workflow=workflow,
0324 base_output_dir="/tmp/dtlz2_ax_panda_optimization/case2",
0325 log_level="WARNING",
0326 scheduler_config={
0327 "runner_type": "PanDAiDDSRunner",
0328 "config": panda_config,
0329 },
0330 )
0331
0332
0333 search_space = SearchSpace(
0334 parameters={
0335 "x1": {"type": "range", "bounds": [0.0, 1.0]},
0336 "x2": {"type": "range", "bounds": [0.0, 1.0]},
0337 "x3": {"type": "range", "bounds": [0.0, 1.0]},
0338 }
0339 )
0340
0341
0342 ax_config = AxOptimizerConfig(
0343 initialization_strategy="sobol",
0344 n_initial_samples=10,
0345 batch_size=3,
0346 generator="BOTORCH_MODULAR",
0347 seed=42,
0348 )
0349
0350
0351 optimizer = AxOptimizer(
0352 search_space=search_space,
0353 config=ax_config,
0354 objective_names=["f1", "f2"],
0355 seed=42,
0356 )
0357
0358 print(f"\n✓ Optimizer: Ax Bayesian Optimizer")
0359 print(f" Initialization: {ax_config.initialization_strategy} ({ax_config.n_initial_samples} points)")
0360 print(f" Generator: {ax_config.generator}")
0361 print(f" Batch Size: {ax_config.batch_size}")
0362 print(f" Total Iterations: 10 Bayesian iterations")
0363
0364
0365 print(f"\n{'Iter':<6} {'Batch':<6} {'x1':<10} {'x2':<10} {'x3':<10} {'f1':<12} {'f2':<12} {'Phase':<15}")
0366 print("-" * 95)
0367
0368 trial_index = 0
0369
0370
0371 n_sobol_batches = int(np.ceil(ax_config.n_initial_samples / ax_config.batch_size))
0372 for batch in range(n_sobol_batches):
0373 batch_size = min(ax_config.batch_size, ax_config.n_initial_samples - batch * ax_config.batch_size)
0374 candidates = optimizer.suggest_candidates(n_candidates=batch_size)
0375
0376 for i, design_point in enumerate(candidates):
0377 objectives = executor.execute(design_point)
0378 optimizer.update_with_results(trial_index, design_point, objectives)
0379
0380 print(f"{trial_index+1:<6} {batch+1:<6} {design_point['x1']:<10.4f} {design_point['x2']:<10.4f} "
0381 f"{design_point['x3']:<10.4f} {objectives.get('f1', 0):<12.6f} "
0382 f"{objectives.get('f2', 0):<12.6f} {'Sobol Init':<15}")
0383
0384 trial_index += 1
0385
0386
0387 n_bayesian_iterations = 10
0388 for iteration in range(n_bayesian_iterations):
0389 candidates = optimizer.suggest_candidates(n_candidates=ax_config.batch_size)
0390
0391 for i, design_point in enumerate(candidates):
0392 objectives = executor.execute(design_point)
0393 optimizer.update_with_results(trial_index, design_point, objectives)
0394
0395 print(f"{trial_index+1:<6} {iteration+1:<6} {design_point['x1']:<10.4f} {design_point['x2']:<10.4f} "
0396 f"{design_point['x3']:<10.4f} {objectives.get('f1', 0):<12.6f} "
0397 f"{objectives.get('f2', 0):<12.6f} {'Bayesian':<15}")
0398
0399 trial_index += 1
0400
0401
0402 pareto_front = optimizer.get_pareto_front()
0403
0404 print(f"\n✓ Optimization Complete!")
0405 print(f" Total evaluations: {trial_index}")
0406 print(f" Sobol initialization: {ax_config.n_initial_samples}")
0407 print(f" Bayesian iterations: {n_bayesian_iterations}")
0408 print(f" Pareto front points: {len(pareto_front)}")
0409
0410 print(f"\nPareto Front (non-dominated points):")
0411 print(f"{'Trial':<8} {'x1':<10} {'x2':<10} {'x3':<10} {'f1':<12} {'f2':<12}")
0412 print("-" * 72)
0413 for trial in pareto_front[:10]:
0414 dp = trial.parameters
0415 obj = trial.metrics if trial.metrics else {}
0416 print(f"{trial.index:<8} {dp.get('x1', 0):<10.4f} {dp.get('x2', 0):<10.4f} {dp.get('x3', 0):<10.4f} "
0417 f"{obj.get('f1', 0):<12.6f} {obj.get('f2', 0):<12.6f}")
0418
0419 return optimizer, executor
0420
0421
0422
0423
0424
0425
0426 if __name__ == "__main__":
0427
0428 logging.basicConfig(
0429 level=logging.DEBUG,
0430 format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
0431 datefmt='%Y-%m-%d %H:%M:%S'
0432 )
0433
0434
0435 print("\n" + "="*80)
0436 print("DTLZ2 Multi-Objective Bayesian Optimization with Ax + PanDAiDDS Scheduler")
0437 print("="*80)
0438 print("\nConfiguration:")
0439 print(" • 10 Sobol initialization points")
0440 print(" • 10 Bayesian optimization iterations")
0441 print(" • Batch size of 3 (parallel evaluations)")
0442 print(" • Total evaluations: 10 + (10 × 3) = 40 points")
0443 print("\nScheduler:")
0444 print(" • PanDAiDDS for local parallel execution")
0445 print(" • Backend: loky (safe multiprocessing)")
0446 print(" • Workers: all available CPUs")
0447 print("\nWorkflow Configurations:")
0448 print(" 1. Single branch - both objectives in one Python function")
0449 print(" 2. Separate branches - each objective in different branch")
0450 print("\nDTLZ2 Problem:")
0451 print(" Variables: x1, x2, x3 in [0, 1]")
0452 print(" Objectives: f1, f2 (minimize both)")
0453 print(" Optimal Pareto front: x1 in [0, 1], x2 = x3 = 0.5")
0454
0455 try:
0456
0457 optimizer1, executor1 = run_case_1_single_branch()
0458 optimizer2, executor2 = run_case_2_separate_branches()
0459
0460
0461 print("\n" + "="*80)
0462 print("COMPARISON SUMMARY")
0463 print("="*80)
0464
0465 print("\nCase 1 (Single Branch):")
0466 print(f" Workflow: {executor1.workflow.name}")
0467 print(f" Branches: {len(executor1.workflow.branches)}")
0468 print(f" Total evaluations: {len(optimizer1.get_trials())}")
0469 print(f" Pareto front size: {len(optimizer1.get_pareto_front())}")
0470 print(f" Output directory: {executor1.output_dir}")
0471
0472 print("\nCase 2 (Separate Branches):")
0473 print(f" Workflow: {executor2.workflow.name}")
0474 print(f" Branches: {len(executor2.workflow.branches)}")
0475 print(f" Total evaluations: {len(optimizer2.get_trials())}")
0476 print(f" Pareto front size: {len(optimizer2.get_pareto_front())}")
0477 print(f" Output directory: {executor2.output_dir}")
0478
0479 print("\n" + "="*80)
0480 print("✅ Ax + PanDAiDDS Optimization Showcase Complete!")
0481 print("="*80)
0482 print("\nKey Results:")
0483 print(" • Both workflow configurations use identical Ax optimizer")
0484 print(" • Bayesian optimization with SAASBO surrogate model")
0485 print(" • qNEHVI acquisition for multi-objective optimization")
0486 print(" • Batch optimization with 3 parallel evaluations per iteration")
0487 print(" • Sobol initialization ensures good space exploration")
0488 print(" • PanDAiDDS scheduler provides local multi-core parallelism")
0489
0490 except ImportError as e:
0491 print("\n" + "="*80)
0492 print("⚠️ ERROR: Missing Dependencies")
0493 print("="*80)
0494 print(f"\n{e}")
0495 print("\nTo run this showcase, install required packages:")
0496 print(" pip install ax-platform panda")
0497 print("\nAlternatively, use the simple random optimizer showcase:")
0498 print(" python examples/dtlz2_optimizer_showcase.py")