Skip to content

Commit 976808a

Browse files
authored
Merge pull request #5 from PhilippBalles/feat/linear-single-track
2 parents 71f4a6d + 523e449 commit 976808a

1 file changed

Lines changed: 250 additions & 0 deletions

File tree

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
#########################################################################################
2+
##
3+
## Linear single-track Block
4+
##
5+
#########################################################################################
6+
7+
8+
9+
# IMPORTS ===============================================================================
10+
11+
import numpy as np
12+
13+
from pathsim.blocks.ode import ODE
14+
15+
16+
# BLOCK Definitions ================================================================================
17+
18+
class LinearSingleTrack(ODE):
19+
"""Linearized single-track (bicycle) vehicle model with external
20+
longitudinal velocity input.
21+
22+
The model is valid for small slip angles and lateral accelerations
23+
within the linear tire range (roughly :math:`a_y < 4\\,\\mathrm{m/s^2}`
24+
on dry asphalt for typical passenger cars) at moderate, slowly varying
25+
forward speed. Near standstill the slip-angle kinematics are singular
26+
in :math:`v_x`; the implementation replaces it with the smooth norm
27+
:math:`\\sqrt{v_x^2 + v_{x,\\mathrm{eps}}^2}`, so the model is not
28+
physically meaningful below :math:`v_{x,\\mathrm{eps}}`.
29+
30+
The equations of the ``LinearSingleTrack`` block are derived from the
31+
nonlinear, force-driven single-track model with the equations of motion
32+
(body frame, ISO 8855)
33+
34+
.. math::
35+
36+
\\begin{aligned}
37+
m\\,\\dot v_x &= F_{x,f}^{b} + F_{x,r}^{b} + m\\,v_y\\,r, \\\\
38+
m\\,\\dot v_y &= F_{y,f}^{b} + F_{y,r}^{b} - m\\,v_x\\,r, \\\\
39+
I_z\\,\\dot r &= l_f\\,F_{y,f}^{b} - l_r\\,F_{y,r}^{b},
40+
\\end{aligned}
41+
42+
where the body-frame axle forces are obtained by rotating the
43+
tire-frame forces through the steer angles,
44+
45+
.. math::
46+
47+
\\begin{aligned}
48+
F_{x,f}^{b} &= F_{x,f}\\cos\\delta - F_{y,f}\\sin\\delta, \\\\
49+
F_{y,f}^{b} &= F_{x,f}\\sin\\delta + F_{y,f}\\cos\\delta, \\\\
50+
F_{x,r}^{b} &= F_{x,r}\\cos\\delta_r - F_{y,r}\\sin\\delta_r, \\\\
51+
F_{y,r}^{b} &= F_{x,r}\\sin\\delta_r + F_{y,r}\\cos\\delta_r,
52+
\\end{aligned}
53+
54+
and the tire slip angles are
55+
56+
.. math::
57+
58+
\\alpha_f = \\delta - \\arctan\\frac{v_y + l_f\\,r}{v_x},
59+
\\qquad
60+
\\alpha_r = \\delta_r - \\arctan\\frac{v_y - l_r\\,r}{v_x}.
61+
62+
The model is linearized about steady straight-line driving at speed
63+
:math:`v_x` using the following assumptions:
64+
65+
- **Quasi-steady longitudinal motion**: :math:`\\dot v_x \\approx 0`.
66+
Under this assumption the longitudinal equation of motion is dropped
67+
and :math:`v_x` becomes an external input.
68+
69+
- **Front-axle steering only**: :math:`\\delta_r = 0`.
70+
71+
- **Small angles**: steering angle, sideslip, and tire slip angles are
72+
small, :math:`\\delta, \\beta, \\alpha_f, \\alpha_r \\ll 1`. With
73+
:math:`\\cos\\delta \\approx 1` and :math:`\\sin\\delta \\approx \\delta`
74+
the force rotation reduces to
75+
76+
.. math::
77+
78+
F_{y,f}^{b} \\approx F_{y,f},
79+
\\qquad
80+
F_{y,r}^{b} \\approx F_{y,r},
81+
82+
and with :math:`\\arctan x \\approx x` the slip angles become
83+
84+
.. math::
85+
86+
\\alpha_f = \\delta - \\frac{v_y + l_f\\,r}{v_x},
87+
\\qquad
88+
\\alpha_r = -\\,\\frac{v_y - l_r\\,r}{v_x}.
89+
90+
- **Linear tire model**:
91+
92+
.. math::
93+
94+
F_{y,f} = C_{F\\alpha,f}\\,\\alpha_f,
95+
\\qquad
96+
F_{y,r} = C_{F\\alpha,r}\\,\\alpha_r.
97+
98+
Substituting these into the lateral and yaw equations of motion and
99+
writing :math:`C_f \\equiv C_{F\\alpha,f}` and
100+
:math:`C_r \\equiv C_{F\\alpha,r}` yields the linear state equations
101+
102+
.. math::
103+
104+
\\begin{aligned}
105+
\\dot v_y &= -\\frac{C_f + C_r}{m\\,v_x}\\,v_y
106+
+ \\left( \\frac{C_r l_r - C_f l_f}{m\\,v_x} - v_x \\right) r
107+
+ \\frac{C_f}{m}\\,\\delta, \\\\
108+
\\dot r &= \\frac{C_r l_r - C_f l_f}{I_z\\,v_x}\\,v_y
109+
- \\frac{C_f l_f^{2} + C_r l_r^{2}}{I_z\\,v_x}\\,r
110+
+ \\frac{C_f l_f}{I_z}\\,\\delta.
111+
\\end{aligned}
112+
113+
The pose kinematics are appended in their exact nonlinear form:
114+
115+
.. math::
116+
117+
\\dot\\psi = r,
118+
\\qquad
119+
\\dot X = v_x\\cos\\psi - v_y\\sin\\psi,
120+
\\qquad
121+
\\dot Y = v_x\\sin\\psi + v_y\\cos\\psi.
122+
123+
124+
Input Ports
125+
-----------
126+
delta : float
127+
front-axle steering angle [rad]
128+
v_x : float
129+
longitudinal velocity [m/s]
130+
131+
Output Ports
132+
------------
133+
v_y : float
134+
lateral velocity [m/s]
135+
r : float
136+
yaw rate [rad/s]
137+
psi : float
138+
yaw angle [rad]
139+
X : float
140+
vehicle position along the global X-axis [m]
141+
Y : float
142+
vehicle position along the global Y-axis [m]
143+
144+
145+
Parameters
146+
----------
147+
m : float
148+
Vehicle mass [kg].
149+
I_z : float
150+
Yaw moment of inertia [kg m^2].
151+
l_f : float
152+
Distance from CG to front axle [m].
153+
l_r : float
154+
Distance from CG to rear axle [m].
155+
C_Falpha_f : float
156+
Front-axle cornering stiffness [N/rad], > 0.
157+
C_Falpha_r : float
158+
Rear-axle cornering stiffness [N/rad], > 0.
159+
initial_value : array_like, optional
160+
Initial state vector ``[v_y, r, psi, X, Y]``
161+
162+
163+
"""
164+
165+
# port labels for semantic access
166+
input_port_labels = {"delta": 0, "v_x": 1}
167+
output_port_labels = {"v_y": 0, "r": 1, "psi": 2, "X": 3, "Y": 4}
168+
169+
def __init__(self, m=1500.0, I_z=3000.0, l_f=1.2, l_r=1.4,
170+
C_Falpha_f=80000.0, C_Falpha_r=80000.0, v_x_eps = 0.5, initial_value=None):
171+
172+
# vehicle parameters
173+
self.m = m
174+
self.I_z = I_z
175+
self.l_f = l_f
176+
self.l_r = l_r
177+
self.C_Falpha_f = C_Falpha_f
178+
self.C_Falpha_r = C_Falpha_r
179+
self.v_x_eps = v_x_eps
180+
181+
182+
if initial_value is None:
183+
initial_value = np.zeros(5)
184+
185+
super().__init__(
186+
func=self._func_dyn,
187+
initial_value=np.asarray(initial_value, dtype=float),
188+
jac=self._jac_dyn,
189+
)
190+
191+
192+
def _func_dyn(self, x, u, t):
193+
"""Right-hand side of the linear single-track ODEs.
194+
195+
Parameters
196+
----------
197+
x : array[float]
198+
State vector ``[v_y, r, psi, X, Y]``.
199+
u : array[float]
200+
Input vector ``[delta, v_x]``.
201+
t : float
202+
Time.
203+
204+
Returns
205+
-------
206+
dxdt : array[float]
207+
State derivative vector.
208+
"""
209+
v_y, r, psi, X, Y = x
210+
delta, v_x = u[0], u[1]
211+
212+
# sign-preserving singularity guard for the slip-angle denominator
213+
v_x_safe = np.sqrt(v_x**2 + self.v_x_eps**2)
214+
215+
# linearised slip angles (small-angle: tan(alpha) ~ alpha)
216+
alpha_f = delta - (v_y + self.l_f * r) / v_x_safe
217+
alpha_r = - (v_y - self.l_r * r) / v_x_safe
218+
219+
# linear tyre lateral forces
220+
F_y_f = self.C_Falpha_f * alpha_f
221+
F_y_r = self.C_Falpha_r * alpha_r
222+
223+
# equations of motion
224+
dv_y = (F_y_f + F_y_r) / self.m - v_x * r
225+
dr = (self.l_f * F_y_f - self.l_r * F_y_r) / self.I_z
226+
dpsi = r
227+
dX = v_x * np.cos(psi) - v_y * np.sin(psi)
228+
dY = v_x * np.sin(psi) + v_y * np.cos(psi)
229+
230+
return np.array([dv_y, dr, dpsi, dX, dY])
231+
232+
233+
def _jac_dyn(self, x, u, t):
234+
"""Analytic state Jacobian df/dx of the linear single-track equations.
235+
"""
236+
v_y, r, psi = x[0], x[1], x[2]
237+
v_x = u[1]
238+
v_x_safe = np.sqrt(v_x**2 + self.v_x_eps**2)
239+
240+
J = np.zeros((5, 5))
241+
J[0, 0] = (-self.C_Falpha_f - self.C_Falpha_r)/(self.m*v_x_safe)
242+
J[0, 1] = (-self.C_Falpha_f*self.l_f + self.C_Falpha_r*self.l_r - self.m*v_x*v_x_safe)/(self.m*v_x_safe)
243+
J[1, 0] = (-self.C_Falpha_f*self.l_f + self.C_Falpha_r*self.l_r)/(self.I_z*v_x_safe)
244+
J[1, 1] = (-self.C_Falpha_f*self.l_f**2 - self.C_Falpha_r*self.l_r**2)/(self.I_z*v_x_safe)
245+
J[2, 1] = 1
246+
J[3, 0] = -np.sin(psi)
247+
J[3, 2] = -v_x*np.sin(psi) - v_y*np.cos(psi)
248+
J[4, 0] = np.cos(psi)
249+
J[4, 2] = v_x*np.cos(psi) - v_y*np.sin(psi)
250+
return J

0 commit comments

Comments
 (0)