Some checks failed
Build Simulation and Test / Run All Tests (push) Failing after 8m17s
Major rewrite.
194 lines
7.4 KiB
Python
194 lines
7.4 KiB
Python
"""Configuration loading and management."""
|
|
|
|
import json
|
|
import yaml
|
|
from pathlib import Path
|
|
from typing import Dict, Any, Union
|
|
from dataclasses import asdict
|
|
|
|
from .simulation_config import HeadlessConfig, InteractiveConfig, ExperimentConfig
|
|
|
|
|
|
class ConfigLoader:
|
|
"""Loads configuration from files and creates config objects."""
|
|
|
|
@staticmethod
|
|
def load_headless_config(config_path: Union[str, Path]) -> HeadlessConfig:
|
|
"""Load headless configuration from file."""
|
|
config_path = Path(config_path)
|
|
|
|
if not config_path.exists():
|
|
raise FileNotFoundError(f"Config file not found: {config_path}")
|
|
|
|
with open(config_path, 'r') as f:
|
|
if config_path.suffix.lower() == '.json':
|
|
data = json.load(f)
|
|
elif config_path.suffix.lower() in ['.yml', '.yaml']:
|
|
data = yaml.safe_load(f)
|
|
else:
|
|
raise ValueError(f"Unsupported config format: {config_path.suffix}")
|
|
|
|
return ConfigLoader._dict_to_headless_config(data)
|
|
|
|
@staticmethod
|
|
def load_interactive_config(config_path: Union[str, Path]) -> InteractiveConfig:
|
|
"""Load interactive configuration from file."""
|
|
config_path = Path(config_path)
|
|
|
|
if not config_path.exists():
|
|
# Return default config if file doesn't exist
|
|
return InteractiveConfig()
|
|
|
|
with open(config_path, 'r') as f:
|
|
if config_path.suffix.lower() == '.json':
|
|
data = json.load(f)
|
|
elif config_path.suffix.lower() in ['.yml', '.yaml']:
|
|
data = yaml.safe_load(f)
|
|
else:
|
|
raise ValueError(f"Unsupported config format: {config_path.suffix}")
|
|
|
|
return ConfigLoader._dict_to_interactive_config(data)
|
|
|
|
@staticmethod
|
|
def load_experiment_config(config_path: Union[str, Path]) -> ExperimentConfig:
|
|
"""Load experiment configuration from file."""
|
|
config_path = Path(config_path)
|
|
|
|
if not config_path.exists():
|
|
raise FileNotFoundError(f"Config file not found: {config_path}")
|
|
|
|
with open(config_path, 'r') as f:
|
|
if config_path.suffix.lower() == '.json':
|
|
data = json.load(f)
|
|
elif config_path.suffix.lower() in ['.yml', '.yaml']:
|
|
data = yaml.safe_load(f)
|
|
else:
|
|
raise ValueError(f"Unsupported config format: {config_path.suffix}")
|
|
|
|
return ConfigLoader._dict_to_experiment_config(data)
|
|
|
|
@staticmethod
|
|
def save_config(config: Union[HeadlessConfig, InteractiveConfig, ExperimentConfig],
|
|
config_path: Union[str, Path]):
|
|
"""Save configuration to file."""
|
|
config_path = Path(config_path)
|
|
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
# Convert config to dictionary
|
|
if isinstance(config, HeadlessConfig):
|
|
data = asdict(config)
|
|
elif isinstance(config, InteractiveConfig):
|
|
data = asdict(config)
|
|
elif isinstance(config, ExperimentConfig):
|
|
data = asdict(config)
|
|
else:
|
|
raise ValueError(f"Unknown config type: {type(config)}")
|
|
|
|
with open(config_path, 'w') as f:
|
|
if config_path.suffix.lower() == '.json':
|
|
json.dump(data, f, indent=2)
|
|
elif config_path.suffix.lower() in ['.yml', '.yaml']:
|
|
yaml.dump(data, f, default_flow_style=False)
|
|
else:
|
|
raise ValueError(f"Unsupported config format: {config_path.suffix}")
|
|
|
|
@staticmethod
|
|
def _dict_to_headless_config(data: Dict[str, Any]) -> HeadlessConfig:
|
|
"""Convert dictionary to HeadlessConfig."""
|
|
# Extract output config if present
|
|
output_data = data.get('output', {})
|
|
output_config = OutputConfig(**output_data)
|
|
|
|
# Extract simulation config if present
|
|
sim_data = data.get('simulation', {})
|
|
simulation_config = SimulationConfig(**sim_data)
|
|
|
|
return HeadlessConfig(
|
|
max_ticks=data.get('max_ticks'),
|
|
max_duration=data.get('max_duration'),
|
|
output=output_config,
|
|
simulation=simulation_config
|
|
)
|
|
|
|
@staticmethod
|
|
def _dict_to_interactive_config(data: Dict[str, Any]) -> InteractiveConfig:
|
|
"""Convert dictionary to InteractiveConfig."""
|
|
# Extract simulation config if present
|
|
sim_data = data.get('simulation', {})
|
|
simulation_config = SimulationConfig(**sim_data)
|
|
|
|
return InteractiveConfig(
|
|
window_width=data.get('window_width', 0),
|
|
window_height=data.get('window_height', 0),
|
|
vsync=data.get('vsync', True),
|
|
resizable=data.get('resizable', True),
|
|
show_grid=data.get('show_grid', True),
|
|
show_interaction_radius=data.get('show_interaction_radius', False),
|
|
show_legend=data.get('show_legend', True),
|
|
control_bar_height=data.get('control_bar_height', 48),
|
|
inspector_width=data.get('inspector_width', 260),
|
|
properties_width=data.get('properties_width', 320),
|
|
console_height=data.get('console_height', 120),
|
|
simulation=simulation_config
|
|
)
|
|
|
|
@staticmethod
|
|
def _dict_to_experiment_config(data: Dict[str, Any]) -> ExperimentConfig:
|
|
"""Convert dictionary to ExperimentConfig."""
|
|
# Extract base config if present
|
|
base_data = data.get('base_config', {})
|
|
base_config = ConfigLoader._dict_to_headless_config(base_data)
|
|
|
|
return ExperimentConfig(
|
|
name=data.get('name', 'experiment'),
|
|
description=data.get('description', ''),
|
|
runs=data.get('runs', 1),
|
|
run_duration=data.get('run_duration'),
|
|
run_ticks=data.get('run_ticks'),
|
|
variables=data.get('variables', {}),
|
|
base_config=base_config,
|
|
aggregate_results=data.get('aggregate_results', True),
|
|
aggregate_format=data.get('aggregate_format', 'csv')
|
|
)
|
|
|
|
@staticmethod
|
|
def get_default_headless_config() -> HeadlessConfig:
|
|
"""Get default headless configuration."""
|
|
return HeadlessConfig()
|
|
|
|
@staticmethod
|
|
def get_default_interactive_config() -> InteractiveConfig:
|
|
"""Get default interactive configuration."""
|
|
return InteractiveConfig()
|
|
|
|
@staticmethod
|
|
def create_sample_configs(output_dir: str = "configs"):
|
|
"""Create sample configuration files."""
|
|
output_path = Path(output_dir)
|
|
output_path.mkdir(exist_ok=True)
|
|
|
|
# Sample headless config
|
|
headless_config = ConfigLoader.get_default_headless_config()
|
|
ConfigLoader.save_config(headless_config, output_path / "headless_default.json")
|
|
|
|
# Sample interactive config
|
|
interactive_config = ConfigLoader.get_default_interactive_config()
|
|
ConfigLoader.save_config(interactive_config, output_path / "interactive_default.json")
|
|
|
|
# Sample experiment config
|
|
experiment_config = ExperimentConfig(
|
|
name="tps_sweep",
|
|
description="Test different TPS values",
|
|
runs=5,
|
|
run_duration=60.0, # 1 minute per run
|
|
variables={
|
|
"simulation.default_tps": [10, 20, 40, 80, 160]
|
|
}
|
|
)
|
|
ConfigLoader.save_config(experiment_config, output_path / "experiment_tps_sweep.json")
|
|
|
|
print(f"Sample configs created in {output_path}")
|
|
|
|
|
|
# Import OutputConfig for the loader
|
|
from .simulation_config import OutputConfig, SimulationConfig |