from pathlib import Path
import numpy as np
import yaml
from pydantic import TypeAdapter
from .compsim import Competition
from .compsim.models import CompetitionScoring
from .track import (
SampledTrackInput,
SampledTrackReference,
SampledTrackSource,
Track,
TrackConfig,
from_corners,
from_sampled,
)
from .vehicle import Car
from .vehicle.irl_vehicle import IrlCar
from .vehicle.vehicle_models import AnyVehicleModel, ExtendedVehicleModel
_TRACK_CONFIG_ADAPTER: TypeAdapter[TrackConfig] = TypeAdapter(TrackConfig)
_SAVED_TRACK_DECIMALS: int = 4
def _track_from_config(track_config: dict, distance_step: float, base_dir: Path) -> Track:
"""
Build one event track from its ``tracks.<event>`` entry in a competition config.
Parameters
----------
track_config : dict
Either a corner list (see CornerListInput) or a ``path`` to a sampled track YAML.
distance_step : float
Simulation distance step in meters, applied to every track.
base_dir : Path
Directory that a sampled track ``path`` is resolved against.
Returns
-------
Track
"""
track_config["distance_step"] = distance_step
validated = _TRACK_CONFIG_ADAPTER.validate_python(track_config)
if isinstance(validated, SampledTrackReference):
with open(base_dir / validated.path, "r") as file:
sampled = SampledTrackInput.model_validate(yaml.safe_load(file))
return from_sampled(sampled, distance_step)
return from_corners(validated)
def _competition_from_car_and_comp_dict(car: Car, comp_dict: dict, base_dir: Path) -> Competition:
"""
Create a Competition object from a Car instance and competition configuration dictionary.
Parameters
----------
car : Car
comp_dict : dict
The competition configuration dictionary.
base_dir : Path
Directory that sampled track paths in ``comp_dict`` are resolved against.
Returns
-------
Competition
"""
distance_step = comp_dict["simulation"]["distance_step"]
tracks = comp_dict["tracks"]
return Competition(
car,
_track_from_config(tracks["accel"], distance_step, base_dir),
_track_from_config(tracks["skidpad"], distance_step, base_dir),
_track_from_config(tracks["autoX"], distance_step, base_dir),
_track_from_config(tracks["endurance"], distance_step, base_dir),
CompetitionScoring.model_validate(comp_dict["scoring"]),
)
[docs]
def save_track_to_yaml(track: Track, yaml_path: str, source: SampledTrackSource) -> None:
"""
Save a Track as uniformly spaced samples so a competition YAML can reference it.
Parameters
----------
track : Track
Track to save. Its radius, x and y samples are written.
yaml_path : str
Destination file path.
source : SampledTrackSource
Bookkeeping about which log and lap the track came from.
Notes
-----
Reference the saved file from ``parameters/competition.yaml`` with::
tracks:
endurance:
path: tracks/<file name>.yaml
"""
sampled = SampledTrackInput(
distance_step=track.distance_step,
source=source,
radius=np.round(track.radius, _SAVED_TRACK_DECIMALS).tolist(),
x_m=np.round(track.x_m, _SAVED_TRACK_DECIMALS).tolist(),
y_m=np.round(track.y_m, _SAVED_TRACK_DECIMALS).tolist(),
)
with open(yaml_path, "w") as file:
yaml.safe_dump(
sampled.model_dump(), file, default_flow_style=None, width=120, sort_keys=False
)
[docs]
def load_car_from_yaml(yaml_path: str) -> Car | IrlCar:
"""Load a vehicle configuration and return a Car or IrlCar instance.
The YAML type is determined by the ``type`` field:
- ``type: base`` (or omitted) → :class:`~suboptimumg.vehicle.vehicle.Car`
- ``type: irl_setup`` → :class:`~suboptimumg.vehicle.complex_vehicle.IrlCar`
Parameters
----------
yaml_path : str
Path to the YAML configuration file.
Returns
-------
Car or IrlCar
"""
with open(yaml_path, "r") as file:
config = yaml.safe_load(file)
vehicle_model = AnyVehicleModel.validate_python(config)
if isinstance(vehicle_model, ExtendedVehicleModel):
return IrlCar(vehicle_model=vehicle_model)
return Car(vehicle_model=vehicle_model)
[docs]
def load_competition_from_yaml(car_yaml_path: str, comp_yaml_path: str) -> Competition:
"""
Create a Competition object from YAML configuration files.
Parameters
----------
car_yaml_path : str
The file path to the vehicle configuration YAML file.
comp_yaml_path : str
The file path to the competition configuration YAML file.
Returns
-------
Competition
"""
car = load_car_from_yaml(car_yaml_path)
with open(comp_yaml_path, "r") as file:
comp_config = yaml.safe_load(file)
return _competition_from_car_and_comp_dict(car, comp_config, Path(comp_yaml_path).parent)
[docs]
def load_competition_from_dicts(car_config: dict, comp_config: dict) -> Competition:
"""
Create a Competition object from configuration dictionaries.
Parameters
----------
car_config : dict
The vehicle configuration dictionary.
comp_config : dict
The competition configuration dictionary. Sampled track paths resolve
against the current working directory.
Returns
-------
Competition
"""
vehicle_model = AnyVehicleModel.validate_python(car_config)
car = (
IrlCar(vehicle_model=vehicle_model)
if isinstance(vehicle_model, ExtendedVehicleModel)
else Car(vehicle_model=vehicle_model)
)
return _competition_from_car_and_comp_dict(car, comp_config, Path.cwd())