Source code for suboptimumg.vehicle.aerodynamics.aerodynamics

from collections.abc import Generator
from contextlib import contextmanager

from ...constants import RHO
from ..subsystem_models import AeroModel, DRSModel
from ..vehicle_models import VehicleModel


[docs] class DRS: def __init__( self, vehicle_model: VehicleModel, ) -> None: assert ( vehicle_model.aero.drs is not None ), "Cannot construct DRS for a vehicle whose aero.drs parameters are unset" self.vehicle_model: VehicleModel = vehicle_model self.params: DRSModel = vehicle_model.aero.drs
[docs] def get_drs_drag(self, v: float) -> float: """ Calculate the drag force based on the current velocity with DRS active. Parameters ---------- v : float Current velocity (m/s) Returns ------- float Drag force (N) """ return 0.5 * self.params.drs_cd * self.params.drs_front_area * v**2 * RHO
[docs] def get_drs_lift(self, v: float) -> float: """ Calculate the lift force based on the current velocity with DRS active. Parameters ---------- v : float Current velocity (m/s) Returns ------- float Lift force (N) """ return 0.5 * self.params.drs_cl * self.params.drs_front_area * v**2 * RHO
[docs] def get_tot_drs_lift(self, v: float) -> tuple[float, float, float, float]: """ Calculate the total lift force based on the current velocity with DRS active. Parameters ---------- v : float Current velocity (m/s) Returns ------- tuple Front and rear lift forces on each tire (N) (fl, fr, rl, rr) """ tot_lift = self.get_drs_lift(v) front_lift = tot_lift * self.params.drs_cop rear_lift = tot_lift * (1 - self.params.drs_cop) return (front_lift / 2, front_lift / 2, rear_lift / 2, rear_lift / 2)
[docs] def drs_activate(self, drs_threshold: float) -> bool: """ Check whether DRS should be activated based on lateral acceleration. Parameters ---------- drs_threshold : float Current lateral acceleration threshold (m/s^2) Returns ------- bool True if DRS should be activated, False otherwise """ return self.params.drs_present and drs_threshold <= self.params.drs_accel_thresh
[docs] class Aero: def __init__( self, vehicle_model: VehicleModel, ) -> None: self.vehicle_model: VehicleModel = vehicle_model self.params: AeroModel = vehicle_model.aero # Construct DRS if it exists in the vehicle model self.drs: DRS | None if self.params.drs is not None: self.drs = DRS(vehicle_model=vehicle_model) else: self.drs = None self.drs_disabled: bool = False
[docs] @contextmanager def drs_disabled_override(self) -> Generator[None, None, None]: """Force DRS off for the duration of a ``with`` block.""" previously_disabled = self.drs_disabled self.drs_disabled = True try: yield finally: self.drs_disabled = previously_disabled
[docs] def drs_is_active(self, drs_threshold: float) -> bool: """Check whether DRS is currently active, honoring the disable override. Parameters ---------- drs_threshold : float Current lateral acceleration threshold (m/s^2) Returns ------- bool True if DRS is equipped, not overridden off, and within threshold. """ if self.drs is None or self.drs_disabled: return False return self.drs.drs_activate(drs_threshold)
[docs] def get_drag(self, v: float, drs_threshold: float) -> float: """ Calculates the drag force based on the current velocity. Varies the output based on the DRS active state. Returns ------- float Drag force (N) """ if self.drs_is_active(drs_threshold): assert self.drs is not None, "DRS reported active but is not equipped" return self.drs.get_drs_drag(v) return 0.5 * self.params.cd * self.params.front_area * v**2 * RHO
[docs] def get_lift(self, v: float) -> float: """ Calculates the lift force based on the current velocity without DRS. Returns ------- float Lift force (N) """ return 0.5 * self.params.cl * self.params.front_area * v**2 * RHO
[docs] def tire_lift_forces( self, v: float, drs_threshold: float, pitch: float | None = None, roll: float | None = None, ) -> tuple[float, float, float, float]: """ Calculates the lift force on each tire based on the current velocity. Varies the output based on the DRS active state. Returns ------- tuple Front and rear lift forces on each tire (N) """ if self.drs_is_active(drs_threshold): assert self.drs is not None, "DRS reported active but is not equipped" return self.drs.get_tot_drs_lift(v) else: tot_lift = self.get_lift(v) front_lift = tot_lift * self.params.cop rear_lift = tot_lift * (1 - self.params.cop) return ( front_lift / 2, front_lift / 2, rear_lift / 2, rear_lift / 2, )