Move vehicle_model.py to opendbc (#34681)
* move * fix * move test too * bump * better * bump to masterpull/34719/head
parent
bd879be27a
commit
6723106bf5
9 changed files with 7 additions and 304 deletions
@ -1 +1 @@ |
|||||||
Subproject commit 27a50f817a3030aa6ee182110be284d4d136836a |
Subproject commit 706567945a80e0baff18592585d4c1a29e1feea1 |
@ -1,67 +0,0 @@ |
|||||||
import pytest |
|
||||||
import math |
|
||||||
|
|
||||||
import numpy as np |
|
||||||
|
|
||||||
from opendbc.car.honda.interface import CarInterface |
|
||||||
from opendbc.car.honda.values import CAR |
|
||||||
from openpilot.selfdrive.controls.lib.vehicle_model import VehicleModel, dyn_ss_sol, create_dyn_state_matrices |
|
||||||
|
|
||||||
|
|
||||||
class TestVehicleModel: |
|
||||||
def setup_method(self): |
|
||||||
CP = CarInterface.get_non_essential_params(CAR.HONDA_CIVIC) |
|
||||||
self.VM = VehicleModel(CP) |
|
||||||
|
|
||||||
def test_round_trip_yaw_rate(self): |
|
||||||
# TODO: fix VM to work at zero speed |
|
||||||
for u in np.linspace(1, 30, num=10): |
|
||||||
for roll in np.linspace(math.radians(-20), math.radians(20), num=11): |
|
||||||
for sa in np.linspace(math.radians(-20), math.radians(20), num=11): |
|
||||||
yr = self.VM.yaw_rate(sa, u, roll) |
|
||||||
new_sa = self.VM.get_steer_from_yaw_rate(yr, u, roll) |
|
||||||
|
|
||||||
assert sa == pytest.approx(new_sa) |
|
||||||
|
|
||||||
def test_dyn_ss_sol_against_yaw_rate(self): |
|
||||||
"""Verify that the yaw_rate helper function matches the results |
|
||||||
from the state space model.""" |
|
||||||
|
|
||||||
for roll in np.linspace(math.radians(-20), math.radians(20), num=11): |
|
||||||
for u in np.linspace(1, 30, num=10): |
|
||||||
for sa in np.linspace(math.radians(-20), math.radians(20), num=11): |
|
||||||
|
|
||||||
# Compute yaw rate based on state space model |
|
||||||
_, yr1 = dyn_ss_sol(sa, u, roll, self.VM) |
|
||||||
|
|
||||||
# Compute yaw rate using direct computations |
|
||||||
yr2 = self.VM.yaw_rate(sa, u, roll) |
|
||||||
assert float(yr1[0]) == pytest.approx(yr2) |
|
||||||
|
|
||||||
def test_syn_ss_sol_simulate(self): |
|
||||||
"""Verifies that dyn_ss_sol matches a simulation""" |
|
||||||
|
|
||||||
for roll in np.linspace(math.radians(-20), math.radians(20), num=11): |
|
||||||
for u in np.linspace(1, 30, num=10): |
|
||||||
A, B = create_dyn_state_matrices(u, self.VM) |
|
||||||
|
|
||||||
# Convert to discrete time system |
|
||||||
dt = 0.01 |
|
||||||
top = np.hstack((A, B)) |
|
||||||
full = np.vstack((top, np.zeros_like(top))) * dt |
|
||||||
Md = sum([np.linalg.matrix_power(full, k) / math.factorial(k) for k in range(25)]) |
|
||||||
Ad = Md[:A.shape[0], :A.shape[1]] |
|
||||||
Bd = Md[:A.shape[0], A.shape[1]:] |
|
||||||
|
|
||||||
for sa in np.linspace(math.radians(-20), math.radians(20), num=11): |
|
||||||
inp = np.array([[sa], [roll]]) |
|
||||||
|
|
||||||
# Simulate for 1 second |
|
||||||
x1 = np.zeros((2, 1)) |
|
||||||
for _ in range(100): |
|
||||||
x1 = Ad @ x1 + Bd @ inp |
|
||||||
|
|
||||||
# Compute steady state solution directly |
|
||||||
x2 = dyn_ss_sol(sa, u, roll, self.VM) |
|
||||||
|
|
||||||
np.testing.assert_almost_equal(x1, x2, decimal=3) |
|
@ -1,230 +0,0 @@ |
|||||||
#!/usr/bin/env python3 |
|
||||||
""" |
|
||||||
Dynamic bicycle model from "The Science of Vehicle Dynamics (2014), M. Guiggiani" |
|
||||||
|
|
||||||
The state is x = [v, r]^T |
|
||||||
with v lateral speed [m/s], and r rotational speed [rad/s] |
|
||||||
|
|
||||||
The input u is the steering angle [rad], and roll [rad] |
|
||||||
|
|
||||||
The system is defined by |
|
||||||
x_dot = A*x + B*u |
|
||||||
|
|
||||||
A depends on longitudinal speed, u [m/s], and vehicle parameters CP |
|
||||||
""" |
|
||||||
|
|
||||||
import numpy as np |
|
||||||
from numpy.linalg import solve |
|
||||||
|
|
||||||
from cereal import car |
|
||||||
|
|
||||||
ACCELERATION_DUE_TO_GRAVITY = 9.8 |
|
||||||
|
|
||||||
|
|
||||||
class VehicleModel: |
|
||||||
def __init__(self, CP: car.CarParams): |
|
||||||
""" |
|
||||||
Args: |
|
||||||
CP: Car Parameters |
|
||||||
""" |
|
||||||
# for math readability, convert long names car params into short names |
|
||||||
self.m: float = CP.mass |
|
||||||
self.j: float = CP.rotationalInertia |
|
||||||
self.l: float = CP.wheelbase |
|
||||||
self.aF: float = CP.centerToFront |
|
||||||
self.aR: float = CP.wheelbase - CP.centerToFront |
|
||||||
self.chi: float = CP.steerRatioRear |
|
||||||
|
|
||||||
self.cF_orig: float = CP.tireStiffnessFront |
|
||||||
self.cR_orig: float = CP.tireStiffnessRear |
|
||||||
self.update_params(1.0, CP.steerRatio) |
|
||||||
|
|
||||||
def update_params(self, stiffness_factor: float, steer_ratio: float) -> None: |
|
||||||
"""Update the vehicle model with a new stiffness factor and steer ratio""" |
|
||||||
self.cF: float = stiffness_factor * self.cF_orig |
|
||||||
self.cR: float = stiffness_factor * self.cR_orig |
|
||||||
self.sR: float = steer_ratio |
|
||||||
|
|
||||||
def steady_state_sol(self, sa: float, u: float, roll: float) -> np.ndarray: |
|
||||||
"""Returns the steady state solution. |
|
||||||
|
|
||||||
If the speed is too low we can't use the dynamic model (tire slip is undefined), |
|
||||||
we then have to use the kinematic model |
|
||||||
|
|
||||||
Args: |
|
||||||
sa: Steering wheel angle [rad] |
|
||||||
u: Speed [m/s] |
|
||||||
roll: Road Roll [rad] |
|
||||||
|
|
||||||
Returns: |
|
||||||
2x1 matrix with steady state solution (lateral speed, rotational speed) |
|
||||||
""" |
|
||||||
if u > 0.1: |
|
||||||
return dyn_ss_sol(sa, u, roll, self) |
|
||||||
else: |
|
||||||
return kin_ss_sol(sa, u, self) |
|
||||||
|
|
||||||
def calc_curvature(self, sa: float, u: float, roll: float) -> float: |
|
||||||
"""Returns the curvature. Multiplied by the speed this will give the yaw rate. |
|
||||||
|
|
||||||
Args: |
|
||||||
sa: Steering wheel angle [rad] |
|
||||||
u: Speed [m/s] |
|
||||||
roll: Road Roll [rad] |
|
||||||
|
|
||||||
Returns: |
|
||||||
Curvature factor [1/m] |
|
||||||
""" |
|
||||||
return (self.curvature_factor(u) * sa / self.sR) + self.roll_compensation(roll, u) |
|
||||||
|
|
||||||
def curvature_factor(self, u: float) -> float: |
|
||||||
"""Returns the curvature factor. |
|
||||||
Multiplied by wheel angle (not steering wheel angle) this will give the curvature. |
|
||||||
|
|
||||||
Args: |
|
||||||
u: Speed [m/s] |
|
||||||
|
|
||||||
Returns: |
|
||||||
Curvature factor [1/m] |
|
||||||
""" |
|
||||||
sf = calc_slip_factor(self) |
|
||||||
return (1. - self.chi) / (1. - sf * u**2) / self.l |
|
||||||
|
|
||||||
def get_steer_from_curvature(self, curv: float, u: float, roll: float) -> float: |
|
||||||
"""Calculates the required steering wheel angle for a given curvature |
|
||||||
|
|
||||||
Args: |
|
||||||
curv: Desired curvature [1/m] |
|
||||||
u: Speed [m/s] |
|
||||||
roll: Road Roll [rad] |
|
||||||
|
|
||||||
Returns: |
|
||||||
Steering wheel angle [rad] |
|
||||||
""" |
|
||||||
|
|
||||||
return (curv - self.roll_compensation(roll, u)) * self.sR * 1.0 / self.curvature_factor(u) |
|
||||||
|
|
||||||
def roll_compensation(self, roll: float, u: float) -> float: |
|
||||||
"""Calculates the roll-compensation to curvature |
|
||||||
|
|
||||||
Args: |
|
||||||
roll: Road Roll [rad] |
|
||||||
u: Speed [m/s] |
|
||||||
|
|
||||||
Returns: |
|
||||||
Roll compensation curvature [rad] |
|
||||||
""" |
|
||||||
sf = calc_slip_factor(self) |
|
||||||
|
|
||||||
if abs(sf) < 1e-6: |
|
||||||
return 0 |
|
||||||
else: |
|
||||||
return (ACCELERATION_DUE_TO_GRAVITY * roll) / ((1 / sf) - u**2) |
|
||||||
|
|
||||||
def get_steer_from_yaw_rate(self, yaw_rate: float, u: float, roll: float) -> float: |
|
||||||
"""Calculates the required steering wheel angle for a given yaw_rate |
|
||||||
|
|
||||||
Args: |
|
||||||
yaw_rate: Desired yaw rate [rad/s] |
|
||||||
u: Speed [m/s] |
|
||||||
roll: Road Roll [rad] |
|
||||||
|
|
||||||
Returns: |
|
||||||
Steering wheel angle [rad] |
|
||||||
""" |
|
||||||
curv = yaw_rate / u |
|
||||||
return self.get_steer_from_curvature(curv, u, roll) |
|
||||||
|
|
||||||
def yaw_rate(self, sa: float, u: float, roll: float) -> float: |
|
||||||
"""Calculate yaw rate |
|
||||||
|
|
||||||
Args: |
|
||||||
sa: Steering wheel angle [rad] |
|
||||||
u: Speed [m/s] |
|
||||||
roll: Road Roll [rad] |
|
||||||
|
|
||||||
Returns: |
|
||||||
Yaw rate [rad/s] |
|
||||||
""" |
|
||||||
return self.calc_curvature(sa, u, roll) * u |
|
||||||
|
|
||||||
|
|
||||||
def kin_ss_sol(sa: float, u: float, VM: VehicleModel) -> np.ndarray: |
|
||||||
"""Calculate the steady state solution at low speeds |
|
||||||
At low speeds the tire slip is undefined, so a kinematic |
|
||||||
model is used. |
|
||||||
|
|
||||||
Args: |
|
||||||
sa: Steering angle [rad] |
|
||||||
u: Speed [m/s] |
|
||||||
VM: Vehicle model |
|
||||||
|
|
||||||
Returns: |
|
||||||
2x1 matrix with steady state solution |
|
||||||
""" |
|
||||||
K = np.zeros((2, 1)) |
|
||||||
K[0, 0] = VM.aR / VM.sR / VM.l * u |
|
||||||
K[1, 0] = 1. / VM.sR / VM.l * u |
|
||||||
return K * sa |
|
||||||
|
|
||||||
|
|
||||||
def create_dyn_state_matrices(u: float, VM: VehicleModel) -> tuple[np.ndarray, np.ndarray]: |
|
||||||
"""Returns the A and B matrix for the dynamics system |
|
||||||
|
|
||||||
Args: |
|
||||||
u: Vehicle speed [m/s] |
|
||||||
VM: Vehicle model |
|
||||||
|
|
||||||
Returns: |
|
||||||
A tuple with the 2x2 A matrix, and 2x2 B matrix |
|
||||||
|
|
||||||
Parameters in the vehicle model: |
|
||||||
cF: Tire stiffness Front [N/rad] |
|
||||||
cR: Tire stiffness Front [N/rad] |
|
||||||
aF: Distance from CG to front wheels [m] |
|
||||||
aR: Distance from CG to rear wheels [m] |
|
||||||
m: Mass [kg] |
|
||||||
j: Rotational inertia [kg m^2] |
|
||||||
sR: Steering ratio [-] |
|
||||||
chi: Steer ratio rear [-] |
|
||||||
""" |
|
||||||
A = np.zeros((2, 2)) |
|
||||||
B = np.zeros((2, 2)) |
|
||||||
A[0, 0] = - (VM.cF + VM.cR) / (VM.m * u) |
|
||||||
A[0, 1] = - (VM.cF * VM.aF - VM.cR * VM.aR) / (VM.m * u) - u |
|
||||||
A[1, 0] = - (VM.cF * VM.aF - VM.cR * VM.aR) / (VM.j * u) |
|
||||||
A[1, 1] = - (VM.cF * VM.aF**2 + VM.cR * VM.aR**2) / (VM.j * u) |
|
||||||
|
|
||||||
# Steering input |
|
||||||
B[0, 0] = (VM.cF + VM.chi * VM.cR) / VM.m / VM.sR |
|
||||||
B[1, 0] = (VM.cF * VM.aF - VM.chi * VM.cR * VM.aR) / VM.j / VM.sR |
|
||||||
|
|
||||||
# Roll input |
|
||||||
B[0, 1] = -ACCELERATION_DUE_TO_GRAVITY |
|
||||||
|
|
||||||
return A, B |
|
||||||
|
|
||||||
|
|
||||||
def dyn_ss_sol(sa: float, u: float, roll: float, VM: VehicleModel) -> np.ndarray: |
|
||||||
"""Calculate the steady state solution when x_dot = 0, |
|
||||||
Ax + Bu = 0 => x = -A^{-1} B u |
|
||||||
|
|
||||||
Args: |
|
||||||
sa: Steering angle [rad] |
|
||||||
u: Speed [m/s] |
|
||||||
roll: Road Roll [rad] |
|
||||||
VM: Vehicle model |
|
||||||
|
|
||||||
Returns: |
|
||||||
2x1 matrix with steady state solution |
|
||||||
""" |
|
||||||
A, B = create_dyn_state_matrices(u, VM) |
|
||||||
inp = np.array([[sa], [roll]]) |
|
||||||
return -solve(A, B) @ inp # type: ignore |
|
||||||
|
|
||||||
|
|
||||||
def calc_slip_factor(VM: VehicleModel) -> float: |
|
||||||
"""The slip factor is a measure of how the curvature changes with speed |
|
||||||
it's positive for Oversteering vehicle, negative (usual case) otherwise. |
|
||||||
""" |
|
||||||
return VM.m * (VM.cF * VM.aF - VM.cR * VM.aR) / (VM.l**2 * VM.cF * VM.cR) |
|
Loading…
Reference in new issue