Source code for suboptimumg.sweep.energy_grid_models

from typing import Optional, Tuple

import numpy as np
import numpy.typing as npt
from pydantic import BaseModel, ConfigDict, Field, computed_field

from ..compsim.models import CompetitionData
from .models import SweepParamConfig


[docs] class EnergyGridConfig(BaseModel): """Configuration for energy-constrained grid characterization. Sweeps two design variables on a regular grid. At each grid point, coast_trigger is solved via bisection so that 22-lap endurance energy equals (pack capacity - buffer). """ model_config = ConfigDict(frozen=True, extra="forbid") var_1: SweepParamConfig = Field(description="First grid variable (x-axis)") var_2: SweepParamConfig = Field(description="Second grid variable (y-axis)") coast_trigger_bounds: Tuple[float, float] = Field( default=(5.0, 50.0), description="Search range for coast_trigger bisection (m/s)", ) capacity_buffer_kwh: float = Field( default=0.1, description="Energy safety margin subtracted from pack capacity (kWh)", ge=0, ) bisection_tol: float = Field( default=0.05, description="Convergence tolerance on coast_trigger (m/s)", gt=0, ) bisection_max_iter: int = Field( default=40, description="Maximum bisection iterations per grid point", gt=0, )
[docs] class EnergyGridProcessInput(BaseModel): """Frozen input shipped to each worker process.""" model_config = ConfigDict(arbitrary_types_allowed=True, frozen=True) comp_data: CompetitionData = Field(description="Serialized competition config") var_1_name: str = Field(description="First variable dotted name") var_1_value: float = Field(description="First variable value") var_2_name: str = Field(description="Second variable dotted name") var_2_value: float = Field(description="Second variable value") coast_trigger_lo: float = Field(description="Lower bound for bisection") coast_trigger_hi: float = Field(description="Upper bound for bisection") capacity_buffer_kwh: float = Field(description="Energy safety margin (kWh)") bisection_tol: float = Field(description="Bisection convergence tolerance") bisection_max_iter: int = Field(description="Max bisection iterations") x_idx: int = Field(description="Grid x index") y_idx: int = Field(description="Grid y index")
[docs] class EnergyGridProcessOutput(BaseModel): """Frozen output returned from each worker process.""" model_config = ConfigDict(frozen=True) x_idx: int = Field(description="Grid x index") y_idx: int = Field(description="Grid y index") optimal_coast_trigger: float = Field(description="Solved coast_trigger (m/s)") feasible: bool = Field(description="Whether energy budget is satisfiable") total_points: float = Field(description="Sum of all event points") accel_pts: float = Field(default=0) skidpad_pts: float = Field(default=0) autoX_pts: float = Field(default=0) endurance_pts: float = Field(default=0) efficiency_pts: float = Field(default=0) accel_t: float = Field(default=0) skidpad_t: float = Field(default=0) autoX_t: float = Field(default=0) endurance_t: float = Field(default=0) net_energy_kwh: float = Field(default=0, description="22-lap net energy (kWh)") capacity_kwh: float = Field(default=0, description="Available pack capacity (kWh)") vehicle_mass: float = Field(default=0, description="Effective vehicle mass (kg)") bisection_iters: int = Field(default=0, description="Bisection iterations used") warnings: str = Field(default="") error: Optional[str] = Field(default=None)
[docs] class EnergyGridData(BaseModel): """Collected 2D grid results for all metrics.""" model_config = ConfigDict(arbitrary_types_allowed=True, frozen=True) var_1_name: str = Field(description="First variable name (x-axis)") var_1_list: npt.NDArray[np.float64] = Field(description="First variable values") var_2_name: str = Field(description="Second variable name (y-axis)") var_2_list: npt.NDArray[np.float64] = Field(description="Second variable values") # Nominal pack parameters for kg to kWh axis conversion nominal_pack_weight: float = Field(description="Baseline pack weight (kg)") nominal_capacity: float = Field(description="Baseline pack capacity (kWh)") energy_density: float = Field(description="Pack energy density (kWh/kg)") total_pts: npt.NDArray[np.float64] = Field( description="2D list of total_pts, corresponding to grid generated from var_1_list x var_2_list" ) accel_pts: npt.NDArray[np.float64] = Field( description="2D list of accel event points, same shape as total_pts" ) skidpad_pts: npt.NDArray[np.float64] = Field( description="2D list of skidpad event points, same shape as total_pts" ) autoX_pts: npt.NDArray[np.float64] = Field( description="2D list of autoX event points, same shape as total_pts" ) endurance_pts: npt.NDArray[np.float64] = Field( description="2D list of endurance event points, same shape as total_pts" ) efficiency_pts: npt.NDArray[np.float64] = Field( description="2D list of efficiency event points, same shape as total_pts" ) accel_t: npt.NDArray[np.float64] = Field( description="2D list of accel event times, same shape as total_pts" ) skidpad_t: npt.NDArray[np.float64] = Field( description="2D list of skidpad event times, same shape as total_pts" ) autoX_t: npt.NDArray[np.float64] = Field( description="2D list of autoX event times, same shape as total_pts" ) endurance_t: npt.NDArray[np.float64] = Field( description="2D list of endurance event times, same shape as total_pts" ) optimal_coast_trigger: npt.NDArray[np.float64] = Field( description="2D list of optimal coast triggers, same shape as total_pts" ) net_energy_kwh: npt.NDArray[np.float64] = Field( description="2D list of net energy (kWh), same shape as total_pts" ) capacity_kwh: npt.NDArray[np.float64] = Field( description="2D list of available pack capacity (kWh), same shape as total_pts" ) vehicle_mass: npt.NDArray[np.float64] = Field( description="2D list of effective vehicle masses (kg), same shape as total_pts" ) feasible: npt.NDArray[np.bool_] = Field( description="2D list of feasibility flags, same shape as total_pts" ) bisection_iters: npt.NDArray[np.int_] = Field( description="2D list of bisection iterations, same shape as total_pts" )
[docs] @classmethod def create( cls, var_1_name: str, var_1_list: np.ndarray, var_2_name: str, var_2_list: np.ndarray, nominal_pack_weight: float, nominal_capacity: float, energy_density: float, ) -> "EnergyGridData": d1, d2 = len(var_1_list), len(var_2_list) z = lambda: np.zeros((d1, d2), dtype=float) return cls( var_1_name=var_1_name, var_1_list=var_1_list, var_2_name=var_2_name, var_2_list=var_2_list, nominal_pack_weight=nominal_pack_weight, nominal_capacity=nominal_capacity, energy_density=energy_density, total_pts=z(), accel_pts=z(), skidpad_pts=z(), autoX_pts=z(), endurance_pts=z(), efficiency_pts=z(), accel_t=z(), skidpad_t=z(), autoX_t=z(), endurance_t=z(), optimal_coast_trigger=z(), net_energy_kwh=z(), capacity_kwh=z(), vehicle_mass=z(), feasible=np.zeros((d1, d2), dtype=bool), bisection_iters=np.zeros((d1, d2), dtype=int), )