File indexing completed on 2026-04-28 07:12:52
0001 """Full configuration loader - combines problem and optimization payloads."""
0002
0003 from pathlib import Path
0004 from typing import Dict, Optional, Any
0005
0006 import yaml
0007 from pydantic import BaseModel
0008
0009 from .problem_config import ProblemConfiguration, ProblemConfigLoader
0010 from .optimization_config import OptimizationConfiguration
0011
0012
0013 class FullConfig(BaseModel):
0014 """Complete configuration combining problem and optimization."""
0015 problem: ProblemConfiguration
0016 optimization: OptimizationConfiguration
0017
0018
0019 def _normalize_full_config_data(data: Dict[str, Any], config_path: Path) -> Dict[str, Any]:
0020 """Normalize loosely structured YAML into a FullConfig-friendly dict."""
0021 if "problem" not in data:
0022 raise ValueError("Config must contain a 'problem' section")
0023
0024 base_dir = config_path.parent
0025 problem_raw = dict(data.get("problem", {}))
0026
0027 type_val = problem_raw.get("type") or problem_raw.get("problem_type") or ""
0028 problem_raw["type"] = type_val
0029 problem_raw["problem_type"] = type_val
0030
0031 problem_raw.setdefault("output_location", str(base_dir / "output"))
0032 problem_raw.setdefault("work_location", str(base_dir / "work"))
0033
0034 if "design_parameters_file" not in problem_raw:
0035 design_space = problem_raw.get("design_space") or {}
0036 if isinstance(design_space, dict) and design_space.get("path"):
0037 problem_raw["design_parameters_file"] = design_space["path"]
0038
0039 if "design_space" in problem_raw:
0040 problem_raw.pop("design_space", None)
0041
0042 problem_cfg = ProblemConfigLoader.from_dict(problem_raw, base_dir=str(base_dir))
0043
0044 if "optimization" in data:
0045 opt_cfg = OptimizationConfiguration(**data["optimization"])
0046 else:
0047 optimizer_block = data.get("optimizer", {})
0048 objectives_raw = problem_raw.get("objectives", [])
0049 bo_params = optimizer_block.get("bo", {}).get("parameters", {}) if isinstance(optimizer_block, dict) else {}
0050 opt_cfg = OptimizationConfiguration(
0051 name=data.get("metadata", {}).get("project", "optimization"),
0052 description=data.get("metadata", {}).get("description", ""),
0053 optimizer={
0054 "name": optimizer_block.get("kind", ""),
0055 "type": optimizer_block.get("kind", ""),
0056 "parameters": bo_params,
0057 },
0058 objectives=[
0059 f"{'minimize' if obj.get('minimize', True) else 'maximize'}:{obj['name']}"
0060 for obj in objectives_raw
0061 if isinstance(obj, dict) and "name" in obj
0062 ],
0063 constraints=[],
0064 n_iterations=optimizer_block.get("max_iterations", 0),
0065 n_initial_samples=bo_params.get("n_initial_samples", 0),
0066 parallel_evaluations=bo_params.get("parallel_evaluations", 1),
0067 )
0068
0069 return {"problem": problem_cfg, "optimization": opt_cfg}
0070
0071
0072 def load_config(config_file: str) -> FullConfig:
0073 """
0074 Load complete configuration from a YAML file.
0075
0076 Args:
0077 config_file: Path to YAML configuration file
0078
0079 Returns:
0080 FullConfig object with all configurations loaded
0081
0082 Raises:
0083 FileNotFoundError: If config file doesn't exist
0084 ValueError: If configuration is invalid
0085 """
0086 config_path = Path(config_file)
0087
0088 if not config_path.exists():
0089 raise FileNotFoundError(f"Config file not found: {config_file}")
0090
0091 with open(config_path, 'r') as f:
0092 data = yaml.safe_load(f)
0093
0094 normalized = _normalize_full_config_data(data or {}, config_path)
0095
0096 return FullConfig(**normalized)