Source code for suboptimumg.vehicle.irl_vehicle

import numpy as np
import numpy.typing as npt

from ..constants import G
from .vehicle import Car
from .vehicle_models import ExtendedVehicleModel


[docs] class IrlCar(Car): """Car extended with real-world session setup parameters for log analysis. Accepts an :class:`ExtendedVehicleModel` which bundles both the simulation car parameters (mass, geometry, aero, etc.) and the session-specific setup (alignment, ackermann curve, suspension stiffness/motion ratios). Parameters ---------- vehicle_model : ExtendedVehicleModel Unified model containing both car.yaml and vehicle_setup.yaml data. """ def __init__(self, vehicle_model: ExtendedVehicleModel): super().__init__(vehicle_model) self.align = vehicle_model.alignment self.ackermann = vehicle_model.steering_setup.ackermann self.sus_setup = vehicle_model.suspension_setup
[docs] def right_tire_angle(self, sw_deg: float | np.ndarray) -> np.ndarray: """Right tire steer angle from the ackermann curve. Positive sw_deg = right turn (right tire is inner, steers more). """ return self.ackermann(sw_deg)
[docs] def left_tire_angle(self, sw_deg: float | np.ndarray) -> np.ndarray: """Left tire steer angle via ackermann symmetry: ``-f(-x)``.""" return -self.ackermann(-np.asarray(sw_deg, dtype=np.float64))
[docs] def tire_angles(self, sw_deg: float | np.ndarray) -> tuple[np.ndarray, np.ndarray]: """Return ``(left_deg, right_deg)`` for a given steering input.""" return self.left_tire_angle(sw_deg), self.right_tire_angle(sw_deg)
[docs] def roll_stiffness_total(self) -> float: """Total roll stiffness (front + rear) in Nm/rad.""" return ( self.sus_setup.roll_stiffness_front_Nm_per_rad + self.sus_setup.roll_stiffness_rear_Nm_per_rad )
[docs] def roll_stiffness_distribution(self) -> float: """Front roll stiffness as a fraction of total (0-1).""" return ( self.sus_setup.roll_stiffness_front_Nm_per_rad / self.roll_stiffness_total() )
[docs] def lateral_weight_transfer( self, ay_g: float | np.ndarray ) -> tuple[np.ndarray, np.ndarray]: """Lateral weight transfer per axle (elastic component only). Parameters ---------- ay_g : float or array Lateral acceleration in g's (positive = turning left). Returns ------- (front_wt_N, rear_wt_N) Elastic weight transfer at front and rear axles (N). Positive = load transfer to the outside wheel. """ ay_g = np.asarray(ay_g, dtype=np.float64) ay = ay_g * G total_lateral_force = self.params.mass * ay roll_dist = self.roll_stiffness_distribution() roll_moment = total_lateral_force * self.params.cg_h front_wt = roll_dist * roll_moment / self.params.front_track rear_wt = (1.0 - roll_dist) * roll_moment / self.params.rear_track return front_wt, rear_wt
[docs] def longitudinal_weight_transfer( self, ax_g: float | np.ndarray ) -> float | np.ndarray: """Longitudinal weight transfer on the front axle. Parameters ---------- ax_g : float or array Longitudinal acceleration in g's (positive = forward accel). Returns ------- float or np.ndarray Change in front axle normal force (N). Negative under acceleration, positive under braking. """ ax_g = np.asarray(ax_g, dtype=np.float64) wt_total = self.params.mass * ax_g * G * self.params.cg_h / self.params.wb return -wt_total
[docs] def pot_to_heave( self, pot_mm: float | npt.NDArray[np.float64], axle: str ) -> float | npt.NDArray[np.float64]: """Converts shock pot displacement (mm) to chassis heave displacement (m). Parameters ---------- pot_mm : float | npt.NDArray[np.float64] Shock pot displacement in millimeters. Positive = compression. axle : str "front" or "rear", used to select the correct motion ratio. Returns ------- float | npt.NDArray[np.float64] Heave displacement in meters. Positive = compression. """ if axle == "front": mr = self.sus_setup.motion_ratio_front elif axle == "rear": mr = self.sus_setup.motion_ratio_rear else: raise ValueError(f"axle must be 'front' or 'rear', got '{axle}'") return np.asarray(pot_mm, dtype=np.float64) / 1000.0 * mr
[docs] def heave_to_force( self, heave_m: float | npt.NDArray[np.float64], axle: str ) -> float | npt.NDArray[np.float64]: """Converts Heave displacement (m) to vertical spring force (N). Parameters ---------- heave_m : float | npt.NDArray[np.float64] Heave displacement in meters. Positive = compression. axle : str "front" or "rear", used to select the correct heave stiffness. Returns ------- float | npt.NDArray[np.float64] Vertical spring force in Newtons. Positive = compression. """ if axle == "front": k = self.sus_setup.heave_stiffness_front_N_per_m elif axle == "rear": k = self.sus_setup.heave_stiffness_rear_N_per_m else: raise ValueError(f"axle must be 'front' or 'rear', got '{axle}'") return k * np.asarray(heave_m, dtype=np.float64)
[docs] def pot_to_force( self, pot_mm: float | npt.NDArray[np.float64], axle: str ) -> float | npt.NDArray[np.float64]: """Converts shock pot displacement (mm) to vertical spring force (N). Parameters ---------- pot_mm : float | npt.NDArray[np.float64] Shock pot displacement in millimeters. Positive = compression. axle : str "front" or "rear", used to select the correct motion ratio and heave stiffness. Returns ------- float | npt.NDArray[np.float64] Vertical spring force in Newtons. Positive = compression. """ return self.heave_to_force(self.pot_to_heave(pot_mm, axle), axle)
[docs] def aero_force_from_pots( self, front_pot_delta_mm: float | npt.NDArray[np.float64], rear_pot_delta_mm: float | npt.NDArray[np.float64], ax_g: float | npt.NDArray[np.float64] = 0.0, ) -> tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]: """Estimate front/rear aero downforce from shock pot deltas. Parameters ---------- front_pot_delta_mm, rear_pot_delta_mm Change in average axle pot displacement from a zero-aero reference state (mm). Positive = compression. ax_g Longitudinal acceleration in g's. Used to subtract the spring-borne portion of longitudinal weight transfer (accounting for anti-dive/squat). Returns ------- (front_aero_N, rear_aero_N) Estimated aero downforce at each axle (N). """ ax_g = np.asarray(ax_g, dtype=np.float64) f_front_spring = self.pot_to_force(front_pot_delta_mm, "front") f_rear_spring = self.pot_to_force(rear_pot_delta_mm, "rear") wt_total = self.params.mass * ax_g * G * self.params.cg_h / self.params.wb wt_front_spring = -wt_total * (1.0 - self.sus_setup.anti_dive_pct) wt_rear_spring = wt_total * (1.0 - self.sus_setup.anti_squat_pct) front_aero = f_front_spring - wt_front_spring rear_aero = f_rear_spring - wt_rear_spring return np.asarray(front_aero, dtype=np.float64), np.asarray( rear_aero, dtype=np.float64 )