Back to home page

EIC code displayed by LXR

 
 

    


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

0001 """Run the advanced inline Ax configuration from dtlz2_starter_kit.ipynb in debug mode."""
0002 
0003 from __future__ import annotations
0004 
0005 import argparse
0006 import json
0007 import logging
0008 import os
0009 import sys
0010 from datetime import datetime, timezone
0011 from pathlib import Path
0012 from pprint import pformat
0013 from typing import Any
0014 
0015 REPO_ROOT = Path(__file__).resolve().parents[3]
0016 if str(REPO_ROOT) not in sys.path:
0017     sys.path.insert(0, str(REPO_ROOT))
0018 
0019 from examples.dtlz2.dtlz2_evaluator import objective_payload
0020 from aid2e.utilities import build_optimizer_from_config
0021 from aid2e.utilities.configurations import load_config
0022 from aid2e.utilities.configurations.optimizer_config import OptimizerConfiguration
0023 
0024 
0025 DEFAULT_CONFIG_PATH = Path(__file__).resolve().parents[1] / "configurations" / "dtlz2_ax_joblib.yml"
0026 LOGGER = logging.getLogger("examples.optimizers.ax_advanced_inline_debug")
0027 
0028 
0029 def parse_args(argv: list[str]) -> argparse.Namespace:
0030     parser = argparse.ArgumentParser(
0031         description=(
0032             "Exercise the inline advanced Ax setup from dtlz2_starter_kit.ipynb "
0033             "with debug logging and JSON output."
0034         )
0035     )
0036     parser.add_argument(
0037         "--config",
0038         type=Path,
0039         default=DEFAULT_CONFIG_PATH,
0040         help="Path to the DTLZ2 optimizer config YAML.",
0041     )
0042     parser.add_argument(
0043         "--output-dir",
0044         type=Path,
0045         default=Path.cwd(),
0046         help="Directory for JSON artifacts. Defaults to the current working directory.",
0047     )
0048     parser.add_argument(
0049         "--debug",
0050         action=argparse.BooleanOptionalAction,
0051         default=True,
0052         help="Enable verbose debug logging.",
0053     )
0054     return parser.parse_args(argv)
0055 
0056 
0057 def configure_logging(debug: bool) -> None:
0058     level = logging.DEBUG if debug else logging.INFO
0059     logging.basicConfig(
0060         level=level,
0061         format="%(asctime)s %(levelname)-8s [%(name)s] %(message)s",
0062         force=True,
0063     )
0064     for logger_name in ("aid2e", "ax", "botorch", "gpytorch"):
0065         logging.getLogger(logger_name).setLevel(level)
0066 
0067 
0068 def dtlz2_objectives(parameters: dict[str, Any]) -> dict[str, float]:
0069     """Compute the two-objective DTLZ2 values for the example design space."""
0070     return objective_payload(design_point=parameters)
0071 
0072 
0073 def trial_to_dict(trial: Any) -> dict[str, Any]:
0074     return {
0075         "index": trial.index,
0076         "status": trial.status,
0077         "parameters": dict(trial.parameters),
0078         "metrics": dict(trial.metrics or {}),
0079         "metadata": dict(trial.metadata or {}),
0080     }
0081 
0082 
0083 def evaluate_candidates(
0084     optimizer: Any,
0085     candidates: list[dict[str, Any]],
0086     *,
0087     phase: str,
0088 ) -> list[dict[str, Any]]:
0089     """Evaluate locally and feed results back into the optimizer."""
0090     start_index = len(optimizer.get_trials()) - len(candidates)
0091     records: list[dict[str, Any]] = []
0092 
0093     for offset, parameters in enumerate(candidates):
0094         trial_index = start_index + offset
0095         metrics = dtlz2_objectives(parameters)
0096         optimizer.update_with_results(
0097             trial_index=trial_index,
0098             parameters=parameters,
0099             metrics=metrics,
0100         )
0101         record = {
0102             "trial_index": trial_index,
0103             "phase": phase,
0104             "parameters": dict(parameters),
0105             "metrics": metrics,
0106         }
0107         LOGGER.debug("Recorded %s", pformat(record))
0108         records.append(record)
0109 
0110     return records
0111 
0112 
0113 def build_inline_payload() -> dict[str, Any]:
0114     return {
0115         "name": "ax",
0116         "type": "bayesian",
0117         "parameters": {
0118             "initialization_strategy": "sobol",
0119             "generator": "BOTORCH_MODULAR",
0120             "generator_kwargs": {
0121                 "surrogate_spec": {
0122                     "model_configs": [
0123                         {
0124                             "botorch_model_class": "SaasFullyBayesianSingleTaskGP"
0125                         }
0126                     ]
0127                 },
0128                 "botorch_acqf_class": "qLogNoisyExpectedHypervolumeImprovement",
0129             },
0130             "objective_thresholds": {"f1": 1.0, "f2": 1.0},
0131             "n_initial_samples": 10,
0132             "n_iterations": 20,
0133             "batch_size": 4,
0134             "seed": 7,
0135         },
0136     }
0137 
0138 
0139 def main(argv: list[str]) -> int:
0140     args = parse_args(argv)
0141     configure_logging(args.debug)
0142 
0143     config_path = args.config.resolve()
0144     output_dir = args.output_dir.resolve()
0145     output_dir.mkdir(parents=True, exist_ok=True)
0146 
0147     LOGGER.info("Repository root: %s", REPO_ROOT)
0148     LOGGER.info("Config path: %s", config_path)
0149     LOGGER.info("Output directory: %s", output_dir)
0150     LOGGER.info("Debug logging enabled: %s", args.debug)
0151 
0152     ax_full_config = load_config(str(config_path))
0153     ax_inline_optimizer_payload = build_inline_payload()
0154     ax_inline_optimizer_cfg = OptimizerConfiguration(**ax_inline_optimizer_payload)
0155     ax_inline_optimizer = build_optimizer_from_config(
0156         ax_full_config.problem,
0157         ax_inline_optimizer_cfg,
0158     )
0159 
0160     optimizer_config = ax_inline_optimizer_cfg.parse_algorithm_params()
0161     if optimizer_config is None:
0162         raise RuntimeError("Failed to parse the inline Ax optimizer configuration.")
0163 
0164     nodes = getattr(ax_inline_optimizer.generation_strategy, "nodes", None)
0165     generation_summary = {
0166         "name": ax_inline_optimizer.generation_strategy.name,
0167         "nodes": [node.name for node in nodes] if nodes else [],
0168     }
0169 
0170     print("Inline Ax optimizer payload (SAAS surrogate + qLogNEHVI):")
0171     print(pformat(ax_inline_optimizer_payload))
0172     print("Generation strategy summary:")
0173     print(pformat(generation_summary))
0174 
0175     records: list[dict[str, Any]] = []
0176     first_candidate: dict[str, Any] | None = None
0177 
0178     remaining_init = optimizer_config.n_initial_samples
0179     init_round = 0
0180     while remaining_init > 0:
0181         init_round += 1
0182         current_batch = min(optimizer_config.batch_size, remaining_init)
0183         candidates = ax_inline_optimizer.suggest_candidates(n_candidates=current_batch)
0184         if first_candidate is None and candidates:
0185             first_candidate = dict(candidates[0])
0186             print("First Ax candidate from the inline config:")
0187             print(pformat(first_candidate))
0188         records.extend(
0189             evaluate_candidates(
0190                 ax_inline_optimizer,
0191                 candidates,
0192                 phase=f"init-{init_round}",
0193             )
0194         )
0195         remaining_init -= current_batch
0196 
0197     for iteration in range(optimizer_config.n_iterations):
0198         candidates = ax_inline_optimizer.suggest_candidates(
0199             n_candidates=optimizer_config.batch_size
0200         )
0201         records.extend(
0202             evaluate_candidates(
0203                 ax_inline_optimizer,
0204                 candidates,
0205                 phase=f"iter-{iteration + 1}",
0206             )
0207         )
0208 
0209     summary = {
0210         "n_trials": ax_inline_optimizer.get_optimization_results()["n_trials"],
0211         "pareto_points": len(ax_inline_optimizer.get_pareto_front()),
0212         "objective_names": [obj.name for obj in ax_full_config.problem.objectives],
0213     }
0214 
0215     artifact = {
0216         "timestamp_utc": datetime.now(timezone.utc).isoformat(),
0217         "slurm_job_id": os.environ.get("SLURM_JOB_ID"),
0218         "cwd": str(Path.cwd()),
0219         "config_path": str(config_path),
0220         "output_dir": str(output_dir),
0221         "debug": args.debug,
0222         "inline_payload": ax_inline_optimizer_payload,
0223         "generation_strategy": generation_summary,
0224         "first_candidate": first_candidate,
0225         "records": records,
0226         "summary": summary,
0227         "trials": [trial_to_dict(trial) for trial in ax_inline_optimizer.get_trials()],
0228     }
0229 
0230     job_suffix = os.environ.get("SLURM_JOB_ID", "local")
0231     artifact_path = output_dir / f"ax_advanced_inline_debug_{job_suffix}.json"
0232     artifact_path.write_text(json.dumps(artifact, indent=2), encoding="utf-8")
0233 
0234     print("Optimization summary:")
0235     print(pformat(summary))
0236     print(f"Artifact written to: {artifact_path}")
0237     LOGGER.info("Artifact written to %s", artifact_path)
0238     return 0
0239 
0240 
0241 if __name__ == "__main__":
0242     raise SystemExit(main(sys.argv[1:]))