from abc import ABC, abstractmethod
from ..models import VehicleModel
[docs]
class Suspension(ABC):
def __init__(self, vehicle_model: VehicleModel):
self.vehicle_model = vehicle_model
[docs]
@staticmethod
@abstractmethod
def lateral_weight_transfer(self, *args, **kwargs):
pass
[docs]
@staticmethod
@abstractmethod
def longitudinal_weight_transfer(self, *args, **kwargs):
pass
[docs]
class SimpleSuspension(Suspension):
def __init__(self, vehicle_model: VehicleModel, **kwargs):
super().__init__(vehicle_model=vehicle_model)
self.params = self.vehicle_model.sus
[docs]
def lateral_weight_transfer(
self,
axle_track: float,
m_front: float,
m_rear: float,
cg_height: float,
lat_acc_x: float,
**kwargs,
) -> tuple[float, float]:
"""
Calculate lateral weight transfer using simple suspension model.
Parameters
----------
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
**kwargs : dict
Additional keyword arguments (unused)
Returns
-------
tuple[float, float]
Lateral weight transfer (N) across specific axle, (left, right)
Notes
-----
Formula used:
.. math::
|\\Delta F_{y,sus}| = \\frac{m \\cdot a_x \\cdot h_{CG}}{t}
where :math:`\\Delta F_{y,sus}` is change in normal force on the suspension (N)
"""
# TODO: Standardize naming to avoid x/y coordinates and use long/lat instead.
# Also, x in general should be long and y should be lat
# TODO: add plausibility checks to inputed values
mass = m_front + m_rear
delta_f_y_total = (mass * lat_acc_x * cg_height) / axle_track
left_delta = -delta_f_y_total
right_delta = delta_f_y_total
return (left_delta, right_delta)
[docs]
def longitudinal_weight_transfer(
self,
wheelbase: float,
total_mass: float,
cg_height: float,
long_acc_z: float,
**kwargs,
) -> 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
**kwargs : dict
Additional keyword arguments (unused)
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 inputed 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):
"""
https://kktse.github.io/jekyll/update/2021/05/12/simplied-lateral-load-transfer-analysis.html
Front & Rear Roll Stiffness includes:
- Corner Spring/Torsion Bar Stiffness (Spring Rate, Motion Ratio, etc.)
- Anti-roll bar stiffness (ARB Setting, Motion Ratio, etc.)
"""
def __init__(
self,
vehicle_model: VehicleModel,
):
super().__init__(vehicle_model=vehicle_model)
self.params = self.vehicle_model.sus
# Hypothetical parameters for complex longitudinal weight transfer, right now unimplemented
# self.k_theta_f = front_pitch_stiffness_k
# self.k_theta_r = rear_pitch_stiffness_k
# self.PC_h = pitch_center_height
[docs]
def lateral_weight_transfer(
self,
axle: str,
axle_track: float,
m_front: float,
m_rear: float,
cg_height: float,
lat_acc_x: float,
**kwargs,
) -> tuple[float, float]:
"""
Calculate lateral weight transfer using complex suspension model.
Parameters
----------
axle : str
The position of the suspension (either 'front' or 'rear')
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)
**kwargs : dict
Additional keyword arguments (unused)
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,
**kwargs,
) -> 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)
**kwargs : dict
Additional keyword arguments (unused)
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)
"""
# Right now, complex longitudinal weight transfer is lacking parameters
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)
# # Calculate geometric load transfer
# geometric_load_transfer = (total_mass * self.PC_h * long_acc_z) / wheelbase
# # Calculate elastic load transfer
# elastic_load_transfer = (
# total_mass * (cg_height - self.PC_h) * long_acc_z
# ) / wheelbase
# # Calculate total pitch stiffness
# total_pitch_stiffness = self.k_theta_f + self.k_theta_r
# # Calculate elastic load transfer distribution
# front_elastic_ratio = self.k_theta_f / total_pitch_stiffness
# rear_elastic_ratio = self.k_theta_r / total_pitch_stiffness
# # Calculate total load transfer for each axle
# front_delta = -geometric_load_transfer - (
# front_elastic_ratio * elastic_load_transfer
# )
# rear_delta = geometric_load_transfer + (
# rear_elastic_ratio * elastic_load_transfer
# )
# return (front_delta, rear_delta)
# class Suspension:
# def __init__(
# self,
# front_roll_k: float,
# rear_roll_k: float,
# front_z: float,
# rear_z: float,
# front_h: float,
# rear_h: float,
# torsion_k: float,
# front_d: float,
# rear_d: float,
# front_m_sprung: float,
# rear_m_sprung: float,
# front_m_unsprung: float,
# rear_m_unsprung: float,
# **kwargs,
# ):
# self.torsion_k = torsion_k # torsion stiffness (TODO: check units)
# # Front Suspension
# self.front_roll_k = front_roll_k # front roll stiffness (TODO: check units)
# self.front_z = front_z # front vertical stiffness (TODO: check units)
# self.front_h = front_h # front roll center height (m)
# self.front_d = front_d # front damping coefficient (TODO: check units)
# self.front_m_sprung = front_m_sprung # front sprung mass (kg)
# self.front_m_unsprung = front_m_unsprung # front unsprung mass (kg)
# # Rear Suspension
# self.rear_roll_k = rear_roll_k # rear roll stiffness (TODO: check units)
# self.rear_z = rear_z # rear vertical stiffness (TODO: check units)
# self.rear_h = rear_h # rear roll center height (m)
# self.rear_d = rear_d # rear damping coefficient (TODO: check units)
# self.rear_m_sprung = rear_m_sprung # rear sprung mass (kg)
# self.rear_m_unsprung = rear_m_unsprung # rear unsprung mass (kg)
# def lat_wt_sus(self, axle, lat_acc, axle_track):
# """
# Calculates the lateral weight transfer using a suspension model
# Args:
# axle (str): The position of the suspension (either 'front' or 'rear').
# lat_acc (float): The lateral acceleration of the vehicle.
# axle_track (float): The front or rear track width of the vehicle.
# Returns:
# float: Lateral weight transfer (N)
# """
# if axle == "front":
# roll_k = self.front_roll_k
# z = self.front_z
# h = self.front_h
# d = self.front_d
# m_sprung = self.front_m_sprung
# m_unsprung = self.front_m_unsprung
# opp_roll_k = self.rear_roll_k
# opp_d = self.rear_d
# opp_m_sprung = self.rear_m_sprung
# elif axle == "rear":
# roll_k = self.rear_roll_k
# z = self.rear_z
# h = self.rear_h
# d = self.rear_d
# m_sprung = self.rear_m_sprung
# m_unsprung = self.rear_m_unsprung
# opp_roll_k = self.front_roll_k
# opp_d = self.front_d
# opp_m_sprung = self.front_m_sprung
# else:
# raise ValueError("Position must be 'front' or 'rear'")
# lat_wt_sus = (
# lat_acc
# / axle_track
# * (
# (roll_k * d * m_sprung)
# / (
# roll_k
# + (opp_roll_k * self.torsion_k / (opp_roll_k + self.torsion_k))
# )
# + (
# (roll_k * self.torsion_k / (roll_k + self.torsion_k))
# * opp_d
# * opp_m_sprung
# )
# / ((roll_k * self.torsion_k / (roll_k + self.torsion_k)) + opp_roll_k)
# + z * m_sprung
# + h * m_unsprung
# )
# )
# return lat_wt_sus