OptiLine-Py 0.1.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- OptiLine/KinematicProfs.py +970 -0
- OptiLine/__init__.py +1 -0
- OptiLine/map_builder.py +790 -0
- OptiLine/opt_mintime.py +769 -0
- OptiLine/solvers.py +2342 -0
- OptiLine/utils.py +1656 -0
- optiline_py-0.1.7.dist-info/METADATA +35 -0
- optiline_py-0.1.7.dist-info/RECORD +9 -0
- optiline_py-0.1.7.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,970 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import math
|
|
3
|
+
from OptiLine.utils import calc_splines, conv_filt
|
|
4
|
+
from scipy.interpolate import splprep, splev
|
|
5
|
+
|
|
6
|
+
try:
|
|
7
|
+
from numba import njit as _njit
|
|
8
|
+
except ImportError:
|
|
9
|
+
def _njit(fn=None, **_): # transparent no-op fallback
|
|
10
|
+
return fn if fn is not None else lambda f: f
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# mode_int encoding: 0 = accel_forw, 1 = decel_forw, 2 = decel_backw
|
|
14
|
+
@_njit(cache=True)
|
|
15
|
+
def _calc_ax_poss_nb(vx_start, radius,
|
|
16
|
+
ggv_vx, ggv_ax, ggv_ay,
|
|
17
|
+
ax_mach_vx, ax_mach_ax,
|
|
18
|
+
mu, dyn_model_exp, drag_coeff, m_veh,
|
|
19
|
+
mode_int):
|
|
20
|
+
ax_max_tires = mu * np.interp(vx_start, ggv_vx, ggv_ax)
|
|
21
|
+
ay_max_tires = mu * np.interp(vx_start, ggv_vx, ggv_ay)
|
|
22
|
+
ay_used = vx_start * vx_start / radius
|
|
23
|
+
|
|
24
|
+
if mode_int != 1 and ax_max_tires < 0.0: # accel_forw or decel_backw
|
|
25
|
+
ax_max_tires = -ax_max_tires
|
|
26
|
+
elif mode_int == 1 and ax_max_tires > 0.0: # decel_forw
|
|
27
|
+
ax_max_tires = -ax_max_tires
|
|
28
|
+
|
|
29
|
+
radicand = 1.0 - (ay_used / ay_max_tires) ** dyn_model_exp
|
|
30
|
+
ax_avail_tires = ax_max_tires * radicand ** (1.0 / dyn_model_exp) if radicand > 0.0 else 0.0
|
|
31
|
+
|
|
32
|
+
if mode_int == 0: # accel_forw — apply motor limits
|
|
33
|
+
ax_mach_tmp = np.interp(vx_start, ax_mach_vx, ax_mach_ax)
|
|
34
|
+
ax_avail = ax_avail_tires if ax_avail_tires < ax_mach_tmp else ax_mach_tmp
|
|
35
|
+
else:
|
|
36
|
+
ax_avail = ax_avail_tires
|
|
37
|
+
|
|
38
|
+
ax_drag = -(vx_start * vx_start) * drag_coeff / m_veh
|
|
39
|
+
|
|
40
|
+
if mode_int != 2: # accel_forw or decel_forw
|
|
41
|
+
return ax_avail + ax_drag
|
|
42
|
+
else: # decel_backw
|
|
43
|
+
return ax_avail - ax_drag
|
|
44
|
+
|
|
45
|
+
def calc_vel_profile(ax_max_machines: np.ndarray,
|
|
46
|
+
kappa: np.ndarray,
|
|
47
|
+
el_lengths: np.ndarray,
|
|
48
|
+
closed: bool,
|
|
49
|
+
drag_coeff: float,
|
|
50
|
+
m_veh: float,
|
|
51
|
+
ggv: np.ndarray = None,
|
|
52
|
+
loc_gg: np.ndarray = None,
|
|
53
|
+
v_max: float = None,
|
|
54
|
+
dyn_model_exp: float = 1.0,
|
|
55
|
+
mu: np.ndarray = None,
|
|
56
|
+
v_start: float = None,
|
|
57
|
+
v_end: float = None,
|
|
58
|
+
filt_window: int = None,
|
|
59
|
+
p_ggv: np.ndarray = None) -> np.ndarray:
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
.. description::
|
|
63
|
+
Calculates a velocity profile using the tire and motor limits as good as possible.
|
|
64
|
+
|
|
65
|
+
.. inputs::
|
|
66
|
+
:param ax_max_machines: longitudinal acceleration limits by the electrical motors: [vx, ax_max_machines]. Velocity
|
|
67
|
+
in m/s, accelerations in m/s2. They should be handed in without considering drag resistance,
|
|
68
|
+
i.e. simply by calculating F_x_drivetrain / m_veh
|
|
69
|
+
:type ax_max_machines: np.ndarray
|
|
70
|
+
:param kappa: curvature profile of given trajectory in rad/m (always unclosed).
|
|
71
|
+
:type kappa: np.ndarray
|
|
72
|
+
:param el_lengths: element lengths (distances between coordinates) of given trajectory.
|
|
73
|
+
:type el_lengths: np.ndarray
|
|
74
|
+
:param closed: flag to set if the velocity profile must be calculated for a closed or unclosed trajectory.
|
|
75
|
+
:type closed: bool
|
|
76
|
+
:param drag_coeff: drag coefficient including all constants: drag_coeff = 0.5 * c_w * A_front * rho_air
|
|
77
|
+
:type drag_coeff: float
|
|
78
|
+
:param m_veh: vehicle mass in kg.
|
|
79
|
+
:type m_veh: float
|
|
80
|
+
:param ggv: ggv-diagram to be applied: [vx, ax_max, ay_max]. Velocity in m/s, accelerations in m/s2.
|
|
81
|
+
ATTENTION: Insert either ggv + mu (optional) or loc_gg!
|
|
82
|
+
:type ggv: np.ndarray
|
|
83
|
+
:param loc_gg: local gg diagrams along the path points: [[ax_max_0, ay_max_0], [ax_max_1, ay_max_1], ...],
|
|
84
|
+
accelerations in m/s2. ATTENTION: Insert either ggv + mu (optional) or loc_gg!
|
|
85
|
+
:type loc_gg: np.ndarray
|
|
86
|
+
:param v_max: Maximum longitudinal speed in m/s (optional if ggv is supplied, taking the minimum of the
|
|
87
|
+
fastest velocities covered by the ggv and ax_max_machines arrays then).
|
|
88
|
+
:type v_max: float
|
|
89
|
+
:param dyn_model_exp: exponent used in the vehicle dynamics model (usual range [1.0,2.0]).
|
|
90
|
+
:type dyn_model_exp: float
|
|
91
|
+
:param mu: friction coefficients (always unclosed).
|
|
92
|
+
:type mu: np.ndarray
|
|
93
|
+
:param v_start: start velocity in m/s (used in unclosed case only).
|
|
94
|
+
:type v_start: float
|
|
95
|
+
:param v_end: end velocity in m/s (used in unclosed case only).
|
|
96
|
+
:type v_end: float
|
|
97
|
+
:param filt_window: filter window size for moving average filter (must be odd).
|
|
98
|
+
:type filt_window: int
|
|
99
|
+
|
|
100
|
+
.. outputs::
|
|
101
|
+
:return vx_profile: calculated velocity profile (always unclosed).
|
|
102
|
+
:rtype vx_profile: np.ndarray
|
|
103
|
+
|
|
104
|
+
.. notes::
|
|
105
|
+
All inputs must be inserted unclosed, i.e. kappa[-1] != kappa[0], even if closed is set True! (el_lengths is kind of
|
|
106
|
+
closed if closed is True of course!)
|
|
107
|
+
|
|
108
|
+
case closed is True:
|
|
109
|
+
len(kappa) = len(el_lengths) = len(mu) = len(vx_profile)
|
|
110
|
+
|
|
111
|
+
case closed is False:
|
|
112
|
+
len(kappa) = len(el_lengths) + 1 = len(mu) = len(vx_profile)
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
# ------------------------------------------------------------------------------------------------------------------
|
|
116
|
+
# INPUT CHECKS -----------------------------------------------------------------------------------------------------
|
|
117
|
+
# ------------------------------------------------------------------------------------------------------------------
|
|
118
|
+
|
|
119
|
+
# check if either ggv (and optionally mu) or loc_gg are handed in
|
|
120
|
+
if (ggv is not None or mu is not None) and loc_gg is not None:
|
|
121
|
+
raise RuntimeError("Either ggv and optionally mu OR loc_gg must be supplied, not both (or all) of them!")
|
|
122
|
+
|
|
123
|
+
if ggv is None and loc_gg is None:
|
|
124
|
+
raise RuntimeError("Either ggv or loc_gg must be supplied!")
|
|
125
|
+
|
|
126
|
+
# check shape of loc_gg
|
|
127
|
+
if loc_gg is not None:
|
|
128
|
+
if loc_gg.ndim != 2:
|
|
129
|
+
raise RuntimeError("loc_gg must have two dimensions!")
|
|
130
|
+
|
|
131
|
+
if loc_gg.shape[0] != kappa.size:
|
|
132
|
+
raise RuntimeError("Length of loc_gg and kappa must be equal!")
|
|
133
|
+
|
|
134
|
+
if loc_gg.shape[1] != 2:
|
|
135
|
+
raise RuntimeError("loc_gg must consist of two columns: [ax_max, ay_max]!")
|
|
136
|
+
|
|
137
|
+
# check shape of ggv
|
|
138
|
+
if ggv is not None and ggv.shape[1] != 3:
|
|
139
|
+
raise RuntimeError("ggv diagram must consist of the three columns [vx, ax_max, ay_max]!")
|
|
140
|
+
|
|
141
|
+
# check size of mu
|
|
142
|
+
if mu is not None and kappa.size != mu.size:
|
|
143
|
+
raise RuntimeError("kappa and mu must have the same length!")
|
|
144
|
+
|
|
145
|
+
# check size of kappa and element lengths
|
|
146
|
+
if closed and kappa.size != el_lengths.size:
|
|
147
|
+
raise RuntimeError("kappa and el_lengths must have the same length if closed!")
|
|
148
|
+
|
|
149
|
+
elif not closed and kappa.size != el_lengths.size + 1:
|
|
150
|
+
raise RuntimeError("kappa must have the length of el_lengths + 1 if unclosed!")
|
|
151
|
+
|
|
152
|
+
# check start and end velocities
|
|
153
|
+
if not closed and v_start is None:
|
|
154
|
+
raise RuntimeError("v_start must be provided for the unclosed case!")
|
|
155
|
+
|
|
156
|
+
if v_start is not None and v_start < 0.0:
|
|
157
|
+
v_start = 0.0
|
|
158
|
+
print('WARNING: Input v_start was < 0.0. Using v_start = 0.0 instead!')
|
|
159
|
+
|
|
160
|
+
if v_end is not None and v_end < 0.0:
|
|
161
|
+
v_end = 0.0
|
|
162
|
+
print('WARNING: Input v_end was < 0.0. Using v_end = 0.0 instead!')
|
|
163
|
+
|
|
164
|
+
# check dyn_model_exp
|
|
165
|
+
if not 1.0 <= dyn_model_exp <= 2.0:
|
|
166
|
+
print('WARNING: Exponent for the vehicle dynamics model should be in the range [1.0, 2.0]!')
|
|
167
|
+
|
|
168
|
+
# check shape of ax_max_machines
|
|
169
|
+
if ax_max_machines.shape[1] != 2:
|
|
170
|
+
raise RuntimeError("ax_max_machines must consist of the two columns [vx, ax_max_machines]!")
|
|
171
|
+
|
|
172
|
+
# check v_max
|
|
173
|
+
if v_max is None:
|
|
174
|
+
if ggv is None:
|
|
175
|
+
raise RuntimeError("v_max must be supplied if ggv is None!")
|
|
176
|
+
else:
|
|
177
|
+
v_max = min(ggv[-1, 0], ax_max_machines[-1, 0])
|
|
178
|
+
|
|
179
|
+
else:
|
|
180
|
+
# check if ggv covers velocity until v_max
|
|
181
|
+
if ggv is not None and ggv[-1, 0] < v_max:
|
|
182
|
+
raise RuntimeError("ggv has to cover the entire velocity range of the car (i.e. >= v_max)!")
|
|
183
|
+
|
|
184
|
+
# check if ax_max_machines covers velocity until v_max
|
|
185
|
+
if ax_max_machines[-1, 0] < v_max:
|
|
186
|
+
raise RuntimeError("ax_max_machines has to cover the entire velocity range of the car (i.e. >= v_max)!")
|
|
187
|
+
|
|
188
|
+
# ------------------------------------------------------------------------------------------------------------------
|
|
189
|
+
# BRINGING GGV OR LOC_GG INTO SHAPE FOR EQUAL HANDLING AFTERWARDS --------------------------------------------------
|
|
190
|
+
# ------------------------------------------------------------------------------------------------------------------
|
|
191
|
+
|
|
192
|
+
"""For an equal/easier handling of every case afterwards we bring all cases into a form where the local ggv is made
|
|
193
|
+
available for every waypoint, i.e. [ggv_0, ggv_1, ggv_2, ...] -> we have a three dimensional array p_ggv (path_ggv)
|
|
194
|
+
where the first dimension is the waypoint, the second is the velocity and the third is the two acceleration columns
|
|
195
|
+
-> DIM = NO_WAYPOINTS_CLOSED x NO_VELOCITY ENTRIES x 3"""
|
|
196
|
+
|
|
197
|
+
# CASE 1: ggv supplied -> copy it for every waypoint (skip if pre-built p_ggv provided)
|
|
198
|
+
if ggv is not None:
|
|
199
|
+
if p_ggv is None or p_ggv.shape[0] != kappa.size:
|
|
200
|
+
p_ggv = np.repeat(np.expand_dims(ggv, axis=0), kappa.size, axis=0)
|
|
201
|
+
op_mode = 'ggv'
|
|
202
|
+
|
|
203
|
+
# CASE 2: local gg diagram supplied -> add velocity dimension (artificial velocity of 10.0 m/s)
|
|
204
|
+
else:
|
|
205
|
+
p_ggv = np.expand_dims(np.column_stack((np.ones(loc_gg.shape[0]) * 10.0, loc_gg)), axis=1)
|
|
206
|
+
op_mode = 'loc_gg'
|
|
207
|
+
|
|
208
|
+
# ------------------------------------------------------------------------------------------------------------------
|
|
209
|
+
# SPEED PROFILE CALCULATION (FB) -----------------------------------------------------------------------------------
|
|
210
|
+
# ------------------------------------------------------------------------------------------------------------------
|
|
211
|
+
|
|
212
|
+
# transform curvature kappa into corresponding radii (abs because curvature has a sign in our convention)
|
|
213
|
+
radii = np.abs(np.divide(1.0, kappa, out=np.full(kappa.size, np.inf), where=kappa != 0.0))
|
|
214
|
+
|
|
215
|
+
# call solver
|
|
216
|
+
if not closed:
|
|
217
|
+
vx_profile = __solver_fb_unclosed(p_ggv=p_ggv,
|
|
218
|
+
ax_max_machines=ax_max_machines,
|
|
219
|
+
v_max=v_max,
|
|
220
|
+
radii=radii,
|
|
221
|
+
el_lengths=el_lengths,
|
|
222
|
+
mu=mu,
|
|
223
|
+
v_start=v_start,
|
|
224
|
+
v_end=v_end,
|
|
225
|
+
dyn_model_exp=dyn_model_exp,
|
|
226
|
+
drag_coeff=drag_coeff,
|
|
227
|
+
m_veh=m_veh,
|
|
228
|
+
op_mode=op_mode)
|
|
229
|
+
|
|
230
|
+
else:
|
|
231
|
+
vx_profile = __solver_fb_closed(p_ggv=p_ggv,
|
|
232
|
+
ax_max_machines=ax_max_machines,
|
|
233
|
+
v_max=v_max,
|
|
234
|
+
radii=radii,
|
|
235
|
+
el_lengths=el_lengths,
|
|
236
|
+
mu=mu,
|
|
237
|
+
dyn_model_exp=dyn_model_exp,
|
|
238
|
+
drag_coeff=drag_coeff,
|
|
239
|
+
m_veh=m_veh,
|
|
240
|
+
op_mode=op_mode)
|
|
241
|
+
|
|
242
|
+
# ------------------------------------------------------------------------------------------------------------------
|
|
243
|
+
# POSTPROCESSING ---------------------------------------------------------------------------------------------------
|
|
244
|
+
# ------------------------------------------------------------------------------------------------------------------
|
|
245
|
+
|
|
246
|
+
if filt_window is not None:
|
|
247
|
+
vx_profile = conv_filt(signal=vx_profile,
|
|
248
|
+
filt_window=filt_window,
|
|
249
|
+
closed=closed)
|
|
250
|
+
|
|
251
|
+
return vx_profile
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def __solver_fb_unclosed(p_ggv: np.ndarray,
|
|
255
|
+
ax_max_machines: np.ndarray,
|
|
256
|
+
v_max: float,
|
|
257
|
+
radii: np.ndarray,
|
|
258
|
+
el_lengths: np.ndarray,
|
|
259
|
+
v_start: float,
|
|
260
|
+
drag_coeff: float,
|
|
261
|
+
m_veh: float,
|
|
262
|
+
op_mode: str,
|
|
263
|
+
mu: np.ndarray = None,
|
|
264
|
+
v_end: float = None,
|
|
265
|
+
dyn_model_exp: float = 1.0) -> np.ndarray:
|
|
266
|
+
"""
|
|
267
|
+
|
|
268
|
+
.. description::
|
|
269
|
+
Forward-backward solver for an unclosed (open) trajectory. Computes the velocity profile by first applying
|
|
270
|
+
a forward acceleration pass from v_start, then a backward deceleration pass toward v_end.
|
|
271
|
+
|
|
272
|
+
.. inputs::
|
|
273
|
+
:param p_ggv: per-waypoint ggv diagram: [vx, ax_max, ay_max] repeated for each point.
|
|
274
|
+
:type p_ggv: np.ndarray
|
|
275
|
+
:param ax_max_machines: longitudinal acceleration limits by the electrical motors: [vx, ax_max_machines].
|
|
276
|
+
:type ax_max_machines: np.ndarray
|
|
277
|
+
:param v_max: maximum longitudinal speed in m/s.
|
|
278
|
+
:type v_max: float
|
|
279
|
+
:param radii: radius profile of the trajectory in m.
|
|
280
|
+
:type radii: np.ndarray
|
|
281
|
+
:param el_lengths: element lengths between consecutive trajectory points in m.
|
|
282
|
+
:type el_lengths: np.ndarray
|
|
283
|
+
:param v_start: start velocity in m/s.
|
|
284
|
+
:type v_start: float
|
|
285
|
+
:param drag_coeff: drag coefficient including all constants: drag_coeff = 0.5 * c_w * A_front * rho_air.
|
|
286
|
+
:type drag_coeff: float
|
|
287
|
+
:param m_veh: vehicle mass in kg.
|
|
288
|
+
:type m_veh: float
|
|
289
|
+
:param op_mode: operation mode, either 'ggv' or 'loc_gg'.
|
|
290
|
+
:type op_mode: str
|
|
291
|
+
:param mu: friction coefficients along the trajectory (optional, defaults to 1.0).
|
|
292
|
+
:type mu: np.ndarray
|
|
293
|
+
:param v_end: end velocity in m/s (optional).
|
|
294
|
+
:type v_end: float
|
|
295
|
+
:param dyn_model_exp: exponent used in the vehicle dynamics model (usual range [1.0, 2.0]).
|
|
296
|
+
:type dyn_model_exp: float
|
|
297
|
+
|
|
298
|
+
.. outputs::
|
|
299
|
+
:return vx_profile: calculated velocity profile (unclosed).
|
|
300
|
+
:rtype vx_profile: np.ndarray
|
|
301
|
+
"""
|
|
302
|
+
|
|
303
|
+
# ------------------------------------------------------------------------------------------------------------------
|
|
304
|
+
# FORWARD BACKWARD SOLVER ------------------------------------------------------------------------------------------
|
|
305
|
+
# ------------------------------------------------------------------------------------------------------------------
|
|
306
|
+
|
|
307
|
+
# handle mu
|
|
308
|
+
if mu is None:
|
|
309
|
+
mu = np.ones(radii.size)
|
|
310
|
+
mu_mean = 1.0
|
|
311
|
+
else:
|
|
312
|
+
mu_mean = np.mean(mu)
|
|
313
|
+
|
|
314
|
+
# run through all the points and check for possible lateral acceleration
|
|
315
|
+
if op_mode == 'ggv':
|
|
316
|
+
# in ggv mode all ggvs are equal -> we can use the first one
|
|
317
|
+
ay_max_global = mu_mean * np.amin(p_ggv[0, :, 2]) # get first lateral acceleration estimate
|
|
318
|
+
vx_profile = np.sqrt(ay_max_global * radii) # get first velocity profile estimate
|
|
319
|
+
|
|
320
|
+
ay_max_curr = mu * np.interp(vx_profile, p_ggv[0, :, 0], p_ggv[0, :, 2])
|
|
321
|
+
vx_profile = np.sqrt(np.multiply(ay_max_curr, radii))
|
|
322
|
+
|
|
323
|
+
else:
|
|
324
|
+
# in loc_gg mode all ggvs consist of a single line due to the missing velocity dependency, mu is None in this
|
|
325
|
+
# case
|
|
326
|
+
vx_profile = np.sqrt(p_ggv[:, 0, 2] * radii) # get first velocity profile estimate
|
|
327
|
+
|
|
328
|
+
# cut vx_profile to car's top speed
|
|
329
|
+
vx_profile[vx_profile > v_max] = v_max
|
|
330
|
+
|
|
331
|
+
# consider v_start
|
|
332
|
+
if vx_profile[0] > v_start:
|
|
333
|
+
vx_profile[0] = v_start
|
|
334
|
+
|
|
335
|
+
# calculate acceleration profile
|
|
336
|
+
vx_profile = __solver_fb_acc_profile(p_ggv=p_ggv,
|
|
337
|
+
ax_max_machines=ax_max_machines,
|
|
338
|
+
v_max=v_max,
|
|
339
|
+
radii=radii,
|
|
340
|
+
el_lengths=el_lengths,
|
|
341
|
+
mu=mu,
|
|
342
|
+
vx_profile=vx_profile,
|
|
343
|
+
backwards=False,
|
|
344
|
+
dyn_model_exp=dyn_model_exp,
|
|
345
|
+
drag_coeff=drag_coeff,
|
|
346
|
+
m_veh=m_veh)
|
|
347
|
+
|
|
348
|
+
# consider v_end
|
|
349
|
+
if v_end is not None and vx_profile[-1] > v_end:
|
|
350
|
+
vx_profile[-1] = v_end
|
|
351
|
+
|
|
352
|
+
# calculate deceleration profile
|
|
353
|
+
vx_profile = __solver_fb_acc_profile(p_ggv=p_ggv,
|
|
354
|
+
ax_max_machines=ax_max_machines,
|
|
355
|
+
v_max=v_max,
|
|
356
|
+
radii=radii,
|
|
357
|
+
el_lengths=el_lengths,
|
|
358
|
+
mu=mu,
|
|
359
|
+
vx_profile=vx_profile,
|
|
360
|
+
backwards=True,
|
|
361
|
+
dyn_model_exp=dyn_model_exp,
|
|
362
|
+
drag_coeff=drag_coeff,
|
|
363
|
+
m_veh=m_veh)
|
|
364
|
+
|
|
365
|
+
return vx_profile
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def __solver_fb_closed(p_ggv: np.ndarray,
|
|
369
|
+
ax_max_machines: np.ndarray,
|
|
370
|
+
v_max: float,
|
|
371
|
+
radii: np.ndarray,
|
|
372
|
+
el_lengths: np.ndarray,
|
|
373
|
+
drag_coeff: float,
|
|
374
|
+
m_veh: float,
|
|
375
|
+
op_mode: str,
|
|
376
|
+
mu: np.ndarray = None,
|
|
377
|
+
dyn_model_exp: float = 1.0) -> np.ndarray:
|
|
378
|
+
"""
|
|
379
|
+
|
|
380
|
+
.. description::
|
|
381
|
+
Forward-backward solver for a closed (circuit) trajectory. Processes two laps to determine a consistent
|
|
382
|
+
velocity profile, applying an acceleration pass followed by a deceleration pass on the doubled arrays.
|
|
383
|
+
|
|
384
|
+
.. inputs::
|
|
385
|
+
:param p_ggv: per-waypoint ggv diagram: [vx, ax_max, ay_max] repeated for each point.
|
|
386
|
+
:type p_ggv: np.ndarray
|
|
387
|
+
:param ax_max_machines: longitudinal acceleration limits by the electrical motors: [vx, ax_max_machines].
|
|
388
|
+
:type ax_max_machines: np.ndarray
|
|
389
|
+
:param v_max: maximum longitudinal speed in m/s.
|
|
390
|
+
:type v_max: float
|
|
391
|
+
:param radii: radius profile of the trajectory in m.
|
|
392
|
+
:type radii: np.ndarray
|
|
393
|
+
:param el_lengths: element lengths between consecutive trajectory points in m.
|
|
394
|
+
:type el_lengths: np.ndarray
|
|
395
|
+
:param drag_coeff: drag coefficient including all constants: drag_coeff = 0.5 * c_w * A_front * rho_air.
|
|
396
|
+
:type drag_coeff: float
|
|
397
|
+
:param m_veh: vehicle mass in kg.
|
|
398
|
+
:type m_veh: float
|
|
399
|
+
:param op_mode: operation mode, either 'ggv' or 'loc_gg'.
|
|
400
|
+
:type op_mode: str
|
|
401
|
+
:param mu: friction coefficients along the trajectory (optional, defaults to 1.0).
|
|
402
|
+
:type mu: np.ndarray
|
|
403
|
+
:param dyn_model_exp: exponent used in the vehicle dynamics model (usual range [1.0, 2.0]).
|
|
404
|
+
:type dyn_model_exp: float
|
|
405
|
+
|
|
406
|
+
.. outputs::
|
|
407
|
+
:return vx_profile: calculated velocity profile (unclosed, second lap extracted).
|
|
408
|
+
:rtype vx_profile: np.ndarray
|
|
409
|
+
"""
|
|
410
|
+
|
|
411
|
+
# ------------------------------------------------------------------------------------------------------------------
|
|
412
|
+
# FORWARD BACKWARD SOLVER ------------------------------------------------------------------------------------------
|
|
413
|
+
# ------------------------------------------------------------------------------------------------------------------
|
|
414
|
+
|
|
415
|
+
no_points = radii.size
|
|
416
|
+
|
|
417
|
+
# handle mu
|
|
418
|
+
if mu is None:
|
|
419
|
+
mu = np.ones(no_points)*1
|
|
420
|
+
mu_mean = 1.0*1
|
|
421
|
+
else:
|
|
422
|
+
mu_mean = np.mean(mu)
|
|
423
|
+
|
|
424
|
+
# run through all the points and check for possible lateral acceleration
|
|
425
|
+
if op_mode == 'ggv':
|
|
426
|
+
# in ggv mode all ggvs are equal -> we can use the first one
|
|
427
|
+
ay_max_global = mu_mean * np.amin(p_ggv[0, :, 2]) # get first lateral acceleration estimate
|
|
428
|
+
vx_profile = np.sqrt(ay_max_global * radii) # get first velocity estimate (radii must be positive!)
|
|
429
|
+
|
|
430
|
+
# iterate until the initial velocity profile converges (break after max. 100 iterations)
|
|
431
|
+
converged = False
|
|
432
|
+
|
|
433
|
+
for i in range(10):
|
|
434
|
+
vx_profile_prev_iteration = vx_profile
|
|
435
|
+
|
|
436
|
+
ay_max_curr = mu * np.interp(vx_profile, p_ggv[0, :, 0], p_ggv[0, :, 2])
|
|
437
|
+
vx_profile = np.sqrt(np.multiply(ay_max_curr, radii))
|
|
438
|
+
|
|
439
|
+
# break the loop if the maximum change of the velocity profile was below 0.5%
|
|
440
|
+
if np.max(np.abs(vx_profile / vx_profile_prev_iteration - 1.0)) < 0.005:
|
|
441
|
+
converged = True
|
|
442
|
+
break
|
|
443
|
+
|
|
444
|
+
if not converged:
|
|
445
|
+
print("The initial vx profile did not converge after 100 iterations, please check radii and ggv!")
|
|
446
|
+
|
|
447
|
+
else:
|
|
448
|
+
# in loc_gg mode all ggvs consist of a single line due to the missing velocity dependency, mu is None in this
|
|
449
|
+
# case
|
|
450
|
+
vx_profile = np.sqrt(p_ggv[:, 0, 2] * radii) # get first velocity estimate (radii must be positive!)
|
|
451
|
+
|
|
452
|
+
# cut vx_profile to car's top speed
|
|
453
|
+
vx_profile[vx_profile > v_max] = v_max
|
|
454
|
+
|
|
455
|
+
"""We need to calculate the speed profile for three laps and keep the middle lap.
|
|
456
|
+
|
|
457
|
+
Why 3 laps, not 2:
|
|
458
|
+
The backward pass is implemented by *flipping* the array and running the same forward
|
|
459
|
+
algorithm. The forward algorithm only ever writes vx_profile[i+1] — it never touches
|
|
460
|
+
vx_profile[0] (index 0 of the flipped array = vx[-1] of the original).
|
|
461
|
+
|
|
462
|
+
With 2-lap doubling the flipped array has size 2*N. vx[-1] of the second copy sits at
|
|
463
|
+
flipped[0] and is therefore *never* constrained by the backward pass, regardless of
|
|
464
|
+
how many iterations you run. The car's speed at the end of the lap is never forced to
|
|
465
|
+
account for the braking needed to enter the next lap's corner — so vx[-1] stays too
|
|
466
|
+
high and the appended wrap-around acceleration ax = (vx[0]²-vx[-1]²)/(2*ds) comes out
|
|
467
|
+
at -36 m/s².
|
|
468
|
+
|
|
469
|
+
With 3 laps the flipped array has size 3*N. In the flipped representation the corner
|
|
470
|
+
at the start of the third copy sits at flipped[N-1] (= vx[0] of the third copy =
|
|
471
|
+
the slow-corner speed, e.g. 15 m/s). That creates an acceleration phase at index
|
|
472
|
+
N-1 → N, so the forward-on-flipped algorithm constrains flipped[N] = vx[-1] of the
|
|
473
|
+
middle (second) copy to the physically correct braking distance from the corner.
|
|
474
|
+
After flipping back, result[2*N-1] = vx[-1] of the middle lap is correctly limited. ✓
|
|
475
|
+
"""
|
|
476
|
+
|
|
477
|
+
# Triple the track arrays (fixed for both passes)
|
|
478
|
+
radii_triple = np.concatenate((radii, radii, radii), axis=0)
|
|
479
|
+
el_lengths_triple = np.concatenate((el_lengths, el_lengths, el_lengths), axis=0)
|
|
480
|
+
mu_triple = np.concatenate((mu, mu, mu), axis=0)
|
|
481
|
+
p_ggv_triple = np.concatenate((p_ggv, p_ggv, p_ggv), axis=0)
|
|
482
|
+
|
|
483
|
+
# --- forward (acceleration) pass on 3 laps; keep middle lap ---
|
|
484
|
+
vx_profile_triple = np.concatenate((vx_profile, vx_profile, vx_profile), axis=0)
|
|
485
|
+
vx_profile_triple = __solver_fb_acc_profile(p_ggv=p_ggv_triple,
|
|
486
|
+
ax_max_machines=ax_max_machines,
|
|
487
|
+
v_max=v_max,
|
|
488
|
+
radii=radii_triple,
|
|
489
|
+
el_lengths=el_lengths_triple,
|
|
490
|
+
mu=mu_triple,
|
|
491
|
+
vx_profile=vx_profile_triple,
|
|
492
|
+
backwards=False,
|
|
493
|
+
dyn_model_exp=dyn_model_exp,
|
|
494
|
+
drag_coeff=drag_coeff,
|
|
495
|
+
m_veh=m_veh)
|
|
496
|
+
vx_profile = vx_profile_triple[no_points:2 * no_points] # middle lap
|
|
497
|
+
|
|
498
|
+
# --- backward (deceleration) pass on 3 laps; keep middle lap ---
|
|
499
|
+
# vx[-1] of the middle lap is now at flipped[N] (not flipped[0]) and IS
|
|
500
|
+
# constrained by the acceleration phase that starts at flipped[N-1] = vx[0] corner.
|
|
501
|
+
vx_profile_triple = np.concatenate((vx_profile, vx_profile, vx_profile), axis=0)
|
|
502
|
+
vx_profile_triple = __solver_fb_acc_profile(p_ggv=p_ggv_triple,
|
|
503
|
+
ax_max_machines=ax_max_machines,
|
|
504
|
+
v_max=v_max,
|
|
505
|
+
radii=radii_triple,
|
|
506
|
+
el_lengths=el_lengths_triple,
|
|
507
|
+
mu=mu_triple,
|
|
508
|
+
vx_profile=vx_profile_triple,
|
|
509
|
+
backwards=True,
|
|
510
|
+
dyn_model_exp=dyn_model_exp,
|
|
511
|
+
drag_coeff=drag_coeff,
|
|
512
|
+
m_veh=m_veh)
|
|
513
|
+
vx_profile = vx_profile_triple[no_points:2 * no_points] # middle lap
|
|
514
|
+
|
|
515
|
+
return vx_profile
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
def __solver_fb_acc_profile(p_ggv: np.ndarray,
|
|
519
|
+
ax_max_machines: np.ndarray,
|
|
520
|
+
v_max: float,
|
|
521
|
+
radii: np.ndarray,
|
|
522
|
+
el_lengths: np.ndarray,
|
|
523
|
+
mu: np.ndarray,
|
|
524
|
+
vx_profile: np.ndarray,
|
|
525
|
+
drag_coeff: float,
|
|
526
|
+
m_veh: float,
|
|
527
|
+
dyn_model_exp: float = 1.0,
|
|
528
|
+
backwards: bool = False) -> np.ndarray:
|
|
529
|
+
"""
|
|
530
|
+
|
|
531
|
+
.. description::
|
|
532
|
+
Applies a single forward or backward acceleration pass over the velocity profile. Iterates through
|
|
533
|
+
acceleration phases and enforces physically possible velocities point by point using calc_ax_poss.
|
|
534
|
+
|
|
535
|
+
.. inputs::
|
|
536
|
+
:param p_ggv: per-waypoint ggv diagram: [vx, ax_max, ay_max].
|
|
537
|
+
:type p_ggv: np.ndarray
|
|
538
|
+
:param ax_max_machines: longitudinal acceleration limits by the electrical motors: [vx, ax_max_machines].
|
|
539
|
+
:type ax_max_machines: np.ndarray
|
|
540
|
+
:param v_max: maximum longitudinal speed in m/s.
|
|
541
|
+
:type v_max: float
|
|
542
|
+
:param radii: radius profile of the trajectory in m.
|
|
543
|
+
:type radii: np.ndarray
|
|
544
|
+
:param el_lengths: element lengths between consecutive trajectory points in m.
|
|
545
|
+
:type el_lengths: np.ndarray
|
|
546
|
+
:param mu: friction coefficients along the trajectory.
|
|
547
|
+
:type mu: np.ndarray
|
|
548
|
+
:param vx_profile: current velocity profile to be updated in-place.
|
|
549
|
+
:type vx_profile: np.ndarray
|
|
550
|
+
:param drag_coeff: drag coefficient including all constants: drag_coeff = 0.5 * c_w * A_front * rho_air.
|
|
551
|
+
:type drag_coeff: float
|
|
552
|
+
:param m_veh: vehicle mass in kg.
|
|
553
|
+
:type m_veh: float
|
|
554
|
+
:param dyn_model_exp: exponent used in the vehicle dynamics model (usual range [1.0, 2.0]).
|
|
555
|
+
:type dyn_model_exp: float
|
|
556
|
+
:param backwards: if True, performs a backward deceleration pass; otherwise forward acceleration.
|
|
557
|
+
:type backwards: bool
|
|
558
|
+
|
|
559
|
+
.. outputs::
|
|
560
|
+
:return vx_profile: updated velocity profile after the acceleration/deceleration pass.
|
|
561
|
+
:rtype vx_profile: np.ndarray
|
|
562
|
+
"""
|
|
563
|
+
|
|
564
|
+
# ------------------------------------------------------------------------------------------------------------------
|
|
565
|
+
# PREPARATIONS -----------------------------------------------------------------------------------------------------
|
|
566
|
+
# ------------------------------------------------------------------------------------------------------------------
|
|
567
|
+
|
|
568
|
+
no_points = vx_profile.size
|
|
569
|
+
|
|
570
|
+
# check for reversed direction
|
|
571
|
+
if backwards:
|
|
572
|
+
radii_mod = np.flipud(radii)
|
|
573
|
+
el_lengths_mod = np.flipud(el_lengths)
|
|
574
|
+
mu_mod = np.flipud(mu)
|
|
575
|
+
vx_profile = np.flipud(vx_profile)
|
|
576
|
+
else:
|
|
577
|
+
radii_mod = radii
|
|
578
|
+
el_lengths_mod = el_lengths
|
|
579
|
+
mu_mod = mu
|
|
580
|
+
|
|
581
|
+
# Pre-extract constant arrays and mode integer for the numba kernel
|
|
582
|
+
mode_int = 2 if backwards else 0 # 0 = accel_forw, 2 = decel_backw
|
|
583
|
+
ax_mach_vx = ax_max_machines[:, 0]
|
|
584
|
+
ax_mach_ax = ax_max_machines[:, 1]
|
|
585
|
+
|
|
586
|
+
# ------------------------------------------------------------------------------------------------------------------
|
|
587
|
+
# SEARCH START POINTS FOR ACCELERATION PHASES ----------------------------------------------------------------------
|
|
588
|
+
# ------------------------------------------------------------------------------------------------------------------
|
|
589
|
+
|
|
590
|
+
vx_diffs = np.diff(vx_profile)
|
|
591
|
+
acc_inds = np.where(vx_diffs > 0.0)[0] # indices of points with positive acceleration
|
|
592
|
+
if acc_inds.size != 0:
|
|
593
|
+
# check index diffs -> we only need the first point of every acceleration phase
|
|
594
|
+
acc_inds_diffs = np.diff(acc_inds)
|
|
595
|
+
acc_inds_diffs = np.insert(acc_inds_diffs, 0, 2) # first point is always a starting point
|
|
596
|
+
acc_inds_rel = acc_inds[acc_inds_diffs > 1] # starting point indices for acceleration phases
|
|
597
|
+
else:
|
|
598
|
+
acc_inds_rel = [] # if vmax is low and can be driven all the time
|
|
599
|
+
|
|
600
|
+
# ------------------------------------------------------------------------------------------------------------------
|
|
601
|
+
# CALCULATE VELOCITY PROFILE ---------------------------------------------------------------------------------------
|
|
602
|
+
# ------------------------------------------------------------------------------------------------------------------
|
|
603
|
+
|
|
604
|
+
# cast np.array as a list
|
|
605
|
+
acc_inds_rel = list(acc_inds_rel)
|
|
606
|
+
|
|
607
|
+
# while we have indices remaining in the list
|
|
608
|
+
while acc_inds_rel:
|
|
609
|
+
# set index to first list element
|
|
610
|
+
i = acc_inds_rel.pop(0)
|
|
611
|
+
|
|
612
|
+
# start from current index and run until either the end of the lap or a termination criterion are reached
|
|
613
|
+
while i < no_points - 1:
|
|
614
|
+
|
|
615
|
+
ax_possible_cur = _calc_ax_poss_nb(
|
|
616
|
+
float(vx_profile[i]), float(radii_mod[i]),
|
|
617
|
+
p_ggv[i, :, 0], p_ggv[i, :, 1], p_ggv[i, :, 2],
|
|
618
|
+
ax_mach_vx, ax_mach_ax,
|
|
619
|
+
float(mu_mod[i]), dyn_model_exp, drag_coeff, m_veh, mode_int)
|
|
620
|
+
|
|
621
|
+
vx_possible_next = math.sqrt(math.pow(vx_profile[i], 2) + 2 * ax_possible_cur * el_lengths_mod[i])
|
|
622
|
+
|
|
623
|
+
if backwards:
|
|
624
|
+
"""
|
|
625
|
+
We have to loop the calculation if we are in the backwards iteration (currently just once). This is
|
|
626
|
+
because we calculate the possible ax at a point i which does not necessarily fit for point i + 1
|
|
627
|
+
(which is i - 1 in the real direction). At point i + 1 (or i - 1 in real direction) we have a different
|
|
628
|
+
start velocity (vx_possible_next), radius and mu value while the absolute value of ax remains the same
|
|
629
|
+
in both directions.
|
|
630
|
+
"""
|
|
631
|
+
|
|
632
|
+
# looping just once at the moment
|
|
633
|
+
for j in range(1):
|
|
634
|
+
ax_possible_next = _calc_ax_poss_nb(
|
|
635
|
+
float(vx_possible_next), float(radii_mod[i + 1]),
|
|
636
|
+
p_ggv[i + 1, :, 0], p_ggv[i + 1, :, 1], p_ggv[i + 1, :, 2],
|
|
637
|
+
ax_mach_vx, ax_mach_ax,
|
|
638
|
+
float(mu_mod[i + 1]), dyn_model_exp, drag_coeff, m_veh, mode_int)
|
|
639
|
+
|
|
640
|
+
vx_tmp = math.sqrt(math.pow(vx_profile[i], 2) + 2 * ax_possible_next * el_lengths_mod[i])
|
|
641
|
+
|
|
642
|
+
if vx_tmp < vx_possible_next:
|
|
643
|
+
vx_possible_next = vx_tmp
|
|
644
|
+
else:
|
|
645
|
+
break
|
|
646
|
+
|
|
647
|
+
# save possible next velocity if it is smaller than the current value
|
|
648
|
+
if vx_possible_next < vx_profile[i + 1]:
|
|
649
|
+
vx_profile[i + 1] = vx_possible_next
|
|
650
|
+
|
|
651
|
+
i += 1
|
|
652
|
+
|
|
653
|
+
# break current acceleration phase if next speed would be higher than the maximum vehicle velocity or if we
|
|
654
|
+
# are at the next acceleration phase start index
|
|
655
|
+
if vx_possible_next > v_max or (acc_inds_rel and i >= acc_inds_rel[0]):
|
|
656
|
+
break
|
|
657
|
+
|
|
658
|
+
# ------------------------------------------------------------------------------------------------------------------
|
|
659
|
+
# POSTPROCESSING ---------------------------------------------------------------------------------------------------
|
|
660
|
+
# ------------------------------------------------------------------------------------------------------------------
|
|
661
|
+
|
|
662
|
+
# flip output vel_profile if necessary
|
|
663
|
+
if backwards:
|
|
664
|
+
vx_profile = np.flipud(vx_profile)
|
|
665
|
+
|
|
666
|
+
return vx_profile
|
|
667
|
+
|
|
668
|
+
|
|
669
|
+
def calc_ax_poss(vx_start: float,
|
|
670
|
+
radius: float,
|
|
671
|
+
ggv: np.ndarray,
|
|
672
|
+
mu: float,
|
|
673
|
+
dyn_model_exp: float,
|
|
674
|
+
drag_coeff: float,
|
|
675
|
+
m_veh: float,
|
|
676
|
+
ax_max_machines: np.ndarray = None,
|
|
677
|
+
mode: str = 'accel_forw') -> float:
|
|
678
|
+
"""
|
|
679
|
+
This function returns the possible longitudinal acceleration in the current step/point.
|
|
680
|
+
|
|
681
|
+
.. inputs::
|
|
682
|
+
:param vx_start: [m/s] velocity at current point
|
|
683
|
+
:type vx_start: float
|
|
684
|
+
:param radius: [m] radius on which the car is currently driving
|
|
685
|
+
:type radius: float
|
|
686
|
+
:param ggv: ggv-diagram to be applied: [vx, ax_max, ay_max]. Velocity in m/s, accelerations in m/s2.
|
|
687
|
+
:type ggv: np.ndarray
|
|
688
|
+
:param mu: [-] current friction value
|
|
689
|
+
:type mu: float
|
|
690
|
+
:param dyn_model_exp: [-] exponent used in the vehicle dynamics model (usual range [1.0,2.0]).
|
|
691
|
+
:type dyn_model_exp: float
|
|
692
|
+
:param drag_coeff: [m2*kg/m3] drag coefficient incl. all constants: drag_coeff = 0.5 * c_w * A_front * rho_air
|
|
693
|
+
:type drag_coeff: float
|
|
694
|
+
:param m_veh: [kg] vehicle mass
|
|
695
|
+
:type m_veh: float
|
|
696
|
+
:param ax_max_machines: longitudinal acceleration limits by the electrical motors: [vx, ax_max_machines]. Velocity
|
|
697
|
+
in m/s, accelerations in m/s2. They should be handed in without considering drag resistance.
|
|
698
|
+
Can be set None if using one of the decel modes.
|
|
699
|
+
:type ax_max_machines: np.ndarray
|
|
700
|
+
:param mode: [-] operation mode, can be 'accel_forw', 'decel_forw', 'decel_backw'
|
|
701
|
+
-> determines if machine limitations are considered and if ax should be considered negative
|
|
702
|
+
or positive during deceleration (for possible backwards iteration)
|
|
703
|
+
:type mode: str
|
|
704
|
+
|
|
705
|
+
.. outputs::
|
|
706
|
+
:return ax_final: [m/s2] final acceleration from current point to next one
|
|
707
|
+
:rtype ax_final: float
|
|
708
|
+
"""
|
|
709
|
+
|
|
710
|
+
# ------------------------------------------------------------------------------------------------------------------
|
|
711
|
+
# PREPARATIONS -----------------------------------------------------------------------------------------------------
|
|
712
|
+
# ------------------------------------------------------------------------------------------------------------------
|
|
713
|
+
|
|
714
|
+
# check inputs
|
|
715
|
+
if mode not in ['accel_forw', 'decel_forw', 'decel_backw']:
|
|
716
|
+
raise RuntimeError("Unknown operation mode for calc_ax_poss!")
|
|
717
|
+
|
|
718
|
+
if mode == 'accel_forw' and ax_max_machines is None:
|
|
719
|
+
raise RuntimeError("ax_max_machines is required if operation mode is accel_forw!")
|
|
720
|
+
|
|
721
|
+
if ggv.ndim != 2 or ggv.shape[1] != 3:
|
|
722
|
+
raise RuntimeError("ggv must have two dimensions and three columns [vx, ax_max, ay_max]!")
|
|
723
|
+
|
|
724
|
+
# ------------------------------------------------------------------------------------------------------------------
|
|
725
|
+
# CONSIDER TIRE POTENTIAL ------------------------------------------------------------------------------------------
|
|
726
|
+
# ------------------------------------------------------------------------------------------------------------------
|
|
727
|
+
|
|
728
|
+
# calculate possible and used accelerations (considering tires)
|
|
729
|
+
ax_max_tires = mu * np.interp(vx_start, ggv[:, 0], ggv[:, 1])
|
|
730
|
+
ay_max_tires = mu * np.interp(vx_start, ggv[:, 0], ggv[:, 2])
|
|
731
|
+
ay_used = math.pow(vx_start, 2) / radius
|
|
732
|
+
|
|
733
|
+
# during forward acceleration and backward deceleration ax_max_tires must be considered positive, during forward
|
|
734
|
+
# deceleration it must be considered negative
|
|
735
|
+
if mode in ['accel_forw', 'decel_backw'] and ax_max_tires < 0.0:
|
|
736
|
+
print("WARNING: Inverting sign of ax_max_tires because it should be positive but was negative!")
|
|
737
|
+
ax_max_tires *= -1.0
|
|
738
|
+
elif mode == 'decel_forw' and ax_max_tires > 0.0:
|
|
739
|
+
print("WARNING: Inverting sign of ax_max_tires because it should be negative but was positve!")
|
|
740
|
+
ax_max_tires *= -1.0
|
|
741
|
+
|
|
742
|
+
radicand = 1.0 - math.pow(ay_used / ay_max_tires, dyn_model_exp)
|
|
743
|
+
|
|
744
|
+
if radicand > 0.0:
|
|
745
|
+
ax_avail_tires = ax_max_tires * math.pow(radicand, 1.0 / dyn_model_exp)
|
|
746
|
+
else:
|
|
747
|
+
ax_avail_tires = 0.0
|
|
748
|
+
|
|
749
|
+
# ------------------------------------------------------------------------------------------------------------------
|
|
750
|
+
# CONSIDER MACHINE LIMITATIONS -------------------------------------------------------------------------------------
|
|
751
|
+
# ------------------------------------------------------------------------------------------------------------------
|
|
752
|
+
|
|
753
|
+
# consider limitations imposed by electrical machines during forward acceleration
|
|
754
|
+
if mode == 'accel_forw':
|
|
755
|
+
# interpolate machine acceleration to be able to consider varying gear ratios, efficiencies etc.
|
|
756
|
+
ax_max_machines_tmp = np.interp(vx_start, ax_max_machines[:, 0], ax_max_machines[:, 1])
|
|
757
|
+
ax_avail_vehicle = min(ax_avail_tires, ax_max_machines_tmp)
|
|
758
|
+
else:
|
|
759
|
+
ax_avail_vehicle = ax_avail_tires
|
|
760
|
+
|
|
761
|
+
# ------------------------------------------------------------------------------------------------------------------
|
|
762
|
+
# CONSIDER DRAG ----------------------------------------------------------------------------------------------------
|
|
763
|
+
# ------------------------------------------------------------------------------------------------------------------
|
|
764
|
+
|
|
765
|
+
# calculate equivalent longitudinal acceleration of drag force at the current speed
|
|
766
|
+
|
|
767
|
+
ax_drag = -math.pow(vx_start, 2) * drag_coeff / m_veh
|
|
768
|
+
# ax_drag = 0
|
|
769
|
+
|
|
770
|
+
|
|
771
|
+
# drag reduces the possible acceleration in the forward case and increases it in the backward case
|
|
772
|
+
if mode in ['accel_forw', 'decel_forw']:
|
|
773
|
+
ax_final = ax_avail_vehicle + ax_drag
|
|
774
|
+
# attention: this value will now be negative in forward direction if tire is entirely used for cornering
|
|
775
|
+
else:
|
|
776
|
+
ax_final = ax_avail_vehicle - ax_drag
|
|
777
|
+
|
|
778
|
+
return ax_final
|
|
779
|
+
|
|
780
|
+
|
|
781
|
+
|
|
782
|
+
def calc_ax_profile(vx_profile: np.ndarray,
|
|
783
|
+
el_lengths: np.ndarray,
|
|
784
|
+
eq_length_output: bool = False) -> np.ndarray:
|
|
785
|
+
"""
|
|
786
|
+
.. description::
|
|
787
|
+
The function calculates the acceleration profile for a given velocity profile.
|
|
788
|
+
|
|
789
|
+
.. inputs::
|
|
790
|
+
:param vx_profile: array containing the velocity profile used as a basis for the acceleration calculations.
|
|
791
|
+
:type vx_profile: np.ndarray
|
|
792
|
+
:param el_lengths: array containing the element lengths between every point of the velocity profile.
|
|
793
|
+
:type el_lengths: np.ndarray
|
|
794
|
+
:param eq_length_output: assumes zero acceleration for the last point of the acceleration profile and therefore
|
|
795
|
+
returns ax_profile with equal length to vx_profile.
|
|
796
|
+
:type eq_length_output: bool
|
|
797
|
+
|
|
798
|
+
.. outputs::
|
|
799
|
+
:return ax_profile: acceleration profile calculated for the inserted vx_profile.
|
|
800
|
+
:rtype ax_profile: np.ndarray
|
|
801
|
+
|
|
802
|
+
.. notes::
|
|
803
|
+
case eq_length_output is True:
|
|
804
|
+
len(vx_profile) = len(el_lengths) + 1 = len(ax_profile)
|
|
805
|
+
|
|
806
|
+
case eq_length_output is False:
|
|
807
|
+
len(vx_profile) = len(el_lengths) + 1 = len(ax_profile) + 1
|
|
808
|
+
"""
|
|
809
|
+
|
|
810
|
+
# check inputs
|
|
811
|
+
if vx_profile.size != el_lengths.size + 1:
|
|
812
|
+
raise RuntimeError("Array size of vx_profile should be 1 element bigger than el_lengths!")
|
|
813
|
+
|
|
814
|
+
# calculate longitudinal acceleration profile array numerically: (v_end^2 - v_beg^2) / 2*s
|
|
815
|
+
if eq_length_output:
|
|
816
|
+
ax_profile = np.zeros(vx_profile.size)
|
|
817
|
+
ax_profile[:-1] = (np.power(vx_profile[1:], 2) - np.power(vx_profile[:-1], 2)) / (2 * el_lengths)
|
|
818
|
+
else:
|
|
819
|
+
ax_profile = (np.power(vx_profile[1:], 2) - np.power(vx_profile[:-1], 2)) / (2 * el_lengths)
|
|
820
|
+
|
|
821
|
+
return ax_profile
|
|
822
|
+
|
|
823
|
+
|
|
824
|
+
def calc_t_profile(vx_profile: np.ndarray,
|
|
825
|
+
el_lengths: np.ndarray,
|
|
826
|
+
t_start: float = 0.0,
|
|
827
|
+
ax_profile: np.ndarray = None) -> np.ndarray:
|
|
828
|
+
"""
|
|
829
|
+
|
|
830
|
+
.. description::
|
|
831
|
+
Calculate a temporal duration profile for a given trajectory.
|
|
832
|
+
|
|
833
|
+
.. inputs::
|
|
834
|
+
:param vx_profile: array containing the velocity profile.
|
|
835
|
+
:type vx_profile: np.ndarray
|
|
836
|
+
:param el_lengths: array containing the element lengths between every point of the velocity profile.
|
|
837
|
+
:type el_lengths: np.ndarray
|
|
838
|
+
:param t_start: start time in seconds added to first array element.
|
|
839
|
+
:type t_start: float
|
|
840
|
+
:param ax_profile: acceleration profile fitting to the velocity profile.
|
|
841
|
+
:type ax_profile: np.ndarray
|
|
842
|
+
|
|
843
|
+
.. outputs::
|
|
844
|
+
:return t_profile: time profile for the given velocity profile.
|
|
845
|
+
:rtype t_profile: np.ndarray
|
|
846
|
+
|
|
847
|
+
.. notes::
|
|
848
|
+
len(el_lengths) + 1 = len(t_profile)
|
|
849
|
+
|
|
850
|
+
len(vx_profile) and len(ax_profile) must be >= len(el_lengths) as the temporal duration from one point to the next
|
|
851
|
+
is only calculated based on the previous point.
|
|
852
|
+
"""
|
|
853
|
+
|
|
854
|
+
# check inputs
|
|
855
|
+
if vx_profile.size < el_lengths.size:
|
|
856
|
+
raise RuntimeError("vx_profile and el_lenghts must have at least the same length!")
|
|
857
|
+
|
|
858
|
+
if ax_profile is not None and ax_profile.size < el_lengths.size:
|
|
859
|
+
raise RuntimeError("ax_profile and el_lenghts must have at least the same length!")
|
|
860
|
+
|
|
861
|
+
# calculate acceleration profile if required
|
|
862
|
+
if ax_profile is None:
|
|
863
|
+
ax_profile = calc_ax_profile(vx_profile=vx_profile,
|
|
864
|
+
el_lengths=el_lengths,
|
|
865
|
+
eq_length_output=False)
|
|
866
|
+
|
|
867
|
+
# calculate temporal duration of every step between two points
|
|
868
|
+
no_points = el_lengths.size
|
|
869
|
+
ax = ax_profile[:no_points]
|
|
870
|
+
vx = vx_profile[:no_points]
|
|
871
|
+
ax_nonzero = ~np.isclose(ax, 0.0)
|
|
872
|
+
discriminant = np.maximum(vx**2 + 2 * ax * el_lengths, 0.0)
|
|
873
|
+
t_steps = np.where(
|
|
874
|
+
ax_nonzero,
|
|
875
|
+
(-vx + np.sqrt(discriminant)) / np.where(ax_nonzero, ax, 1.0),
|
|
876
|
+
el_lengths / vx
|
|
877
|
+
)
|
|
878
|
+
|
|
879
|
+
# calculate temporal duration profile out of steps
|
|
880
|
+
t_profile = np.insert(np.cumsum(t_steps), 0, 0.0) + t_start
|
|
881
|
+
|
|
882
|
+
return t_profile
|
|
883
|
+
|
|
884
|
+
|
|
885
|
+
def curvature_profile(points, s=3):
|
|
886
|
+
"""
|
|
887
|
+
|
|
888
|
+
.. description::
|
|
889
|
+
Fits a parametric spline to a set of 2D points and computes the curvature profile at each point.
|
|
890
|
+
|
|
891
|
+
.. inputs::
|
|
892
|
+
:param points: array of 2D coordinates [[x0, y0], [x1, y1], ...].
|
|
893
|
+
:type points: np.ndarray
|
|
894
|
+
:param s: smoothing factor for the spline fit (passed to splprep).
|
|
895
|
+
:type s: float
|
|
896
|
+
|
|
897
|
+
.. outputs::
|
|
898
|
+
:return tck: spline representation (knots, coefficients, degree) as returned by splprep.
|
|
899
|
+
:rtype tck: tuple
|
|
900
|
+
:return curvatures: curvature values at each input point in rad/m.
|
|
901
|
+
:rtype curvatures: np.ndarray
|
|
902
|
+
"""
|
|
903
|
+
|
|
904
|
+
points = np.array(points)
|
|
905
|
+
x = points[:, 0]
|
|
906
|
+
y = points[:, 1]
|
|
907
|
+
|
|
908
|
+
# Fit the spline to the points
|
|
909
|
+
tck, u = splprep([x, y], s=s)
|
|
910
|
+
|
|
911
|
+
# Evaluate first and second derivatives of the spline
|
|
912
|
+
dx, dy = splev(u, tck, der=1)
|
|
913
|
+
ddx, ddy = splev(u, tck, der=2)
|
|
914
|
+
|
|
915
|
+
# Compute curvature
|
|
916
|
+
curvatures = (dx * ddy - dy * ddx) / np.power(dx**2 + dy**2, 1.5)
|
|
917
|
+
|
|
918
|
+
return tck, curvatures
|
|
919
|
+
|
|
920
|
+
def curvature_profile2(reftrack):
|
|
921
|
+
"""
|
|
922
|
+
|
|
923
|
+
.. description::
|
|
924
|
+
Computes the curvature profile of a closed reference track using cubic spline coefficients. Splines are
|
|
925
|
+
calculated via calc_splines and curvature is derived analytically from the first and second derivatives.
|
|
926
|
+
|
|
927
|
+
.. inputs::
|
|
928
|
+
:param reftrack: reference track array where columns 0 and 1 are the x and y coordinates respectively.
|
|
929
|
+
:type reftrack: np.ndarray
|
|
930
|
+
|
|
931
|
+
.. outputs::
|
|
932
|
+
:return curv: curvature profile of the reference track in rad/m.
|
|
933
|
+
:rtype curv: np.ndarray
|
|
934
|
+
:return coeffs_x: cubic spline coefficients for the x coordinate.
|
|
935
|
+
:rtype coeffs_x: np.ndarray
|
|
936
|
+
:return coeffs_y: cubic spline coefficients for the y coordinate.
|
|
937
|
+
:rtype coeffs_y: np.ndarray
|
|
938
|
+
"""
|
|
939
|
+
|
|
940
|
+
coeffs_x, coeffs_y, M, normvec_norm2 = calc_splines(path=np.vstack((reftrack[:, 0:2], reftrack[0, 0:2])))
|
|
941
|
+
# dx[i] = (1/ds_f[i])*coeffs_x[i,1]
|
|
942
|
+
# dy[i] = (1/ds_f[i])*coeffs_y[i,1]
|
|
943
|
+
# ddx[i] = (1/ds_f[i])**2*2*coeffs_x[i,2]
|
|
944
|
+
# ddy[i] = (1/ds_f[i])**2*2*coeffs_y[i,2]
|
|
945
|
+
dx = coeffs_x[:, 1]
|
|
946
|
+
dy = coeffs_y[:, 1]
|
|
947
|
+
ddx = 2 * coeffs_x[:, 2]
|
|
948
|
+
ddy = 2 * coeffs_y[:, 2]
|
|
949
|
+
curv = (dx * ddy - dy * ddx) / np.power(dx**2 + dy**2, 1.5)
|
|
950
|
+
|
|
951
|
+
return curv,coeffs_x,coeffs_y
|
|
952
|
+
|
|
953
|
+
def cumulative_distances(distances):
|
|
954
|
+
"""
|
|
955
|
+
|
|
956
|
+
.. description::
|
|
957
|
+
Computes cumulative distances along a path, starting from zero, given segment lengths.
|
|
958
|
+
|
|
959
|
+
.. inputs::
|
|
960
|
+
:param distances: array of segment lengths between consecutive path points.
|
|
961
|
+
:type distances: np.ndarray
|
|
962
|
+
|
|
963
|
+
.. outputs::
|
|
964
|
+
:return cumulative_d: cumulative distance array starting at 0.0, length equal to len(distances) - 1 + 1.
|
|
965
|
+
:rtype cumulative_d: np.ndarray
|
|
966
|
+
"""
|
|
967
|
+
|
|
968
|
+
cumulative_d = np.cumsum(distances[:-1])
|
|
969
|
+
cumulative_d = np.insert(cumulative_d, 0, 0)
|
|
970
|
+
return cumulative_d
|