Back to home page

EIC code displayed by LXR

 
 

    


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     # Header
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     # Design info
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     # Objectives
0112     objectives = problem.get("objectives", [])
0113     click.echo(f"  Objectives: {len(objectives)}")
0114     if not compact and objectives:
0115         for obj in objectives[:3]:  # Show first 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")