import enum
from enum import Enum
from ..log_analysis import (
CUMULATIVE_DISTANCE,
GROUND_SPEED,
POS_X,
POS_Y,
add_cumulative_distance,
)
from ..plotting.gps_visualization import (
plot_time_delta_comparison_traces,
plot_track_heatmap,
)
from ..plotting.plotting_constants import (
ColorbarConfig,
FontConfig,
LayoutConfig,
)
from ..track import *
from ..vehicle import Car
from .lap import *
from .models import *
from .utils import energy_data
[docs]
class OverlayOptions(str, Enum):
VELOCITY = "lap_vels"
ACCELERATION = "lap_accs"
MOTOR_TORQUE = "lap_eff_motor_torques"
POWER = "lap_powers"
[docs]
class CustomRun:
def __init__(
self,
mycar: Car,
track: Track,
):
self.mycar = mycar
self.track = track
self.sim_results_no_coast: LapsimResults | None = None
self.sim_results_coast: LapsimResults | None = None
[docs]
def run(
self, extract_internal_data: bool = False, initial_velocity: float = -1
) -> LapsimResults:
"""
Simulates a run along the custom track. Sets the sim_results attribute for future use.
Parameters
----------
extract_internal_data : bool, optional
Whether to extract internal data during the simulation, by default False.
initial_velocity : float, optional
Starting velocity seed (m/s). ``-1`` (default) lets the solver
pick seeds purely from the v_max profile.
Returns
-------
LapsimResults
Results from running the custom track.
"""
print("Running simulation on custom track...")
self.sim_results_no_coast = lapsim(
mycar=self.mycar,
track=self.track,
use_coast=False,
extract_internal_data=extract_internal_data,
initial_velocity=initial_velocity,
)
self.sim_results_coast = lapsim(
mycar=self.mycar,
track=self.track,
use_coast=True,
extract_internal_data=extract_internal_data,
initial_velocity=initial_velocity,
)
return self.sim_results_no_coast, self.sim_results_coast
[docs]
def print_results_summary(self) -> None:
"""
Print a summary of the custom run results.
"""
if self.sim_results_no_coast is None or self.sim_results_coast is None:
self.run()
distance = round(self.track.cumulative_dist[-1], 2)
print(f"{'Distance:':<10} {str(distance) + ' m':<10} ")
time_no_coast = round(self.sim_results_no_coast.lap_t[-1], 2)
time_coast = round(self.sim_results_coast.lap_t[-1], 2)
_, regened_energy_no_coast, consumed_energy_no_coast = energy_data(
self.sim_results_no_coast.lap_t,
self.sim_results_no_coast.lap_powers,
)
_, regened_energy_coast, consumed_energy_coast = energy_data(
self.sim_results_coast.lap_t, self.sim_results_coast.lap_powers
)
print("\nNO COAST")
print(f"{'Laptime:':<10} {str(time_no_coast) + ' s':<10}")
print(
f"{'Net Energy:':<10} {str(round(consumed_energy_no_coast, 3)) + ' kWh':<10} {'Regen:':<8} {str(round(regened_energy_no_coast, 3)) + ' kWh':<10}"
)
print("\nCOAST")
print(f"{'Laptime:':<10} {str(time_coast) + ' s':<10}")
print(
f"{'Net Energy:':<10} {str(round(consumed_energy_coast, 3)) + ' kWh':<10} {'Regen:':<8} {str(round(regened_energy_coast, 3)) + ' kWh':<10}"
)
[docs]
def plot_real_lap_comparison(
self,
real_lap_data: SingleRunData,
real_data_key: str,
qss_var: OverlayOptions,
*,
layout_config: LayoutConfig = DEFAULT_LAYOUT_CONFIG,
font_config: FontConfig = DEFAULT_FONT_CONFIG,
colorbar_config: ColorbarConfig = DEFAULT_COLORBAR_CONFIG,
) -> go.Figure:
"""
Prints a comparison of the simulated lap with a real lap(s) from PERDA.
Parameters
----------
real_lap_data : SingleRunData
SingleRunData representing a single real lap to compare against.
real_data_key : str
The key for the real lap data to use for comparison.
qss_var : OverlayOptions
The QSS variable to overlay on the plot.
layout_config : LayoutConfig, optional
font_config : FontConfig, optional
colorbar_config : ColorbarConfig, optional
"""
missing = [c for c in (POS_X, POS_Y) if c not in real_lap_data]
if missing:
raise KeyError(
f"plot_real_lap_comparison: missing variables(s) {missing}.\n"
f"Try running preprocess_gps on the real_lap_data"
)
if self.sim_results_no_coast is None or self.sim_results_coast is None:
self.run()
di = real_lap_data[real_data_key]
_, di_aligned = left_join_data_instances(real_lap_data[POS_X], [di])
qss_data = getattr(self.sim_results_coast, qss_var.value)
return plot_track_heatmap(
baseline_pos_x=self.track.x_m,
baseline_pos_y=self.track.y_m,
baseline_var=qss_data,
comparison_pos_x=real_lap_data[POS_X].value_np,
comparison_pos_y=real_lap_data[POS_Y].value_np,
comparison_var=di_aligned.value_np,
variable_name=qss_var.name,
baseline_label=f"{qss_var.value} (QSS)",
comparison_label=f"{real_data_key} (Real Data)",
title=f"Simulated {qss_var.name} vs Real Lap Data",
layout_config=layout_config,
font_config=font_config,
colorbar_config=colorbar_config,
)
[docs]
def plot_real_lap_time_delta_traces(
self,
real_laps: list[SingleRunData],
real_data_key: str,
qss_var: OverlayOptions,
layout_config: LayoutConfig = DEFAULT_LAYOUT_CONFIG,
font_config: FontConfig = DEFAULT_FONT_CONFIG,
) -> go.Figure:
"""
Plots comparisons of the simulated lap with multiple real laps from PERDA.
Parameters
----------
real_laps : list[SingleRunData]
List of SingleRunData representing real laps to compare against.
real_data_key : str
The key for the real lap data to use for comparison.
qss_var : OverlayOptions
The QSS variable to overlay on the plot.
layout_config : LayoutConfig, optional
font_config : FontConfig, optional
colorbar_config : ColorbarConfig, optional
"""
missing = [i + 1 for i, lap in enumerate(real_laps) if GROUND_SPEED not in lap]
if missing:
raise KeyError(
f"plot_real_lap_comparison: laps missing groundspeed variables {missing} (1-indexed).\n"
f"Try running add_groundspeed on all real_laps"
)
if self.sim_results_no_coast is None or self.sim_results_coast is None:
self.run()
for lap in real_laps:
if CUMULATIVE_DISTANCE not in lap:
add_cumulative_distance(lap)
laps = {
"QSS": (
self.track.get_dist_from_start_array(),
getattr(self.sim_results_coast, qss_var.value),
self.sim_results_coast.lap_vels,
)
}
for i, lap in enumerate(real_laps):
cum_dist, vel, var = left_join_data_instances(
lap[CUMULATIVE_DISTANCE], [lap[GROUND_SPEED], lap[real_data_key]]
)
laps[f"Lap {i+1}"] = (
cum_dist.value_np,
vel.value_np,
var.value_np,
)
return plot_time_delta_comparison_traces(
laps=laps,
variable_name=real_data_key,
reference_lap="QSS",
layout_config=layout_config,
font_config=font_config,
)