Source code for suboptimumg.compsim.utils
"""
Utility functions for competition scoring and energy calculations.
"""
import numpy as np
import numpy.typing as npt
from ..constants import joule_to_kwh
from .models import *
[docs]
def compute_event_points(scoring_config: EventScoring, t_your: float) -> float:
"""
Compute competition points for an event based on your time.
Parameters
----------
scoring_config : EventScoring
Configuration for event scoring
t_your : float
Your time for the event
Returns
-------
float
Competition points earned
"""
best_time = min(t_your, scoring_config.event_best_time)
worst_time = best_time * scoring_config.worst_time_ratio
your_ratio_to_worst = (worst_time / t_your) ** scoring_config.scale_time_ratio - 1
best_ratio_to_worst = (
worst_time / best_time
) ** scoring_config.scale_time_ratio - 1
return scoring_config.min_points + scoring_config.max_point_gain * (
your_ratio_to_worst / best_ratio_to_worst
)
[docs]
def compute_efficiency_points(
scoring_config: EfficiencyScoring, consumed_energy: float, endurance_runtime: float
) -> float:
"""
Compute efficiency points based on energy consumption and endurance runtime.
Parameters
----------
scoring_config : EfficiencyScoring
Configuration for efficiency scoring
consumed_energy : float
Energy consumed per lap (kWh)
endurance_runtime : float
Total endurance runtime (seconds)
Returns
-------
float
Efficiency points earned
"""
# Calculate our car's CO2 emissions (Assumes power is constant across each lap)
total_energy = consumed_energy * scoring_config.num_endurance_laps
co2_your = scoring_config.co2_scaling * total_energy
# Calculate our average laptime
laptime_your = endurance_runtime / scoring_config.num_endurance_laps
# Calculate our efficiency factor
t_ratio = scoring_config.fastest_lap_time / laptime_your
co2_ratio = scoring_config.co2_min / co2_your
eff_your = t_ratio * co2_ratio
# Calculate the rules-defined minimum efficiency, a constant used to scale points
eff_min_t_ratio = 1.0 / scoring_config.max_time_scale
eff_min_co2_ratio = scoring_config.co2_min / scoring_config.eff_min_co2_your
eff_min = eff_min_t_ratio * eff_min_co2_ratio
# Convert efficiency factor to competition points
eff_points = (
scoring_config.max_eff_points
* (eff_your - eff_min)
/ (scoring_config.eff_max - eff_min)
)
return min(eff_points, scoring_config.max_eff_points)
[docs]
def energy_data(
time_list: npt.NDArray[np.float64], power_list: npt.NDArray[np.float64]
) -> tuple[float, float, float]:
"""
Manually integrates power to determine energy consumption across a lap. Uses trapezoidal integration
Parameters
----------
time_list: npt.ndarray[np.float64]
Cumulative timestamps corresponding to power values (seconds)
power_list : npt.ndarray[np.float64]
List of power values (Watts)
Returns
-------
float
Positive energy consumption in kWh
float
Negative energy consumption in kWh
float
Net energy consumption in kWh
"""
t = np.asarray(time_list, dtype=float)
p = np.asarray(power_list, dtype=float)
dt = np.diff(t) # n - 1 intervals
interval_energy = (
0.5 * (p[:-1] + p[1:])
) * dt # trapezoidal rule over n - 1 intervals
pos_mask = (p[:-1] > 0) & (p[1:] > 0)
neg_mask = (p[:-1] < 0) & (p[1:] < 0)
pos_joules = interval_energy[pos_mask].sum()
neg_joules = interval_energy[neg_mask].sum()
total_joules = interval_energy.sum()
return (
joule_to_kwh(pos_joules),
joule_to_kwh(neg_joules),
joule_to_kwh(total_joules),
)
[docs]
def backsolve_energy_from_efficiency(
scoring_config: EfficiencyScoring,
total_endurance_time: float,
efficiency_score: float,
) -> float:
"""
Calculates consumed energy in kWh given competition configuration,
total endurance event time, and efficiency score.
Parameters
----------
scoring_config : EfficiencyScoring
total_endurance_time : float
Total endurance event time in seconds (tyour)
efficiency_score : float
Efficiency points score
Returns
-------
float
Consumed energy in kWh
"""
# Calculate eff_min (efficiency factor minimum)
# From: compute_efficiency_factor(max_time_scale * fastest_lap_time, eff_min_co2_your)
eff_min = (1 / scoring_config.max_time_scale) * (
scoring_config.co2_min / scoring_config.eff_min_co2_your
)
# Solve for eff_your from efficiency_score
# eff_points = max_eff_points * (eff_your - eff_min) / (eff_max - eff_min)
eff_your = (
efficiency_score
* (scoring_config.eff_max - eff_min)
/ scoring_config.max_eff_points
+ eff_min
)
# Calculate average laptime
laptime_your = total_endurance_time / scoring_config.num_endurance_laps
# Solve for co2_your from eff_your
# eff_your = (fastest_lap_time / laptime_your) * (co2_min / co2_your)
co2_your = (
(scoring_config.fastest_lap_time / laptime_your)
* scoring_config.co2_min
/ eff_your
)
# Solve for consumed_energy from co2_your
# co2_your = co2_scaling * consumed_energy_total
consumed_energy_kwh = co2_your / scoring_config.co2_scaling
return consumed_energy_kwh
[docs]
def pretty_print_results(
comp_results: CompetitionResults,
):
"""
Print formatted competition results.
Parameters
----------
comp_results : CompetitionResults
"""
accel_time = round(comp_results.accel.tyour, 3)
accel_pts = round(comp_results.accel.points, 1)
print(f"{'Accel time:':<12} {accel_time:<8} {'Accel points:':<14} {accel_pts}")
skidpad_time = round(comp_results.skidpad.tyour, 3)
skidpad_pts = round(comp_results.skidpad.points, 1)
print(f"{'SkidP time:':<12} {skidpad_time:<8} {'SkidP points:':<14} {skidpad_pts}")
autox_time = round(comp_results.autoX.tyour, 3)
autox_pts = round(comp_results.autoX.points, 1)
print(f"{'AutoX time:':<12} {autox_time:<8} {'AutoX points:':<14} {autox_pts}")
endurance_time = round(comp_results.endurance.tyour, 1)
endurance_pts = round(comp_results.endurance.points, 1)
efficiency_pts = round(comp_results.efficiency_points, 1)
print(
f"{'Endur time:':<12} {endurance_time:<8} {'Endur points:':<14} {endurance_pts}"
)
print(f"{'':<21} {'Effcy points:':<14} {efficiency_pts}")
total_pts = round(comp_results.total_points, 1)
print(f"{'':<21} {'Total points:':<14} {total_pts}")