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)