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,281 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class LinearPath:
|
|
5
|
+
def __init__(self, pi: np.ndarray, pf: np.ndarray) -> None:
|
|
6
|
+
"""
|
|
7
|
+
Initialize a linear path from point pi to point pf.
|
|
8
|
+
|
|
9
|
+
Parameters:
|
|
10
|
+
pi (array-like): Initial point coordinates [x, y, z]
|
|
11
|
+
pf (array-like): Final point coordinates [x, y, z]
|
|
12
|
+
"""
|
|
13
|
+
self.pi: np.ndarray = np.array(pi)
|
|
14
|
+
self.pf: np.ndarray = np.array(pf)
|
|
15
|
+
self.length: float = np.linalg.norm(self.pf - self.pi)
|
|
16
|
+
|
|
17
|
+
# Unit tangent vector (constant for linear path)
|
|
18
|
+
if self.length > 0:
|
|
19
|
+
self.tangent: np.ndarray = (self.pf - self.pi) / self.length
|
|
20
|
+
else:
|
|
21
|
+
self.tangent = np.zeros(3)
|
|
22
|
+
|
|
23
|
+
def position(self, s: float) -> np.ndarray:
|
|
24
|
+
"""
|
|
25
|
+
Calculate position at arc length s.
|
|
26
|
+
|
|
27
|
+
Parameters:
|
|
28
|
+
s (float or array): Arc length parameter(s)
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
numpy.ndarray: Position vector(s)
|
|
32
|
+
"""
|
|
33
|
+
# Ensure s is within valid range
|
|
34
|
+
s = np.clip(s, 0, self.length)
|
|
35
|
+
|
|
36
|
+
# Equation 4.34: p(s) = pi + (s/||pf-pi||)(pf-pi)
|
|
37
|
+
return self.pi + (s / self.length) * (self.pf - self.pi) if self.length > 0 else self.pi
|
|
38
|
+
|
|
39
|
+
def velocity(self, _s: float | None = None) -> np.ndarray:
|
|
40
|
+
"""
|
|
41
|
+
Calculate first derivative with respect to arc length.
|
|
42
|
+
For linear path, this is constant and doesn't depend on s.
|
|
43
|
+
|
|
44
|
+
Parameters:
|
|
45
|
+
_s (float, optional): Arc length parameter (not used for linear path)
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
numpy.ndarray: Velocity (tangent) vector
|
|
49
|
+
"""
|
|
50
|
+
# Equation 4.35: dp/ds = (pf-pi)/||pf-pi||
|
|
51
|
+
return self.tangent
|
|
52
|
+
|
|
53
|
+
@staticmethod
|
|
54
|
+
def acceleration(_s: float | None = None) -> np.ndarray:
|
|
55
|
+
"""
|
|
56
|
+
Calculate second derivative with respect to arc length.
|
|
57
|
+
For linear path, this is always zero.
|
|
58
|
+
|
|
59
|
+
Parameters:
|
|
60
|
+
_s (float, optional): Arc length parameter (not used for linear path)
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
numpy.ndarray: Acceleration vector (always zero for linear path)
|
|
64
|
+
"""
|
|
65
|
+
# Equation 4.36: d²p/ds² = 0
|
|
66
|
+
return np.zeros(3)
|
|
67
|
+
|
|
68
|
+
def evaluate_at(self, s_values: float | list[float] | np.ndarray) -> dict[str, np.ndarray]:
|
|
69
|
+
"""
|
|
70
|
+
Evaluate position, velocity, and acceleration at specific arc length values.
|
|
71
|
+
|
|
72
|
+
Parameters:
|
|
73
|
+
s_values (float or array-like): Arc length parameter(s)
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
dict: Dictionary containing arrays for position, velocity, and acceleration
|
|
77
|
+
Each array has shape (n, 3) where n is the number of s values
|
|
78
|
+
"""
|
|
79
|
+
# Convert scalar to array if needed
|
|
80
|
+
s_values_arr: np.ndarray = (
|
|
81
|
+
np.array([s_values]) if np.isscalar(s_values) else np.array(s_values)
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
# Clip values to valid range
|
|
85
|
+
s_clipped = np.clip(s_values_arr, 0, self.length)
|
|
86
|
+
|
|
87
|
+
# Initialize result arrays
|
|
88
|
+
n = len(s_clipped)
|
|
89
|
+
positions = np.zeros((n, 3))
|
|
90
|
+
velocities = np.zeros((n, 3))
|
|
91
|
+
accelerations = np.zeros((n, 3))
|
|
92
|
+
|
|
93
|
+
# Calculate positions for each s
|
|
94
|
+
for i, s in enumerate(s_clipped):
|
|
95
|
+
positions[i] = self.position(s)
|
|
96
|
+
|
|
97
|
+
# For linear path, velocity is constant and acceleration is zero
|
|
98
|
+
velocities[:] = self.velocity()
|
|
99
|
+
# accelerations already initialized to zeros
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
"position": positions,
|
|
103
|
+
"velocity": velocities,
|
|
104
|
+
"acceleration": accelerations,
|
|
105
|
+
"s": s_clipped,
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
def all_traj(self, num_points: int = 100) -> dict[str, np.ndarray]:
|
|
109
|
+
"""
|
|
110
|
+
Generate a complete trajectory along the entire linear path.
|
|
111
|
+
|
|
112
|
+
Parameters:
|
|
113
|
+
num_points (int): Number of points to generate along the path
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
dict: Dictionary containing arrays for position, velocity, and acceleration
|
|
117
|
+
Each array has shape (num_points, 3)
|
|
118
|
+
"""
|
|
119
|
+
# Generate evenly spaced points along the entire path
|
|
120
|
+
s_values = np.linspace(0, self.length, num_points)
|
|
121
|
+
|
|
122
|
+
# Use evaluate_at to get the trajectory data
|
|
123
|
+
return self.evaluate_at(s_values)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class CircularPath:
|
|
127
|
+
def __init__(self, r: np.ndarray, d: np.ndarray, pi: np.ndarray) -> None:
|
|
128
|
+
"""
|
|
129
|
+
Initialize a circular path.
|
|
130
|
+
|
|
131
|
+
Parameters:
|
|
132
|
+
r (array-like): Unit vector of circle axis
|
|
133
|
+
d (array-like): Position vector of a point on the circle axis
|
|
134
|
+
pi (array-like): Position vector of a point on the circle
|
|
135
|
+
"""
|
|
136
|
+
self.r: np.ndarray = np.array(r)
|
|
137
|
+
self.d: np.ndarray = np.array(d)
|
|
138
|
+
self.pi: np.ndarray = np.array(pi)
|
|
139
|
+
|
|
140
|
+
# Normalize axis vector
|
|
141
|
+
self.r /= np.linalg.norm(self.r)
|
|
142
|
+
|
|
143
|
+
# Compute delta vector
|
|
144
|
+
delta = self.pi - self.d
|
|
145
|
+
|
|
146
|
+
# Check if pi is not on the axis
|
|
147
|
+
if np.abs(np.dot(delta, self.r)) >= np.linalg.norm(delta):
|
|
148
|
+
raise ValueError("The point pi must not be on the circle axis")
|
|
149
|
+
|
|
150
|
+
# Compute center (equation 4.37)
|
|
151
|
+
self.center = self.d + np.dot(delta, self.r) * self.r
|
|
152
|
+
|
|
153
|
+
# Compute radius
|
|
154
|
+
self.radius = np.linalg.norm(self.pi - self.center)
|
|
155
|
+
|
|
156
|
+
# Compute rotation matrix
|
|
157
|
+
x_prime = (self.pi - self.center) / self.radius # Unit vector in x' direction
|
|
158
|
+
z_prime = self.r # Unit vector in z' direction
|
|
159
|
+
y_prime = np.cross(z_prime, x_prime) # Unit vector in y' direction
|
|
160
|
+
|
|
161
|
+
# Rotation matrix R = [x' y' z']
|
|
162
|
+
self.R = np.column_stack((x_prime, y_prime, z_prime))
|
|
163
|
+
|
|
164
|
+
def position(self, s: float | np.ndarray) -> np.ndarray:
|
|
165
|
+
"""
|
|
166
|
+
Calculate position at arc length s.
|
|
167
|
+
|
|
168
|
+
Parameters:
|
|
169
|
+
s (float or array): Arc length parameter(s)
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
numpy.ndarray: Position vector(s)
|
|
173
|
+
"""
|
|
174
|
+
if np.isscalar(s):
|
|
175
|
+
# Position in local coordinate system (equation 4.38)
|
|
176
|
+
p_prime = np.array(
|
|
177
|
+
[self.radius * np.cos(s / self.radius), self.radius * np.sin(s / self.radius), 0]
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
# Position in global coordinate system (equation 4.39)
|
|
181
|
+
return self.center + self.R @ p_prime
|
|
182
|
+
|
|
183
|
+
# Ensure s is treated as an array/iterable
|
|
184
|
+
s_array = np.asarray(s)
|
|
185
|
+
positions = []
|
|
186
|
+
for s_val in s_array:
|
|
187
|
+
p_prime = np.array(
|
|
188
|
+
[
|
|
189
|
+
self.radius * np.cos(s_val / self.radius),
|
|
190
|
+
self.radius * np.sin(s_val / self.radius),
|
|
191
|
+
0,
|
|
192
|
+
]
|
|
193
|
+
)
|
|
194
|
+
positions.append(self.center + self.R @ p_prime)
|
|
195
|
+
return np.array(positions)
|
|
196
|
+
|
|
197
|
+
def velocity(self, s: float) -> np.ndarray:
|
|
198
|
+
"""
|
|
199
|
+
Calculate first derivative with respect to arc length.
|
|
200
|
+
|
|
201
|
+
Parameters:
|
|
202
|
+
s (float): Arc length parameter
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
numpy.ndarray: Velocity (tangent) vector
|
|
206
|
+
"""
|
|
207
|
+
# Velocity in local coordinate system (equation 4.40)
|
|
208
|
+
dp_prime_ds = np.array([-np.sin(s / self.radius), np.cos(s / self.radius), 0])
|
|
209
|
+
|
|
210
|
+
# Velocity in global coordinate system
|
|
211
|
+
return self.R @ dp_prime_ds
|
|
212
|
+
|
|
213
|
+
def acceleration(self, s: float) -> np.ndarray:
|
|
214
|
+
"""
|
|
215
|
+
Calculate second derivative with respect to arc length.
|
|
216
|
+
|
|
217
|
+
Parameters:
|
|
218
|
+
s (float): Arc length parameter
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
numpy.ndarray: Acceleration vector
|
|
222
|
+
"""
|
|
223
|
+
# Acceleration in local coordinate system (equation 4.41)
|
|
224
|
+
d2p_prime_ds2 = np.array(
|
|
225
|
+
[-np.cos(s / self.radius) / self.radius, -np.sin(s / self.radius) / self.radius, 0]
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
# Acceleration in global coordinate system
|
|
229
|
+
return self.R @ d2p_prime_ds2
|
|
230
|
+
|
|
231
|
+
def evaluate_at(self, s_values: float | list[float] | np.ndarray) -> dict[str, np.ndarray]:
|
|
232
|
+
"""
|
|
233
|
+
Evaluate position, velocity, and acceleration at specific arc length values.
|
|
234
|
+
|
|
235
|
+
Parameters:
|
|
236
|
+
s_values (float or array-like): Arc length parameter(s)
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
dict: Dictionary containing arrays for position, velocity, and acceleration
|
|
240
|
+
Each array has shape (n, 3) where n is the number of s values
|
|
241
|
+
"""
|
|
242
|
+
# Convert scalar to array if needed
|
|
243
|
+
s_values_arr: np.ndarray = (
|
|
244
|
+
np.array([s_values]) if np.isscalar(s_values) else np.array(s_values)
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
# Initialize result arrays
|
|
248
|
+
n = len(s_values_arr)
|
|
249
|
+
positions = np.zeros((n, 3))
|
|
250
|
+
velocities = np.zeros((n, 3))
|
|
251
|
+
accelerations = np.zeros((n, 3))
|
|
252
|
+
|
|
253
|
+
# Calculate values for each s
|
|
254
|
+
for i, s in enumerate(s_values_arr):
|
|
255
|
+
positions[i] = self.position(s)
|
|
256
|
+
velocities[i] = self.velocity(s)
|
|
257
|
+
accelerations[i] = self.acceleration(s)
|
|
258
|
+
|
|
259
|
+
return {
|
|
260
|
+
"position": positions,
|
|
261
|
+
"velocity": velocities,
|
|
262
|
+
"acceleration": accelerations,
|
|
263
|
+
"s": s_values_arr,
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
def all_traj(self, num_points: int = 100) -> dict[str, np.ndarray]:
|
|
267
|
+
"""
|
|
268
|
+
Generate a complete trajectory around the entire circular path.
|
|
269
|
+
|
|
270
|
+
Parameters:
|
|
271
|
+
num_points (int): Number of points to generate around the circle
|
|
272
|
+
|
|
273
|
+
Returns:
|
|
274
|
+
dict: Dictionary containing arrays for position, velocity, and acceleration
|
|
275
|
+
Each array has shape (num_points, 3)
|
|
276
|
+
"""
|
|
277
|
+
# Generate evenly spaced points for a complete circle
|
|
278
|
+
s_values = np.linspace(0, 2 * np.pi * self.radius, num_points)
|
|
279
|
+
|
|
280
|
+
# Use evaluate_at to get the trajectory data
|
|
281
|
+
return self.evaluate_at(s_values)
|