File indexing completed on 2026-08-12 08:24:55
0001 """
0002 Shared helper functions for CLI commands.
0003
0004 This module contains utilities used across multiple CLI commands,
0005 including config type detection, parameter counting, and formatters
0006 for different output modes.
0007 """
0008
0009 from typing import Dict, Any
0010 import click
0011 import yaml
0012
0013
0014 def detect_config_type(data: dict) -> str:
0015 """
0016 Detect configuration type from structure.
0017
0018 Args:
0019 data: Parsed YAML configuration data
0020
0021 Returns:
0022 Config type: 'full', 'problem', 'optimizer', 'design', or 'unknown'
0023 """
0024 if "problem" in data and "optimizer" in data:
0025 return "full"
0026 elif "problem" in data:
0027 return "problem"
0028 elif "optimizer" in data:
0029 return "optimizer"
0030 elif "design_space" in data or "design_parameters" in data:
0031 return "design"
0032 else:
0033 return "unknown"
0034
0035
0036 def count_parameters(design_params: dict) -> int:
0037 """
0038 Count total parameters across all groups.
0039
0040 Args:
0041 design_params: Design parameters dictionary (grouped)
0042
0043 Returns:
0044 Total parameter count
0045 """
0046 count = 0
0047 for group_data in design_params.values():
0048 if isinstance(group_data, dict) and "parameters" in group_data:
0049 count += len(group_data["parameters"])
0050 return count
0051
0052
0053 def format_description_text(data: dict, config_type: str, compact: bool, config_file: str):
0054 """
0055 Display text description of config.
0056
0057 Args:
0058 data: Configuration data dictionary
0059 config_type: Detected configuration type
0060 compact: Whether to show compact output
0061 config_file: Path to config file (for display)
0062 """
0063 from pathlib import Path
0064
0065
0066 click.echo(click.style("=" * 70, fg="cyan"))
0067 click.echo(click.style(f"Configuration: {Path(config_file).name}", bold=True, fg="cyan"))
0068 click.echo(click.style(f"Type: {config_type.upper()}", fg="cyan"))
0069 click.echo(click.style("=" * 70, fg="cyan"))
0070 click.echo()
0071
0072 if config_type == "full":
0073 format_full_config(data, compact)
0074 elif config_type == "problem":
0075 format_problem_config(data, compact)
0076 elif config_type == "optimizer":
0077 format_optimizer_config(data, compact)
0078 elif config_type == "design":
0079 format_design_config(data, compact)
0080 else:
0081 click.echo(click.style("⚠ Unknown configuration format", fg="yellow"))
0082 click.echo("\nRaw structure:")
0083 click.echo(f" Top-level keys: {list(data.keys())}")
0084
0085
0086 def format_full_config(data: dict, compact: bool):
0087 """Format full configuration for text output."""
0088 problem = data.get("problem", {})
0089 optimizer = data.get("optimizer", {})
0090 parameters = optimizer.get("parameters", {})
0091
0092 click.echo(click.style("PROBLEM", fg="green", bold=True))
0093 click.echo(f" Name: {problem.get('name', 'N/A')}")
0094 click.echo(f" Type: {problem.get('type', problem.get('problem_type', 'N/A'))}")
0095
0096 if not compact:
0097 click.echo(f" Output: {problem.get('output_location', 'N/A')}")
0098 click.echo(f" Work Dir: {problem.get('work_location', 'N/A')}")
0099
0100
0101 if "design_parameters_file" in problem:
0102 click.echo(f" Design: {problem['design_parameters_file']} (file)")
0103 elif "inline_design" in problem:
0104 design = problem["inline_design"]
0105 param_count = count_parameters(design.get("design_parameters", {}))
0106 click.echo(f" Design: inline ({param_count} parameters)")
0107 elif "design_space" in problem:
0108 param_count = count_parameters(problem["design_space"].get("design_parameters", {}))
0109 click.echo(f" Design: inline ({param_count} parameters)")
0110
0111
0112 objectives = problem.get("objectives", [])
0113 click.echo(f" Objectives: {len(objectives)}")
0114 if not compact and objectives:
0115 for obj in objectives[:3]:
0116 if isinstance(obj, dict):
0117 click.echo(f" - {obj.get('name')}: {'minimize' if obj.get('minimize', True) else 'maximize'}")
0118 else:
0119 click.echo(f" - {obj}")
0120 if len(objectives) > 3:
0121 click.echo(f" ... and {len(objectives) - 3} more")
0122
0123 click.echo()
0124 click.echo(click.style("OPTIMIZER", fg="green", bold=True))
0125 click.echo(f" Algorithm: {optimizer.get('name', 'N/A')} ({optimizer.get('type', 'N/A')})")
0126 click.echo(f" Iterations: {parameters.get('n_iterations', 'N/A')}")
0127 click.echo(f" Initial Samples: {parameters.get('n_initial_samples', 'N/A')}")
0128 click.echo(f" Batch Size: {parameters.get('batch_size', 1)}")
0129
0130 if not compact and parameters:
0131 click.echo(" Parameters:")
0132 for key, val in list(parameters.items())[:5]:
0133 click.echo(f" {key}: {val}")
0134 if len(parameters) > 5:
0135 click.echo(f" ... and {len(parameters) - 5} more")
0136
0137
0138 def format_problem_config(data: dict, compact: bool):
0139 """Format problem-only config for text output."""
0140 problem = data.get("problem", data)
0141
0142 click.echo(f"Name: {problem.get('name', 'N/A')}")
0143 click.echo(f"Type: {problem.get('type', problem.get('problem_type', 'N/A'))}")
0144
0145 objectives = problem.get("objectives", [])
0146 click.echo(f"Objectives: {len(objectives)}")
0147 if not compact and objectives:
0148 for obj in objectives:
0149 if isinstance(obj, dict):
0150 click.echo(f" - {obj.get('name')}: {'minimize' if obj.get('minimize', True) else 'maximize'}")
0151
0152 if "design_parameters_file" in problem:
0153 click.echo(f"Design Source: {problem['design_parameters_file']}")
0154 elif "inline_design" in problem:
0155 param_count = count_parameters(problem["inline_design"].get("design_parameters", {}))
0156 click.echo(f"Design Source: inline ({param_count} parameters)")
0157
0158
0159 def format_optimizer_config(data: dict, compact: bool):
0160 """Format optimizer-only config for text output."""
0161 optimizer = data.get("optimizer", data)
0162 parameters = optimizer.get("parameters", {})
0163
0164 click.echo(f"Name: {optimizer.get('name', 'N/A')}")
0165 click.echo(f"Algorithm: {optimizer.get('name', 'N/A')} ({optimizer.get('type', 'N/A')})")
0166 click.echo(f"Iterations: {parameters.get('n_iterations', 'N/A')}")
0167 click.echo(f"Initial Samples: {parameters.get('n_initial_samples', 'N/A')}")
0168
0169 if not compact and parameters:
0170 click.echo("\nParameters:")
0171 for key, val in parameters.items():
0172 click.echo(f" {key}: {val}")
0173
0174
0175 def format_design_config(data: dict, compact: bool):
0176 """Format design-only config for text output."""
0177 design_space = data.get("design_space", data)
0178 params = design_space.get("design_parameters", {})
0179 constraints = design_space.get("design_constraints", design_space.get("parameter_constraints", []))
0180
0181 param_count = count_parameters(params)
0182 click.echo(f"Parameters: {param_count}")
0183
0184 if not compact:
0185 for group_name, group_data in params.items():
0186 group_params = group_data.get("parameters", {})
0187 click.echo(f" {group_name}: {len(group_params)} parameters")
0188
0189 click.echo(f"Constraints: {len(constraints)}")
0190 if not compact and constraints:
0191 for c in constraints[:3]:
0192 click.echo(f" - {c.get('name')}: {c.get('rule')}")
0193 if len(constraints) > 3:
0194 click.echo(f" ... and {len(constraints) - 3} more")
0195
0196
0197 def extract_description_data(data: dict, config_type: str) -> dict:
0198 """
0199 Extract structured description for JSON/YAML output.
0200
0201 Args:
0202 data: Configuration data dictionary
0203 config_type: Detected configuration type
0204
0205 Returns:
0206 Structured description dictionary
0207 """
0208 description = {
0209 "type": config_type,
0210 "summary": {}
0211 }
0212
0213 if config_type == "full":
0214 problem = data.get("problem", {})
0215 optimizer = data.get("optimizer", {})
0216 parameters = optimizer.get("parameters", {})
0217 description["summary"] = {
0218 "problem_name": problem.get("name"),
0219 "problem_type": problem.get("type", problem.get("problem_type")),
0220 "optimizer": optimizer.get("name"),
0221 "n_iterations": parameters.get("n_iterations"),
0222 "n_objectives": len(problem.get("objectives", []))
0223 }
0224 elif config_type == "problem":
0225 problem = data.get("problem", data)
0226 description["summary"] = {
0227 "name": problem.get("name"),
0228 "type": problem.get("type", problem.get("problem_type")),
0229 "n_objectives": len(problem.get("objectives", []))
0230 }
0231 elif config_type == "optimizer":
0232 optimizer = data.get("optimizer", data)
0233 description["summary"] = {
0234 "name": optimizer.get("name"),
0235 "optimizer": optimizer.get("name"),
0236 "n_iterations": optimizer.get("parameters", {}).get("n_iterations"),
0237 }
0238 elif config_type == "design":
0239 design_space = data.get("design_space", data)
0240 params = design_space.get("design_parameters", {})
0241 constraints = design_space.get("design_constraints", design_space.get("parameter_constraints", []))
0242 description["summary"] = {
0243 "n_parameters": count_parameters(params),
0244 "n_constraints": len(constraints),
0245 "groups": list(params.keys())
0246 }
0247
0248 return description
0249
0250
0251 def inspect_full_config(config, section: str):
0252 """
0253 Inspect full configuration with optional section filter.
0254
0255 Args:
0256 config: Loaded FullConfig object
0257 section: Section to display ('all', 'problem', 'design', 'optimizer')
0258 """
0259 click.echo(click.style("=" * 70, bold=True))
0260 click.echo(click.style(f"Configuration: {config.problem.name}", bold=True))
0261 click.echo(click.style("=" * 70, bold=True))
0262 click.echo()
0263
0264 if section in ["all", "problem"]:
0265 click.echo(click.style("PROBLEM CONFIGURATION", fg="cyan", bold=True))
0266 click.echo(f" Name: {config.problem.name}")
0267 click.echo(f" Type: {config.problem.problem_type}")
0268 click.echo(f" Output Location: {config.problem.output_location}")
0269 click.echo(f" Work Location: {config.problem.work_location}")
0270 click.echo()
0271
0272 if section in ["all", "design"]:
0273 click.echo(click.style("DESIGN PARAMETERS", fg="cyan", bold=True))
0274 param_names = config.problem.design_config.get_parameter_names()
0275 flat_params = config.problem.design_config.get_flat_parameters()
0276
0277 params_by_group = {}
0278 for name in param_names:
0279 group = name.split('.')[0]
0280 params_by_group.setdefault(group, []).append(name)
0281
0282 for group, names in params_by_group.items():
0283 click.echo(f"\n {group} ({len(names)} parameters):")
0284 for name in names:
0285 param = flat_params[name]
0286 param_short_name = name.split('.', 1)[1]
0287 bounds = config.problem.design_config.get_parameter_bounds(name)
0288 if bounds:
0289 click.echo(f" - {param_short_name}: {param.value} {bounds}")
0290 else:
0291 choices = config.problem.design_config.get_parameter_choices(name)
0292 click.echo(f" - {param_short_name}: {param.value} {choices}")
0293
0294 if config.problem.design_config.parameter_constraints:
0295 click.echo()
0296 click.echo(click.style("PARAMETER CONSTRAINTS", fg="cyan", bold=True))
0297 for constraint in config.problem.design_config.parameter_constraints:
0298 click.echo(f" - {constraint.name}")
0299 click.echo(f" Rule: {constraint.rule}")
0300 if constraint.description:
0301 click.echo(f" Description: {constraint.description}")
0302 click.echo()
0303
0304 if section in ["all", "optimizer"]:
0305 click.echo(click.style("OPTIMIZER CONFIGURATION", fg="cyan", bold=True))
0306 click.echo(f" Name: {config.optimizer.name}")
0307 click.echo(f" Type: {config.optimizer.type}")
0308
0309 if config.optimizer.parameters:
0310 click.echo()
0311 click.echo(" Parameters:")
0312 for key, value in config.optimizer.parameters.items():
0313 click.echo(f" - {key}: {value}")
0314 click.echo()
0315
0316 click.echo(click.style("=" * 70, bold=True))
0317
0318
0319 def inspect_problem_config(config):
0320 """
0321 Inspect problem-only configuration.
0322
0323 Args:
0324 config: Loaded ProblemConfiguration object
0325 """
0326 click.echo(click.style("PROBLEM CONFIGURATION", fg="cyan", bold=True))
0327 click.echo(f" Name: {config.name}")
0328 click.echo(f" Type: {config.problem_type}")
0329 click.echo(f" Objectives: {len(config.objectives)}")
0330 for obj in config.objectives:
0331 direction = getattr(obj, "direction", None)
0332 direction_str = direction.value if direction else "minimize" if getattr(obj, "minimize", True) else "maximize"
0333 click.echo(f" - {obj.name}: {direction_str}")
0334
0335
0336 def inspect_design_config(config):
0337 """
0338 Inspect design-only configuration.
0339
0340 Args:
0341 config: Loaded DesignConfig object
0342 """
0343 click.echo(click.style("DESIGN CONFIGURATION", fg="cyan", bold=True))
0344 param_names = config.get_parameter_names()
0345 click.echo(f" Parameters: {len(param_names)}")
0346
0347 params_by_group = {}
0348 for name in param_names:
0349 group = name.split('.')[0]
0350 params_by_group.setdefault(group, []).append(name)
0351
0352 for group, names in params_by_group.items():
0353 click.echo(f"\n {group}: {len(names)} parameters")