Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-08-12 08:24:55

0001 """
0002 B0 Tracker z-position optimization with Ax Bayesian Optimizer (toy objectives).
0003 
0004 This is a DTLZ2-style showcase adapted to B0 tracker layer z positions.
0005 
0006 Design variables (cm):
0007 - layer1_z_cm, layer2_z_cm, layer3_z_cm, layer4_z_cm
0008 
0009 Objectives (to be replaced by real computations): resolution toy model
0010 """
0011 
0012 from __future__ import annotations
0013 
0014 import numpy as np
0015 from typing import Dict
0016 
0017 from aid2e.utilities.workflows import (
0018     DAGExecutor,
0019     WorkflowDefinition,
0020     BranchDefinition,
0021     StageDefinition,
0022     JobDefinition,
0023     JobContext,
0024 )
0025 from aid2e.utilities.configurations.objectives import (
0026     ObjectiveDefinition,
0027     ObjectiveDirection,
0028 )
0029 from aid2e.optimizers.base import SearchSpace
0030 from aid2e.optimizers.ax import AxOptimizer, AxOptimizerConfig
0031 
0032 
0033 # =============================================================================
0034 # Toy objective function for B0 z layers
0035 # =============================================================================
0036 
0037 def b0_toy_objectives(z1: float, z2: float, z3: float, z4: float) -> Dict[str, float]:
0038     d12 = z2 - z1
0039     d23 = z3 - z2
0040     d34 = z4 - z3
0041 
0042     lever_arm = z4 - z1
0043     nonuniformity = float(np.std([d12, d23, d34]))
0044     asymmetry = float(abs((z1 + z4) - (z2 + z3)))
0045 
0046     return {
0047         "lever_arm": float(lever_arm),
0048         "nonuniformity": float(nonuniformity),
0049         "asymmetry": float(asymmetry),
0050     }
0051 
0052 
0053 def _is_feasible(z1: float, z2: float, z3: float, z4: float) -> bool:
0054     if not (z1 < z2 < z3 < z4):
0055         return False
0056     if not ((z2 - z1) > 5.0 and (z3 - z2) > 5.0 and (z4 - z3) > 5.0):
0057         return False
0058     return True
0059 
0060 
0061 def evaluate_b0_wrapper(context: JobContext) -> Dict[str, float]:
0062     dp = context.design_point
0063     z1 = float(dp["layer1_z_cm"])
0064     z2 = float(dp["layer2_z_cm"])
0065     z3 = float(dp["layer3_z_cm"])
0066     z4 = float(dp["layer4_z_cm"])
0067 
0068     if not _is_feasible(z1, z2, z3, z4):
0069         out = {"b0_resolution": -1e9}
0070         context.xcom_push("objectives", out)
0071         return out
0072 
0073     # ARBITRARY TOY PROXY FOR RESOLUTION (to be replaced by Geant4 simulations)
0074     d12, d23, d34 = (z2-z1), (z3-z2), (z4-z3)
0075     lever = (z4 - z1)
0076     nonu = float(np.std([d12, d23, d34]))
0077     toy_res = float(lever - 10.0 * nonu)
0078 
0079     out = {"b0_resolution": toy_res}
0080     context.xcom_push("objectives", out)
0081     return out
0082 
0083 
0084 # =============================================================================
0085 # Workflow definition (single branch, single job)
0086 # =============================================================================
0087 
0088 def create_b0_workflow() -> WorkflowDefinition:
0089     compute_job = JobDefinition(
0090         name="compute_b0_objectives",
0091         command="python",
0092         payload={
0093             "evaluator_type": "python",
0094             "python_callable": evaluate_b0_wrapper,
0095             "op_args": (),
0096             "op_kwargs": {},
0097         },
0098     )
0099 
0100     eval_stage = StageDefinition(name="evaluate", jobs=[compute_job])
0101     main_branch = BranchDefinition(name="main", stages=[eval_stage])
0102 
0103     workflow = WorkflowDefinition(
0104         name="b0_ax_single_obj",
0105         description="B0 optimization (single objective)",
0106         branches=[main_branch],
0107         objectives=[
0108             ObjectiveDefinition(name="b0_resolution", direction=ObjectiveDirection.MAXIMIZE),
0109         ],
0110     )
0111     return workflow
0112 
0113 
0114 # =============================================================================
0115 # Main optimization loop (Ax)
0116 # =============================================================================
0117 
0118 def run_b0_ax():
0119     print("\n" + "=" * 80)
0120     print("B0 z-layer toy optimization with Ax Bayesian optimizer")
0121     print("=" * 80)
0122 
0123     workflow = create_b0_workflow()
0124     executor = DAGExecutor(
0125         workflow=workflow,
0126         base_output_dir="/tmp/b0_ax_optimization",
0127         log_level="WARNING",
0128     )
0129 
0130     # Bounds copied from your YAML draft (cm)
0131     search_space = SearchSpace(
0132         parameters={
0133             "layer1_z_cm": {"type": "range", "bounds": [-45.0, -35.0]},
0134             "layer2_z_cm": {"type": "range", "bounds": [-18.0, -8.0]},
0135             "layer3_z_cm": {"type": "range", "bounds": [8.0, 18.0]},
0136             "layer4_z_cm": {"type": "range", "bounds": [35.0, 45.0]},
0137         }
0138     )
0139     
0140     ax_config = AxOptimizerConfig(
0141         initialization_strategy="sobol",
0142         n_initial_samples=12,
0143         batch_size=3,
0144         generator="BOTORCH_MODULAR",
0145         seed=42,
0146     )
0147 
0148     optimizer = AxOptimizer(
0149         search_space=search_space,
0150         config=ax_config,
0151         objective_names=["b0_resolution"],
0152         seed=42,
0153     )
0154 
0155     print("\nConfig:")
0156     print(f"  Sobol init: {ax_config.n_initial_samples}")
0157     print(f"  Batch size: {ax_config.batch_size}")
0158     print("  Objectives: maximize b0_resolution /!\ TOY PROXY FOR NOW /!\ ")
0159 
0160     trial_index = 0
0161 
0162     # Phase 1: Sobol initialization
0163     n_sobol_batches = int(np.ceil(ax_config.n_initial_samples / ax_config.batch_size))
0164     for batch in range(n_sobol_batches):
0165         batch_size = min(ax_config.batch_size, ax_config.n_initial_samples - batch * ax_config.batch_size)
0166         candidates = optimizer.suggest_candidates(n_candidates=batch_size)
0167         for design_point in candidates:
0168             objectives = executor.execute(design_point)
0169             optimizer.update_with_results(trial_index, design_point, objectives)
0170             trial_index += 1
0171 
0172     # Phase 2: Bayesian optimization
0173     n_bayes_iterations = 10
0174     for it in range(n_bayes_iterations):
0175         candidates = optimizer.suggest_candidates(n_candidates=ax_config.batch_size)
0176         for design_point in candidates:
0177             objectives = executor.execute(design_point)
0178             optimizer.update_with_results(trial_index, design_point, objectives)
0179             trial_index += 1
0180 
0181     best = optimizer.get_best_trial()
0182     print("\nBest trial:")
0183     print(f"  trial={best.index}")
0184     print(f"  params={best.parameters}")
0185     print(f"  metrics={best.metrics}")
0186 
0187     return optimizer, executor
0188 
0189 
0190 if __name__ == "__main__":
0191     run_b0_ax()