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
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
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
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
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
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
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
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()