InterpolatePy 1.0.0__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.
- interpolatepy/__init__.py +8 -0
- interpolatepy/b_spline.py +737 -0
- interpolatepy/b_spline_approx.py +544 -0
- interpolatepy/b_spline_cubic.py +444 -0
- interpolatepy/b_spline_interpolate.py +515 -0
- interpolatepy/b_spline_smooth.py +639 -0
- interpolatepy/c_s_smoot_search.py +177 -0
- interpolatepy/c_s_smoothing.py +611 -0
- interpolatepy/c_s_with_acc1.py +643 -0
- interpolatepy/c_s_with_acc2.py +494 -0
- interpolatepy/cubic_spline.py +486 -0
- interpolatepy/double_s.py +580 -0
- interpolatepy/frenet_frame.py +245 -0
- interpolatepy/linear.py +107 -0
- interpolatepy/polynomials.py +451 -0
- interpolatepy/simple_paths.py +281 -0
- interpolatepy/trapezoidal.py +613 -0
- interpolatepy/tridiagonal_inv.py +96 -0
- interpolatepy/version.py +5 -0
- interpolatepy-1.0.0.dist-info/METADATA +415 -0
- interpolatepy-1.0.0.dist-info/RECORD +24 -0
- interpolatepy-1.0.0.dist-info/WHEEL +5 -0
- interpolatepy-1.0.0.dist-info/licenses/LICENSE +21 -0
- interpolatepy-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from typing import ClassVar
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# Define constants for polynomial orders
|
|
9
|
+
ORDER_3 = 3
|
|
10
|
+
ORDER_5 = 5
|
|
11
|
+
ORDER_7 = 7
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class BoundaryCondition:
|
|
16
|
+
"""Class for storing boundary conditions for trajectory generation."""
|
|
17
|
+
|
|
18
|
+
position: float
|
|
19
|
+
velocity: float
|
|
20
|
+
acceleration: float = 0.0
|
|
21
|
+
jerk: float = 0.0
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class TimeInterval:
|
|
26
|
+
"""Class for storing time interval for trajectory generation."""
|
|
27
|
+
|
|
28
|
+
start: float
|
|
29
|
+
end: float
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class TrajectoryParams:
|
|
34
|
+
"""Class for storing parameters for multipoint trajectory generation."""
|
|
35
|
+
|
|
36
|
+
points: list[float]
|
|
37
|
+
times: list[float]
|
|
38
|
+
velocities: list[float] | None = None
|
|
39
|
+
accelerations: list[float] | None = None
|
|
40
|
+
jerks: list[float] | None = None
|
|
41
|
+
order: int = ORDER_3
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class PolynomialTrajectory:
|
|
45
|
+
"""
|
|
46
|
+
A class for generating polynomial trajectories with specified boundary conditions.
|
|
47
|
+
|
|
48
|
+
This class provides methods to create polynomial trajectories of different orders
|
|
49
|
+
(3rd, 5th, and 7th) with specified boundary conditions such as position, velocity,
|
|
50
|
+
acceleration, and jerk. It also supports creating trajectories through multiple points.
|
|
51
|
+
|
|
52
|
+
Methods
|
|
53
|
+
-------
|
|
54
|
+
order_3_trajectory
|
|
55
|
+
Generate a 3rd order polynomial trajectory with position and velocity constraints
|
|
56
|
+
order_5_trajectory
|
|
57
|
+
Generate a 5th order polynomial trajectory with position, velocity, and
|
|
58
|
+
acceleration constraints
|
|
59
|
+
order_7_trajectory
|
|
60
|
+
Generate a 7th order polynomial trajectory with position, velocity, acceleration,
|
|
61
|
+
and jerk constraints
|
|
62
|
+
heuristic_velocities
|
|
63
|
+
Compute intermediate velocities for a sequence of points
|
|
64
|
+
multipoint_trajectory
|
|
65
|
+
Generate a trajectory through a sequence of points with specified times
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
# Define the valid polynomial orders as class variables
|
|
69
|
+
VALID_ORDERS: ClassVar[tuple[int, ...]] = (ORDER_3, ORDER_5, ORDER_7)
|
|
70
|
+
|
|
71
|
+
@staticmethod
|
|
72
|
+
def order_3_trajectory(
|
|
73
|
+
initial: BoundaryCondition,
|
|
74
|
+
final: BoundaryCondition,
|
|
75
|
+
time: TimeInterval,
|
|
76
|
+
) -> Callable[[float], tuple[float, float, float, float]]:
|
|
77
|
+
"""
|
|
78
|
+
Generate a 3rd order polynomial trajectory with specified boundary conditions.
|
|
79
|
+
|
|
80
|
+
Parameters
|
|
81
|
+
----------
|
|
82
|
+
initial : BoundaryCondition
|
|
83
|
+
Initial boundary conditions (position, velocity)
|
|
84
|
+
final : BoundaryCondition
|
|
85
|
+
Final boundary conditions (position, velocity)
|
|
86
|
+
time : TimeInterval
|
|
87
|
+
Time interval for the trajectory
|
|
88
|
+
|
|
89
|
+
Returns
|
|
90
|
+
-------
|
|
91
|
+
Callable[[float], tuple[float, float, float, float]]
|
|
92
|
+
Function that computes position, velocity, acceleration, and jerk at time t
|
|
93
|
+
"""
|
|
94
|
+
t_diff = time.end - time.start
|
|
95
|
+
h = final.position - initial.position
|
|
96
|
+
|
|
97
|
+
# Coefficients as defined in equation (2.2)
|
|
98
|
+
a0 = initial.position
|
|
99
|
+
a1 = initial.velocity
|
|
100
|
+
a2 = (3 * h - (2 * initial.velocity + final.velocity) * t_diff) / (t_diff**2)
|
|
101
|
+
a3 = (-2 * h + (initial.velocity + final.velocity) * t_diff) / (t_diff**3)
|
|
102
|
+
|
|
103
|
+
def trajectory(t: float) -> tuple[float, float, float, float]:
|
|
104
|
+
# Ensure t is within bounds
|
|
105
|
+
t = np.clip(t, time.start, time.end)
|
|
106
|
+
|
|
107
|
+
# Time relative to t_start
|
|
108
|
+
tau = t - time.start
|
|
109
|
+
|
|
110
|
+
# Position
|
|
111
|
+
q = a0 + a1 * tau + a2 * tau**2 + a3 * tau**3
|
|
112
|
+
|
|
113
|
+
# Velocity
|
|
114
|
+
qd = a1 + 2 * a2 * tau + 3 * a3 * tau**2
|
|
115
|
+
|
|
116
|
+
# Acceleration
|
|
117
|
+
qdd = 2 * a2 + 6 * a3 * tau
|
|
118
|
+
|
|
119
|
+
# Jerk
|
|
120
|
+
qddd = 6 * a3
|
|
121
|
+
|
|
122
|
+
return q, qd, qdd, qddd
|
|
123
|
+
|
|
124
|
+
return trajectory
|
|
125
|
+
|
|
126
|
+
@staticmethod
|
|
127
|
+
def order_5_trajectory(
|
|
128
|
+
initial: BoundaryCondition,
|
|
129
|
+
final: BoundaryCondition,
|
|
130
|
+
time: TimeInterval,
|
|
131
|
+
) -> Callable[[float], tuple[float, float, float, float]]:
|
|
132
|
+
"""
|
|
133
|
+
Generate a 5th order polynomial trajectory with specified boundary conditions.
|
|
134
|
+
|
|
135
|
+
Parameters
|
|
136
|
+
----------
|
|
137
|
+
initial : BoundaryCondition
|
|
138
|
+
Initial boundary conditions (position, velocity, acceleration)
|
|
139
|
+
final : BoundaryCondition
|
|
140
|
+
Final boundary conditions (position, velocity, acceleration)
|
|
141
|
+
time : TimeInterval
|
|
142
|
+
Time interval for the trajectory
|
|
143
|
+
|
|
144
|
+
Returns
|
|
145
|
+
-------
|
|
146
|
+
Callable[[float], tuple[float, float, float, float]]
|
|
147
|
+
Function that computes position, velocity, acceleration, and jerk at time t
|
|
148
|
+
"""
|
|
149
|
+
t_diff = time.end - time.start
|
|
150
|
+
h = final.position - initial.position
|
|
151
|
+
|
|
152
|
+
# Coefficients as defined in equation (2.5)
|
|
153
|
+
a0 = initial.position
|
|
154
|
+
a1 = initial.velocity
|
|
155
|
+
a2 = initial.acceleration / 2
|
|
156
|
+
a3 = (1 / (2 * t_diff**3)) * (
|
|
157
|
+
20 * h
|
|
158
|
+
- (8 * final.velocity + 12 * initial.velocity) * t_diff
|
|
159
|
+
- (3 * initial.acceleration - final.acceleration) * t_diff**2
|
|
160
|
+
)
|
|
161
|
+
a4 = (1 / (2 * t_diff**4)) * (
|
|
162
|
+
-30 * h
|
|
163
|
+
+ (14 * final.velocity + 16 * initial.velocity) * t_diff
|
|
164
|
+
+ (3 * initial.acceleration - 2 * final.acceleration) * t_diff**2
|
|
165
|
+
)
|
|
166
|
+
a5 = (1 / (2 * t_diff**5)) * (
|
|
167
|
+
12 * h
|
|
168
|
+
- 6 * (final.velocity + initial.velocity) * t_diff
|
|
169
|
+
+ (final.acceleration - initial.acceleration) * t_diff**2
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
def trajectory(t: float) -> tuple[float, float, float, float]:
|
|
173
|
+
# Ensure t is within bounds
|
|
174
|
+
t = np.clip(t, time.start, time.end)
|
|
175
|
+
|
|
176
|
+
# Time relative to t_start
|
|
177
|
+
tau = t - time.start
|
|
178
|
+
|
|
179
|
+
# Position
|
|
180
|
+
q = a0 + a1 * tau + a2 * tau**2 + a3 * tau**3 + a4 * tau**4 + a5 * tau**5
|
|
181
|
+
|
|
182
|
+
# Velocity
|
|
183
|
+
qd = a1 + 2 * a2 * tau + 3 * a3 * tau**2 + 4 * a4 * tau**3 + 5 * a5 * tau**4
|
|
184
|
+
|
|
185
|
+
# Acceleration
|
|
186
|
+
qdd = 2 * a2 + 6 * a3 * tau + 12 * a4 * tau**2 + 20 * a5 * tau**3
|
|
187
|
+
|
|
188
|
+
# Jerk
|
|
189
|
+
qddd = 6 * a3 + 24 * a4 * tau + 60 * a5 * tau**2
|
|
190
|
+
|
|
191
|
+
return q, qd, qdd, qddd
|
|
192
|
+
|
|
193
|
+
return trajectory
|
|
194
|
+
|
|
195
|
+
@staticmethod
|
|
196
|
+
def order_7_trajectory(
|
|
197
|
+
initial: BoundaryCondition,
|
|
198
|
+
final: BoundaryCondition,
|
|
199
|
+
time: TimeInterval,
|
|
200
|
+
) -> Callable[[float], tuple[float, float, float, float]]:
|
|
201
|
+
"""
|
|
202
|
+
Generate a 7th order polynomial trajectory with specified boundary conditions.
|
|
203
|
+
|
|
204
|
+
Parameters
|
|
205
|
+
----------
|
|
206
|
+
initial : BoundaryCondition
|
|
207
|
+
Initial boundary conditions (position, velocity, acceleration, jerk)
|
|
208
|
+
final : BoundaryCondition
|
|
209
|
+
Final boundary conditions (position, velocity, acceleration, jerk)
|
|
210
|
+
time : TimeInterval
|
|
211
|
+
Time interval for the trajectory
|
|
212
|
+
|
|
213
|
+
Returns
|
|
214
|
+
-------
|
|
215
|
+
Callable[[float], tuple[float, float, float, float]]
|
|
216
|
+
Function that computes position, velocity, acceleration, and jerk at time t
|
|
217
|
+
"""
|
|
218
|
+
t_diff = time.end - time.start
|
|
219
|
+
h = final.position - initial.position
|
|
220
|
+
|
|
221
|
+
# Coefficients for 7th order polynomial
|
|
222
|
+
a0 = initial.position
|
|
223
|
+
a1 = initial.velocity
|
|
224
|
+
a2 = initial.acceleration / 2
|
|
225
|
+
a3 = initial.jerk / 6
|
|
226
|
+
a4 = (
|
|
227
|
+
210 * h
|
|
228
|
+
- t_diff
|
|
229
|
+
* (
|
|
230
|
+
(30 * initial.acceleration - 15 * final.acceleration) * t_diff
|
|
231
|
+
+ (4 * initial.jerk + final.jerk) * t_diff**2
|
|
232
|
+
+ 120 * initial.velocity
|
|
233
|
+
+ 90 * final.velocity
|
|
234
|
+
)
|
|
235
|
+
) / (6 * t_diff**4)
|
|
236
|
+
a5 = (
|
|
237
|
+
-168 * h
|
|
238
|
+
+ t_diff
|
|
239
|
+
* (
|
|
240
|
+
(20 * initial.acceleration - 14 * final.acceleration) * t_diff
|
|
241
|
+
+ (2 * initial.jerk + final.jerk) * t_diff**2
|
|
242
|
+
+ 90 * initial.velocity
|
|
243
|
+
+ 78 * final.velocity
|
|
244
|
+
)
|
|
245
|
+
) / (2 * t_diff**5)
|
|
246
|
+
a6 = (
|
|
247
|
+
420 * h
|
|
248
|
+
- t_diff
|
|
249
|
+
* (
|
|
250
|
+
(45 * initial.acceleration - 39 * final.acceleration) * t_diff
|
|
251
|
+
+ (4 * initial.jerk + 3 * final.jerk) * t_diff**2
|
|
252
|
+
+ 216 * initial.velocity
|
|
253
|
+
+ 204 * final.velocity
|
|
254
|
+
)
|
|
255
|
+
) / (6 * t_diff**6)
|
|
256
|
+
a7 = (
|
|
257
|
+
-120 * h
|
|
258
|
+
+ t_diff
|
|
259
|
+
* (
|
|
260
|
+
(12 * initial.acceleration - 12 * final.acceleration) * t_diff
|
|
261
|
+
+ (initial.jerk + final.jerk) * t_diff**2
|
|
262
|
+
+ 60 * initial.velocity
|
|
263
|
+
+ 60 * final.velocity
|
|
264
|
+
)
|
|
265
|
+
) / (6 * t_diff**7)
|
|
266
|
+
|
|
267
|
+
def trajectory(t: float) -> tuple[float, float, float, float]:
|
|
268
|
+
# Ensure t is within bounds
|
|
269
|
+
t = np.clip(t, time.start, time.end)
|
|
270
|
+
|
|
271
|
+
# Time relative to t_start
|
|
272
|
+
tau = t - time.start
|
|
273
|
+
|
|
274
|
+
# Position
|
|
275
|
+
q = (
|
|
276
|
+
a0
|
|
277
|
+
+ a1 * tau
|
|
278
|
+
+ a2 * tau**2
|
|
279
|
+
+ a3 * tau**3
|
|
280
|
+
+ a4 * tau**4
|
|
281
|
+
+ a5 * tau**5
|
|
282
|
+
+ a6 * tau**6
|
|
283
|
+
+ a7 * tau**7
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
# Velocity
|
|
287
|
+
qd = (
|
|
288
|
+
a1
|
|
289
|
+
+ 2 * a2 * tau
|
|
290
|
+
+ 3 * a3 * tau**2
|
|
291
|
+
+ 4 * a4 * tau**3
|
|
292
|
+
+ 5 * a5 * tau**4
|
|
293
|
+
+ 6 * a6 * tau**5
|
|
294
|
+
+ 7 * a7 * tau**6
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
# Acceleration
|
|
298
|
+
qdd = (
|
|
299
|
+
2 * a2
|
|
300
|
+
+ 6 * a3 * tau
|
|
301
|
+
+ 12 * a4 * tau**2
|
|
302
|
+
+ 20 * a5 * tau**3
|
|
303
|
+
+ 30 * a6 * tau**4
|
|
304
|
+
+ 42 * a7 * tau**5
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
# Jerk
|
|
308
|
+
qddd = 6 * a3 + 24 * a4 * tau + 60 * a5 * tau**2 + 120 * a6 * tau**3 + 210 * a7 * tau**4
|
|
309
|
+
|
|
310
|
+
return q, qd, qdd, qddd
|
|
311
|
+
|
|
312
|
+
return trajectory
|
|
313
|
+
|
|
314
|
+
@staticmethod
|
|
315
|
+
def heuristic_velocities(points: list[float], times: list[float]) -> list[float]:
|
|
316
|
+
"""
|
|
317
|
+
Compute intermediate velocities for a sequence of points using the heuristic rule.
|
|
318
|
+
|
|
319
|
+
The heuristic rule sets the velocity at each intermediate point to the average of
|
|
320
|
+
the slopes of the adjacent segments, unless the slopes have different signs, in which
|
|
321
|
+
case the velocity is set to zero.
|
|
322
|
+
|
|
323
|
+
Parameters
|
|
324
|
+
----------
|
|
325
|
+
points : list[float]
|
|
326
|
+
List of position points [q0, q1, ..., qn]
|
|
327
|
+
times : list[float]
|
|
328
|
+
List of time points [t0, t1, ..., tn]
|
|
329
|
+
|
|
330
|
+
Returns
|
|
331
|
+
-------
|
|
332
|
+
list[float]
|
|
333
|
+
List of velocities [v0, v1, ..., vn]
|
|
334
|
+
"""
|
|
335
|
+
n = len(points)
|
|
336
|
+
velocities = [0.0] * n # Initialize with zeros
|
|
337
|
+
|
|
338
|
+
# Compute the slopes between consecutive points
|
|
339
|
+
slopes = [(points[i] - points[i - 1]) / (times[i] - times[i - 1]) for i in range(1, n)]
|
|
340
|
+
|
|
341
|
+
# First and last velocities are set to 0 by default
|
|
342
|
+
velocities[0] = 0.0
|
|
343
|
+
velocities[n - 1] = 0.0
|
|
344
|
+
|
|
345
|
+
# Compute intermediate velocities using the heuristic rule
|
|
346
|
+
for i in range(1, n - 1):
|
|
347
|
+
if np.sign(slopes[i - 1]) != np.sign(slopes[i]):
|
|
348
|
+
velocities[i] = 0.0
|
|
349
|
+
else:
|
|
350
|
+
velocities[i] = 0.5 * (slopes[i - 1] + slopes[i])
|
|
351
|
+
|
|
352
|
+
return velocities
|
|
353
|
+
|
|
354
|
+
@classmethod
|
|
355
|
+
def multipoint_trajectory(
|
|
356
|
+
cls: type["PolynomialTrajectory"],
|
|
357
|
+
params: TrajectoryParams,
|
|
358
|
+
) -> Callable[[float], tuple[float, float, float, float]]:
|
|
359
|
+
"""
|
|
360
|
+
Generate a trajectory through a sequence of points with specified times.
|
|
361
|
+
|
|
362
|
+
Parameters
|
|
363
|
+
----------
|
|
364
|
+
params : TrajectoryParams
|
|
365
|
+
Parameters for trajectory generation including points, times, and optional
|
|
366
|
+
velocities, accelerations, jerks, and polynomial order.
|
|
367
|
+
|
|
368
|
+
Returns
|
|
369
|
+
-------
|
|
370
|
+
Callable[[float], tuple[float, float, float, float]]
|
|
371
|
+
Function that computes trajectory at time t
|
|
372
|
+
|
|
373
|
+
Raises
|
|
374
|
+
------
|
|
375
|
+
ValueError
|
|
376
|
+
If number of points and times are not the same, or if order is not
|
|
377
|
+
one of the valid polynomial orders.
|
|
378
|
+
"""
|
|
379
|
+
n = len(params.points)
|
|
380
|
+
|
|
381
|
+
if n != len(params.times):
|
|
382
|
+
raise ValueError("Number of points and times must be the same")
|
|
383
|
+
|
|
384
|
+
if params.order not in cls.VALID_ORDERS:
|
|
385
|
+
valid_orders_str = ", ".join(str(order) for order in cls.VALID_ORDERS)
|
|
386
|
+
raise ValueError(f"Order must be one of: {valid_orders_str}")
|
|
387
|
+
|
|
388
|
+
# If velocities are not provided, compute using heuristic rule
|
|
389
|
+
vel = params.velocities
|
|
390
|
+
if vel is None:
|
|
391
|
+
vel = cls.heuristic_velocities(params.points, params.times)
|
|
392
|
+
|
|
393
|
+
# If accelerations are not provided, set to zeros
|
|
394
|
+
acc = params.accelerations
|
|
395
|
+
if acc is None and params.order in {ORDER_5, ORDER_7}:
|
|
396
|
+
acc = [0.0] * n
|
|
397
|
+
|
|
398
|
+
# If jerks are not provided, set to zeros
|
|
399
|
+
jrk = params.jerks
|
|
400
|
+
if jrk is None and params.order == ORDER_7:
|
|
401
|
+
jrk = [0.0] * n
|
|
402
|
+
|
|
403
|
+
# Create a list of segment trajectories
|
|
404
|
+
segments = []
|
|
405
|
+
|
|
406
|
+
for i in range(n - 1):
|
|
407
|
+
# Create time interval for this segment
|
|
408
|
+
time_interval = TimeInterval(params.times[i], params.times[i + 1])
|
|
409
|
+
|
|
410
|
+
if params.order == ORDER_3:
|
|
411
|
+
# 3rd order trajectory
|
|
412
|
+
initial = BoundaryCondition(params.points[i], vel[i])
|
|
413
|
+
final = BoundaryCondition(params.points[i + 1], vel[i + 1])
|
|
414
|
+
segment = cls.order_3_trajectory(initial, final, time_interval)
|
|
415
|
+
elif params.order == ORDER_5 and acc is not None:
|
|
416
|
+
# 5th order trajectory
|
|
417
|
+
initial = BoundaryCondition(params.points[i], vel[i], acc[i])
|
|
418
|
+
final = BoundaryCondition(params.points[i + 1], vel[i + 1], acc[i + 1])
|
|
419
|
+
segment = cls.order_5_trajectory(initial, final, time_interval)
|
|
420
|
+
elif params.order == ORDER_7 and acc is not None and jrk is not None:
|
|
421
|
+
# 7th order trajectory
|
|
422
|
+
initial = BoundaryCondition(params.points[i], vel[i], acc[i], jrk[i])
|
|
423
|
+
final = BoundaryCondition(params.points[i + 1], vel[i + 1], acc[i + 1], jrk[i + 1])
|
|
424
|
+
segment = cls.order_7_trajectory(initial, final, time_interval)
|
|
425
|
+
|
|
426
|
+
segments.append((segment, params.times[i], params.times[i + 1]))
|
|
427
|
+
|
|
428
|
+
def trajectory(t: float) -> tuple[float, float, float, float]:
|
|
429
|
+
# Handle boundary cases first for efficiency
|
|
430
|
+
if t < params.times[0]:
|
|
431
|
+
return segments[0][0](params.times[0])
|
|
432
|
+
if t > params.times[-1]:
|
|
433
|
+
return segments[-1][0](params.times[-1])
|
|
434
|
+
|
|
435
|
+
# Binary search to find the appropriate segment
|
|
436
|
+
left, right = 0, len(segments) - 1
|
|
437
|
+
|
|
438
|
+
while left <= right:
|
|
439
|
+
mid = (left + right) // 2
|
|
440
|
+
t_start, t_end = segments[mid][1], segments[mid][2]
|
|
441
|
+
|
|
442
|
+
if t_start <= t <= t_end:
|
|
443
|
+
return segments[mid][0](t)
|
|
444
|
+
if t < t_start:
|
|
445
|
+
right = mid - 1
|
|
446
|
+
else: # t > t_end
|
|
447
|
+
left = mid + 1
|
|
448
|
+
|
|
449
|
+
raise ValueError(f"No segment found for time {t}")
|
|
450
|
+
|
|
451
|
+
return trajectory
|