"""Training loops for simulator-backed and hardware-adapted control.""" from __future__ import annotations from dataclasses import dataclass from pathlib import Path import numpy as np from .environment import ControlEnvironment from .masking import mask_population_perturbations from .optimizer import PEPGOptimizer @dataclass class TrainingConfig: generations: int = 20 population_size: int = 20 checkpoint_every: int = 5 checkpoint_dir: str = "checkpoints/rl_control" def run_simulator_training( env: ControlEnvironment, optimizer: PEPGOptimizer, *, config: TrainingConfig, mask: np.ndarray | None = None, ) -> list[dict[str, float]]: """Run PEPG loop against a calibration environment.""" history: list[dict[str, float]] = [] observation = env.reset() for generation in range(config.generations): candidates, perturbations = optimizer.ask(config.population_size) rewards = np.zeros(config.population_size, dtype=np.float64) for idx in range(config.population_size): action = candidates[idx] - optimizer.mean _, reward, _ = env.step(action) rewards[idx] = reward used_perturb = perturbations if mask is not None: used_perturb = mask_population_perturbations(perturbations, observation, mask) optimizer.tell(used_perturb, rewards) observation = env.reset() metrics = { "generation": float(generation), "reward_mean": float(np.mean(rewards)), "reward_max": float(np.max(rewards)), "sigma_mean": float(np.mean(optimizer.sigma)), } history.append(metrics) if (generation + 1) % config.checkpoint_every == 0: ckpt = Path(config.checkpoint_dir) / f"pepg_gen_{generation + 1}.json" optimizer.save_checkpoint(ckpt) return history