Source code for suboptimumg.vehicle.suspension.suspension

from abc import ABC, abstractmethod
from typing import Literal

from ..subsystem_models import ComplexSuspensionModel
from ..vehicle_models import VehicleModel


[docs] class Suspension(ABC): def __init__(self, vehicle_model: VehicleModel) -> None: """ Initialize the suspension with the full vehicle model. Parameters ---------- vehicle_model : VehicleModel Vehicle model providing suspension and mass parameters. """ self.vehicle_model = vehicle_model
[docs] @abstractmethod def lateral_weight_transfer( self, axle: Literal["front", "rear"], axle_track: float, m_front: float, m_rear: float, cg_height: float, lat_acc_x: float, ) -> tuple[float, float]: """ Calculate lateral weight transfer at one axle. Parameters ---------- axle : {"front", "rear"} Which axle to compute the transfer for. axle_track : float Track width of the axle (m) m_front : float Mass on the front axle (kg) m_rear : float Mass on the rear axle (kg) cg_height : float Height of the center of gravity (m) lat_acc_x : float Lateral acceleration of the vehicle (m/s^2); positive means left turn Returns ------- tuple[float, float] Lateral weight transfer (N) at the specified axle, (left, right) """
[docs] @abstractmethod def longitudinal_weight_transfer( self, wheelbase: float, total_mass: float, cg_height: float, long_acc_z: float, ) -> tuple[float, float]: """ Calculate longitudinal weight transfer between the front and rear axles. Parameters ---------- wheelbase : float Wheelbase of the vehicle (m) total_mass : float Total mass of the vehicle (kg) cg_height : float Height of the center of gravity (m) long_acc_z : float Longitudinal acceleration of the vehicle (m/s^2); positive means go faster Returns ------- tuple[float, float] Longitudinal weight transfer (N) (front, rear) """
[docs] class SimpleSuspension(Suspension): def __init__(self, vehicle_model: VehicleModel) -> None: """ Initialize the simple suspension model. Parameters ---------- vehicle_model : VehicleModel Vehicle model providing suspension and mass parameters. """ super().__init__(vehicle_model=vehicle_model) self.params = self.vehicle_model.sus
[docs] def lateral_weight_transfer( self, axle: Literal["front", "rear"], axle_track: float, m_front: float, m_rear: float, cg_height: float, lat_acc_x: float, ) -> tuple[float, float]: """ Calculate lateral weight transfer using simple suspension model. Parameters ---------- axle : {"front", "rear"} Which axle to compute the transfer for. axle_track : float Track width of the axle (m) m_front : float Mass on the front axle (kg) m_rear : float Mass on the rear axle (kg) cg_height : float Height of the center of gravity (m) lat_acc_x : float Lateral acceleration of the vehicle (m/s^2); positive means left turn Returns ------- tuple[float, float] Lateral weight transfer (N) at the specified axle, (left, right) Notes ----- Per-axle formula: .. math:: |\\Delta F_{y,axle}| = \\frac{m_{axle} \\cdot a_y \\cdot h_{CG}}{t_{axle}} Each axle reacts its share of the chassis roll moment via a side-to-side couple at its track width. Summing the two axle results recovers the total chassis transfer m_total * a * h_cg / track (when t_front == t_rear). The earlier version used m_total here for each axle, which double-counted the transfer. TODO: Standardize naming to avoid x/y coordinates and use long/lat instead; x in general should be long and y should be lat. Also add plausibility checks to input values. """ if axle == "front": m_axle = m_front elif axle == "rear": m_axle = m_rear else: raise ValueError("axle must be 'front' or 'rear'") delta_f_y_axle = (m_axle * lat_acc_x * cg_height) / axle_track left_delta = -delta_f_y_axle right_delta = delta_f_y_axle return (left_delta, right_delta)
[docs] def longitudinal_weight_transfer( self, wheelbase: float, total_mass: float, cg_height: float, long_acc_z: float, ) -> tuple[float, float]: """ Calculate longitudinal weight transfer using simple suspension model. Parameters ---------- wheelbase : float Wheelbase of the vehicle (m) total_mass : float Total mass of the vehicle (kg) cg_height : float Height of the center of gravity (m) long_acc_z : float Longitudinal acceleration of the vehicle (m/s^2); positive means go faster Returns ------- tuple[float, float] Longitudinal weight transfer (N) (front, rear) Notes ----- Formula used: .. math:: |\\Delta F_{y,sus}| = \\frac{m \\cdot a_z \\cdot h_{CG}}{L} where :math:`\\Delta F_{y,sus}` is change in normal force on the suspension (N) TODO: add plausibility checks to input values. """ delta_f_y_total = (total_mass * long_acc_z * cg_height) / wheelbase front_delta = -delta_f_y_total rear_delta = delta_f_y_total return (front_delta, rear_delta)
[docs] class ComplexSuspension(Suspension): def __init__( self, vehicle_model: VehicleModel, ) -> None: """ Initialize the complex suspension model. Parameters ---------- vehicle_model : VehicleModel Vehicle model whose suspension parameters must be a ``ComplexSuspensionModel``. Notes ----- Model is based on https://kktse.github.io/jekyll/update/2021/05/12/simplied-lateral-load-transfer-analysis.html Front and rear roll stiffness include: - Corner spring/torsion bar stiffness (spring rate, motion ratio, etc.) - Anti-roll bar stiffness (ARB setting, motion ratio, etc.) Complex longitudinal weight transfer (front/rear pitch stiffness and pitch center height) is not yet implemented; see ``longitudinal_weight_transfer`` below. """ super().__init__(vehicle_model=vehicle_model) assert isinstance( vehicle_model.sus, ComplexSuspensionModel ), "ComplexSuspension requires a ComplexSuspensionModel" self.params = vehicle_model.sus
[docs] def lateral_weight_transfer( self, axle: Literal["front", "rear"], axle_track: float, m_front: float, m_rear: float, cg_height: float, lat_acc_x: float, ) -> tuple[float, float]: """ Calculate lateral weight transfer using complex suspension model. Parameters ---------- axle : {"front", "rear"} The position of the suspension. axle_track : float The track width of the axle (m) m_front : float The mass on the front axle (kg) m_rear : float The mass on the rear axle (kg) cg_height : float The height of the center of gravity of the vehicle (m) lat_acc_x : float The lateral acceleration of the vehicle (m/s^2) Returns ------- tuple[float, float] Force deltas due to lateral weight transfer (N) (left, right) Notes ----- This model is based on the paper referenced in the class docstring. Formula: .. math:: |\\Delta F_{y,axle}| = \\frac{m_{axle} \\cdot h_{RC,axle}}{t_{axle}} \\cdot a_x + \\frac{k_{\\phi,axle}}{k_{\\phi,axle} + k_{\\phi,opp}} \\cdot \\frac{m_{axle} \\cdot (h_{CG} - h_{RC,axle}) + m_{opp} \\cdot (h_{CG} - h_{RC,opp})}{t_{axle}} \\cdot a_x Where: - :math:`\\Delta F_{y,axle}` : change in normal force on the axle (N) - :math:`m_{axle}` : mass on the axle (kg) - :math:`h_{RC,axle}` : roll center height of the axle (m) - :math:`t_{axle}` : track width of the axle (m) - :math:`a_x` : lateral acceleration of the vehicle (m/s²) - :math:`k_{\\phi,axle}` : roll stiffness of the axle (Nm/rad) - :math:`h_{CG}` : height of the center of gravity (m) """ # Determine axle-specific parameters if axle == "front": h_RC_axle, h_RC_opp = ( self.params.front_roll_center_height, self.params.rear_roll_center_height, ) k_phi, k_phi_opp = ( self.params.front_roll_stiffness_k, self.params.rear_roll_stiffness_k, ) m_axle, m_axle_opp = m_front, m_rear elif axle == "rear": h_RC_axle, h_RC_opp = ( self.params.rear_roll_center_height, self.params.front_roll_center_height, ) k_phi, k_phi_opp = ( self.params.rear_roll_stiffness_k, self.params.front_roll_stiffness_k, ) m_axle, m_axle_opp = m_rear, m_front else: raise ValueError("Axle must be 'front' or 'rear'") # Calculate geometric load transfer geometric_load_transfer = (m_axle * h_RC_axle * lat_acc_x) / axle_track # Calculate roll stiffness ratio roll_stiffness_ratio = k_phi / (k_phi + k_phi_opp) # Calculate elastic load transfer elastic_load_transfer = roll_stiffness_ratio * ( (m_axle * (cg_height - h_RC_axle) + m_axle_opp * (cg_height - h_RC_opp)) * lat_acc_x / axle_track ) # Calculate total lateral weight transfer total_lateral_weight_transfer = geometric_load_transfer + elastic_load_transfer left_delta = -total_lateral_weight_transfer right_delta = total_lateral_weight_transfer return (left_delta, right_delta)
[docs] def longitudinal_weight_transfer( self, wheelbase: float, total_mass: float, cg_height: float, long_acc_z: float, ) -> tuple[float, float]: """ Calculate longitudinal weight transfer using complex suspension model. Parameters ---------- wheelbase : float Wheelbase of the vehicle (m) total_mass : float Total mass of the car (kg) cg_height : float Height of the center of gravity (m) long_acc_z : float Longitudinal acceleration of the vehicle (m/s^2) Returns ------- tuple[float, float] Force deltas due to longitudinal weight transfer (N) (front, rear) Notes ----- This model was derived by Vedansh Goenka based on the paper referenced in the class docstring. Currently uses simplified model pending full implementation with pitch stiffness parameters. Formula: .. math:: \\Delta F_{y,axle} = \\frac{m \\cdot h_{PC}}{L} \\cdot a_z + \\frac{k_{\\theta,axle}}{k_{\\theta,axle} + k_{\\theta,opp}} \\cdot \\frac{m \\cdot (h_{CG} - h_{PC})}{L} \\cdot a_z Where: - :math:`\\Delta F_{y,axle}` : change in normal force on the axle (N) - :math:`m` : mass on the axle (kg) - :math:`h_{PC}` : pitch center height (m) - :math:`L` : wheelbase of the vehicle (m) - :math:`a_z` : longitudinal acceleration of the vehicle (m/s²) - :math:`k_{\\theta,axle}` : pitch stiffness of the axle (Nm/rad) - :math:`h_{CG}` : height of the center of gravity (m) """ delta_f_y_total = (total_mass * long_acc_z * cg_height) / wheelbase front_delta = -delta_f_y_total rear_delta = delta_f_y_total return (front_delta, rear_delta)