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,444 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from interpolatepy.b_spline import BSpline
|
|
4
|
+
from interpolatepy.tridiagonal_inv import solve_tridiagonal
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class CubicBSplineInterpolation(BSpline):
|
|
8
|
+
"""
|
|
9
|
+
A class for cubic B-spline interpolation of a set of points.
|
|
10
|
+
|
|
11
|
+
This class implements a global interpolation algorithm for creating a cubic
|
|
12
|
+
B-spline curve that passes through all specified points with C² continuity.
|
|
13
|
+
It inherits from the BSpline class and extends it with interpolation capabilities.
|
|
14
|
+
|
|
15
|
+
Parameters
|
|
16
|
+
----------
|
|
17
|
+
points : array_like
|
|
18
|
+
The points to interpolate. Should be a 2D array of shape (n, d) where n is
|
|
19
|
+
the number of points and d is the dimension, or a 1D array for single-dimensional points.
|
|
20
|
+
v0 : array_like, optional
|
|
21
|
+
Initial endpoint derivative vector. If None and auto_derivatives is True,
|
|
22
|
+
it will be calculated as (q₁-q₀)/(ū₁-ū₀). If None and auto_derivatives
|
|
23
|
+
is False, zero derivative is used. Default is None.
|
|
24
|
+
vn : array_like, optional
|
|
25
|
+
Final endpoint derivative vector. If None and auto_derivatives is True,
|
|
26
|
+
it will be calculated as (qₙ-qₙ₋₁)/(ūₙ-ūₙ₋₁). If None and auto_derivatives
|
|
27
|
+
is False, zero derivative is used. Default is None.
|
|
28
|
+
method : {'equally_spaced', 'chord_length', 'centripetal'}, optional
|
|
29
|
+
Method for calculating the parameters ūₖ. Default is 'chord_length'.
|
|
30
|
+
auto_derivatives : bool, optional
|
|
31
|
+
Whether to automatically calculate derivatives when not provided.
|
|
32
|
+
Default is False.
|
|
33
|
+
|
|
34
|
+
Attributes
|
|
35
|
+
----------
|
|
36
|
+
interpolation_points : ndarray
|
|
37
|
+
The points used for interpolation.
|
|
38
|
+
n_interpolation_points : int
|
|
39
|
+
The number of interpolation points.
|
|
40
|
+
u_bars : ndarray
|
|
41
|
+
The parameters ūₖ calculated using the specified method.
|
|
42
|
+
v0 : ndarray
|
|
43
|
+
The initial endpoint derivative vector.
|
|
44
|
+
vn : ndarray
|
|
45
|
+
The final endpoint derivative vector.
|
|
46
|
+
|
|
47
|
+
Notes
|
|
48
|
+
-----
|
|
49
|
+
The implementation follows the global interpolation algorithm described in
|
|
50
|
+
Section 8.4.2 of "The NURBS Book" by Piegl and Tiller.
|
|
51
|
+
|
|
52
|
+
See Also
|
|
53
|
+
--------
|
|
54
|
+
BSpline : Parent class for basic B-spline functionality.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
# Define constants
|
|
58
|
+
PARAM_DIFF_THRESHOLD = 1e-10
|
|
59
|
+
MIN_POINTS_FOR_TRIDIAGONAL = 2
|
|
60
|
+
|
|
61
|
+
def __init__(
|
|
62
|
+
self,
|
|
63
|
+
points: list | np.ndarray,
|
|
64
|
+
v0: list | np.ndarray | None = None,
|
|
65
|
+
vn: list | np.ndarray | None = None,
|
|
66
|
+
method: str = "chord_length",
|
|
67
|
+
auto_derivatives: bool = False,
|
|
68
|
+
) -> None:
|
|
69
|
+
"""
|
|
70
|
+
Initialize a cubic B-spline interpolation of a set of points.
|
|
71
|
+
|
|
72
|
+
Parameters
|
|
73
|
+
----------
|
|
74
|
+
points : array_like
|
|
75
|
+
The points to interpolate. Should be a 2D array of shape (n, d) where n is
|
|
76
|
+
the number of points and d is the dimension, or a 1D array for single-dimensional points
|
|
77
|
+
v0 : array_like, optional
|
|
78
|
+
Initial endpoint derivative vector. If None and auto_derivatives is True,
|
|
79
|
+
it will be calculated as (q₁-q₀)/(ū₁-ū₀). If None and auto_derivatives
|
|
80
|
+
is False, zero derivative is used. Default is None.
|
|
81
|
+
vn : array_like, optional
|
|
82
|
+
Final endpoint derivative vector. If None and auto_derivatives is True,
|
|
83
|
+
it will be calculated as (qₙ-qₙ₋₁)/(ūₙ-ūₙ₋₁). If None and auto_derivatives
|
|
84
|
+
is False, zero derivative is used. Default is None.
|
|
85
|
+
method : {'equally_spaced', 'chord_length', 'centripetal'}, optional
|
|
86
|
+
Method for calculating the parameters ūₖ. Default is 'chord_length'.
|
|
87
|
+
auto_derivatives : bool, optional
|
|
88
|
+
Whether to automatically calculate derivatives when not provided.
|
|
89
|
+
Default is False.
|
|
90
|
+
|
|
91
|
+
Returns
|
|
92
|
+
-------
|
|
93
|
+
None
|
|
94
|
+
|
|
95
|
+
Notes
|
|
96
|
+
-----
|
|
97
|
+
This initializer preprocesses the input points and endpoint derivatives,
|
|
98
|
+
calculates the parameters ūₖ, the knot vector, and the control points,
|
|
99
|
+
then initializes the parent BSpline class with the computed values.
|
|
100
|
+
"""
|
|
101
|
+
# Convert points to numpy array and ensure correct format
|
|
102
|
+
if not isinstance(points, np.ndarray):
|
|
103
|
+
points = np.array(points, dtype=np.float64)
|
|
104
|
+
else:
|
|
105
|
+
points = points.astype(np.float64)
|
|
106
|
+
|
|
107
|
+
# Get the number of points and the dimension
|
|
108
|
+
n_points = len(points)
|
|
109
|
+
if points.ndim == 1:
|
|
110
|
+
dimension = 1
|
|
111
|
+
# Reshape for a single point
|
|
112
|
+
points = points.reshape(-1, 1)
|
|
113
|
+
else:
|
|
114
|
+
dimension = points.shape[1]
|
|
115
|
+
|
|
116
|
+
# Store the interpolation-specific properties
|
|
117
|
+
self.interpolation_points = points
|
|
118
|
+
self.n_interpolation_points = n_points
|
|
119
|
+
|
|
120
|
+
# Calculate the parameters ūₖ
|
|
121
|
+
self.u_bars = self._calculate_parameters(method)
|
|
122
|
+
|
|
123
|
+
# Process endpoint derivatives
|
|
124
|
+
n = self.n_interpolation_points - 1 # Index of the last point
|
|
125
|
+
|
|
126
|
+
# If v0 is None, calculate it or set to zero vector
|
|
127
|
+
if v0 is None:
|
|
128
|
+
if auto_derivatives and n > 0:
|
|
129
|
+
# Calculate v0 = (q₁ - q₀) / (ū₁ - ū₀)
|
|
130
|
+
u_diff = self.u_bars[1] - self.u_bars[0]
|
|
131
|
+
if abs(u_diff) > self.PARAM_DIFF_THRESHOLD: # Avoid division by zero
|
|
132
|
+
self.v0 = (points[1] - points[0]) / u_diff
|
|
133
|
+
else:
|
|
134
|
+
self.v0 = np.zeros(dimension, dtype=np.float64)
|
|
135
|
+
else:
|
|
136
|
+
self.v0 = np.zeros(dimension, dtype=np.float64)
|
|
137
|
+
else:
|
|
138
|
+
# Convert to numpy array if it's not already
|
|
139
|
+
if not isinstance(v0, np.ndarray):
|
|
140
|
+
v0 = np.array(v0, dtype=np.float64)
|
|
141
|
+
else:
|
|
142
|
+
v0 = v0.astype(np.float64)
|
|
143
|
+
|
|
144
|
+
# Ensure correct shape
|
|
145
|
+
if v0.ndim == 0: # scalar
|
|
146
|
+
self.v0 = np.zeros(dimension, dtype=np.float64)
|
|
147
|
+
elif v0.ndim == 1 and len(v0) == dimension:
|
|
148
|
+
self.v0 = v0
|
|
149
|
+
else:
|
|
150
|
+
raise ValueError(f"v0 must be a vector of dimension {dimension}")
|
|
151
|
+
|
|
152
|
+
# If vn is None, calculate it or set to zero vector
|
|
153
|
+
if vn is None:
|
|
154
|
+
if auto_derivatives and n > 0:
|
|
155
|
+
# Calculate vn = (qₙ - qₙ₋₁) / (ūₙ - ūₙ₋₁)
|
|
156
|
+
u_diff = self.u_bars[n] - self.u_bars[n - 1]
|
|
157
|
+
if abs(u_diff) > self.PARAM_DIFF_THRESHOLD: # Avoid division by zero
|
|
158
|
+
self.vn = (points[n] - points[n - 1]) / u_diff
|
|
159
|
+
else:
|
|
160
|
+
self.vn = np.zeros(dimension, dtype=np.float64)
|
|
161
|
+
else:
|
|
162
|
+
self.vn = np.zeros(dimension, dtype=np.float64)
|
|
163
|
+
else:
|
|
164
|
+
# Convert to numpy array if it's not already
|
|
165
|
+
if not isinstance(vn, np.ndarray):
|
|
166
|
+
vn = np.array(vn, dtype=np.float64)
|
|
167
|
+
else:
|
|
168
|
+
vn = vn.astype(np.float64)
|
|
169
|
+
|
|
170
|
+
# Ensure correct shape
|
|
171
|
+
if vn.ndim == 0: # scalar
|
|
172
|
+
self.vn = np.zeros(dimension, dtype=np.float64)
|
|
173
|
+
elif vn.ndim == 1 and len(vn) == dimension:
|
|
174
|
+
self.vn = vn
|
|
175
|
+
else:
|
|
176
|
+
raise ValueError(f"vn must be a vector of dimension {dimension}")
|
|
177
|
+
|
|
178
|
+
# Calculate the knot vector
|
|
179
|
+
knots = self._calculate_knot_vector()
|
|
180
|
+
|
|
181
|
+
# Calculate the control points
|
|
182
|
+
control_points = self._calculate_control_points(knots)
|
|
183
|
+
|
|
184
|
+
# Initialize the parent BSpline with cubic degree (3)
|
|
185
|
+
super().__init__(3, knots, control_points)
|
|
186
|
+
|
|
187
|
+
# The rest of the class implementation remains unchanged
|
|
188
|
+
def _calculate_parameters(self, method: str) -> np.ndarray:
|
|
189
|
+
"""
|
|
190
|
+
Calculate the parameters ūₖ for each point.
|
|
191
|
+
|
|
192
|
+
Parameters
|
|
193
|
+
----------
|
|
194
|
+
method : {'equally_spaced', 'chord_length', 'centripetal'}
|
|
195
|
+
Method for calculating the parameters:
|
|
196
|
+
- 'equally_spaced': Parameters are evenly spaced between 0 and 1.
|
|
197
|
+
- 'chord_length': Parameters are proportional to the cumulative chord length.
|
|
198
|
+
- 'centripetal': Parameters are proportional to the cumulative chord length
|
|
199
|
+
raised to the power of mu (0.5).
|
|
200
|
+
|
|
201
|
+
Returns
|
|
202
|
+
-------
|
|
203
|
+
ndarray
|
|
204
|
+
The parameters ūₖ with shape (n,) where n is the number of interpolation points.
|
|
205
|
+
|
|
206
|
+
Notes
|
|
207
|
+
-----
|
|
208
|
+
The endpoints are always set to ū₀ = 0 and ūₙ = 1.
|
|
209
|
+
|
|
210
|
+
For 'chord_length' method, the parameter spacing is proportional to the distance
|
|
211
|
+
between interpolation points.
|
|
212
|
+
|
|
213
|
+
For 'centripetal' method, a value of mu = 0.5 is used as recommended in the literature
|
|
214
|
+
for better shape preservation with non-uniform data.
|
|
215
|
+
|
|
216
|
+
Raises
|
|
217
|
+
------
|
|
218
|
+
ValueError
|
|
219
|
+
If an unknown method is provided.
|
|
220
|
+
"""
|
|
221
|
+
n = self.n_interpolation_points - 1 # Index of the last point
|
|
222
|
+
|
|
223
|
+
# Initialize the parameters
|
|
224
|
+
u_bars = np.zeros(self.n_interpolation_points, dtype=np.float64)
|
|
225
|
+
|
|
226
|
+
# Set the endpoints (equation 8.12)
|
|
227
|
+
u_bars[0] = 0.0
|
|
228
|
+
u_bars[n] = 1.0
|
|
229
|
+
|
|
230
|
+
if method == "equally_spaced":
|
|
231
|
+
# Equally spaced parameters (equation 8.12)
|
|
232
|
+
for k in range(1, n):
|
|
233
|
+
u_bars[k] = k / n
|
|
234
|
+
|
|
235
|
+
elif method == "chord_length":
|
|
236
|
+
# Chord length distribution (equation 8.13)
|
|
237
|
+
# Calculate total chord length
|
|
238
|
+
total_length = 0.0
|
|
239
|
+
for k in range(1, n + 1):
|
|
240
|
+
total_length += np.linalg.norm(
|
|
241
|
+
self.interpolation_points[k] - self.interpolation_points[k - 1]
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
# Calculate parameters
|
|
245
|
+
for k in range(1, n):
|
|
246
|
+
u_bars[k] = (
|
|
247
|
+
u_bars[k - 1]
|
|
248
|
+
+ np.linalg.norm(
|
|
249
|
+
self.interpolation_points[k] - self.interpolation_points[k - 1]
|
|
250
|
+
)
|
|
251
|
+
/ total_length
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
elif method == "centripetal":
|
|
255
|
+
# Centripetal distribution (equation 8.14)
|
|
256
|
+
mu = 0.5 # As recommended in the document
|
|
257
|
+
|
|
258
|
+
# Calculate total "centripetal" length
|
|
259
|
+
total_length = 0.0
|
|
260
|
+
for k in range(1, n + 1):
|
|
261
|
+
total_length += (
|
|
262
|
+
np.linalg.norm(self.interpolation_points[k] - self.interpolation_points[k - 1])
|
|
263
|
+
** mu
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
# Calculate parameters
|
|
267
|
+
for k in range(1, n):
|
|
268
|
+
u_bars[k] = (
|
|
269
|
+
u_bars[k - 1]
|
|
270
|
+
+ np.linalg.norm(
|
|
271
|
+
self.interpolation_points[k] - self.interpolation_points[k - 1]
|
|
272
|
+
)
|
|
273
|
+
** mu
|
|
274
|
+
/ total_length
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
else:
|
|
278
|
+
raise ValueError(
|
|
279
|
+
f"Unknown method: {method}. Options are 'equally_spaced', 'chord_length', "
|
|
280
|
+
f"or 'centripetal'."
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
return u_bars
|
|
284
|
+
|
|
285
|
+
def _calculate_knot_vector(self) -> np.ndarray:
|
|
286
|
+
"""
|
|
287
|
+
Calculate the knot vector based on the parameters ūₖ.
|
|
288
|
+
|
|
289
|
+
Returns
|
|
290
|
+
-------
|
|
291
|
+
ndarray
|
|
292
|
+
The knot vector with shape (n+7,) where n is the number of interpolation
|
|
293
|
+
points minus 1. The first 3 knots are equal to ū₀, the last 3 knots
|
|
294
|
+
are equal to ūₙ, and the middle knots are set to the parameters ūⱼ for
|
|
295
|
+
j = 0, ..., n.
|
|
296
|
+
|
|
297
|
+
Notes
|
|
298
|
+
-----
|
|
299
|
+
This follows equation (8.15) from "The NURBS Book":
|
|
300
|
+
- t₀ = t₁ = t₂ = ū₀
|
|
301
|
+
- tⱼ₊₃ = ūⱼ for j = 0,...,n
|
|
302
|
+
- tₙ₊₄ = tₙ₊₅ = tₙ₊₆ = ūₙ
|
|
303
|
+
|
|
304
|
+
This knot vector ensures that the cubic B-spline curve passes through the
|
|
305
|
+
interpolation points.
|
|
306
|
+
"""
|
|
307
|
+
n = self.n_interpolation_points - 1 # Index of the last point
|
|
308
|
+
|
|
309
|
+
# Create the knot vector with n+7 elements (as per equation 8.15)
|
|
310
|
+
knots = np.zeros(n + 7, dtype=np.float64)
|
|
311
|
+
|
|
312
|
+
# Set the first 3 knots to ū₀ (equation 8.15)
|
|
313
|
+
knots[0:3] = self.u_bars[0]
|
|
314
|
+
|
|
315
|
+
# Set the last 3 knots to ūₙ (equation 8.15)
|
|
316
|
+
knots[-3:] = self.u_bars[n]
|
|
317
|
+
|
|
318
|
+
# Set the middle knots to ūⱼ for j = 0, ..., n (equation 8.15)
|
|
319
|
+
for j in range(n + 1):
|
|
320
|
+
knots[j + 3] = self.u_bars[j]
|
|
321
|
+
|
|
322
|
+
return knots
|
|
323
|
+
|
|
324
|
+
def _calculate_control_points(self, knots: np.ndarray) -> np.ndarray:
|
|
325
|
+
"""
|
|
326
|
+
Calculate the control points by solving a system of equations.
|
|
327
|
+
|
|
328
|
+
Parameters
|
|
329
|
+
----------
|
|
330
|
+
knots : ndarray
|
|
331
|
+
The knot vector with shape (n+7,) where n is the number of interpolation
|
|
332
|
+
points minus 1.
|
|
333
|
+
|
|
334
|
+
Returns
|
|
335
|
+
-------
|
|
336
|
+
ndarray
|
|
337
|
+
The control points with shape (n+3, d) where n is the number of
|
|
338
|
+
interpolation points minus 1 and d is the dimension.
|
|
339
|
+
|
|
340
|
+
Notes
|
|
341
|
+
-----
|
|
342
|
+
This follows the algorithm from "The NURBS Book" section 8.4.2:
|
|
343
|
+
|
|
344
|
+
First, the first and last two control points are calculated directly:
|
|
345
|
+
- p₀ = q₀ (first interpolation point)
|
|
346
|
+
- p₁ = q₀ + (t₄/3) * v₀ (using first derivative)
|
|
347
|
+
- pₙ₊₁ = qₙ - ((1-tₙ₊₂)/3) * vₙ (using last derivative)
|
|
348
|
+
- pₙ₊₂ = qₙ (last interpolation point)
|
|
349
|
+
|
|
350
|
+
Then, for the remaining control points p₂, p₃, ..., pₙ, a tridiagonal system
|
|
351
|
+
is solved based on the requirement that the curve passes through all
|
|
352
|
+
interpolation points.
|
|
353
|
+
|
|
354
|
+
If there are only two interpolation points, only the directly calculated
|
|
355
|
+
control points are needed.
|
|
356
|
+
"""
|
|
357
|
+
n = self.n_interpolation_points - 1 # Index of the last point
|
|
358
|
+
dimension = self.interpolation_points.shape[1]
|
|
359
|
+
|
|
360
|
+
# Initialize the control points array with n+3 elements (p₀ to pₙ₊₂)
|
|
361
|
+
control_points = np.zeros((n + 3, dimension), dtype=np.float64)
|
|
362
|
+
|
|
363
|
+
# Calculate p₀, p₁, pₙ₊₁, and pₙ₊₂ directly from equation (8.16)
|
|
364
|
+
control_points[0] = self.interpolation_points[0]
|
|
365
|
+
|
|
366
|
+
# Calculate p₁ using v0 (scaled by the knot spacing)
|
|
367
|
+
control_points[1] = self.interpolation_points[0] + (knots[4] / 3.0) * self.v0
|
|
368
|
+
|
|
369
|
+
# Calculate pₙ₊₁ using vn (scaled by the knot spacing)
|
|
370
|
+
control_points[n + 1] = (
|
|
371
|
+
self.interpolation_points[n] - ((1.0 - knots[n + 2]) / 3.0) * self.vn
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
control_points[n + 2] = self.interpolation_points[n]
|
|
375
|
+
|
|
376
|
+
# If there are only two points to interpolate, we're done
|
|
377
|
+
if n < self.MIN_POINTS_FOR_TRIDIAGONAL:
|
|
378
|
+
return control_points
|
|
379
|
+
|
|
380
|
+
# For more than two points, solve the tridiagonal system for the remaining control points
|
|
381
|
+
lower_diagonal = np.zeros(n - 1, dtype=np.float64)
|
|
382
|
+
main_diagonal = np.zeros(n - 1, dtype=np.float64)
|
|
383
|
+
upper_diagonal = np.zeros(n - 1, dtype=np.float64)
|
|
384
|
+
right_hand_side = np.zeros((n - 1, dimension), dtype=np.float64)
|
|
385
|
+
|
|
386
|
+
# Create a temporary BSpline for calculating basis functions
|
|
387
|
+
temp_control = np.zeros((n + 3, dimension), dtype=np.float64)
|
|
388
|
+
temp_bs = BSpline(3, knots, temp_control)
|
|
389
|
+
|
|
390
|
+
# Fill the tridiagonal matrix and right-hand side
|
|
391
|
+
for i in range(n - 1):
|
|
392
|
+
k = i + 1 # Point index (from 1 to n-1)
|
|
393
|
+
|
|
394
|
+
# The parameter ūₖ
|
|
395
|
+
u_bar = self.u_bars[k]
|
|
396
|
+
|
|
397
|
+
# Find the knot span for ūₖ
|
|
398
|
+
span = temp_bs.find_knot_span(u_bar)
|
|
399
|
+
|
|
400
|
+
# Calculate the basis functions at ūₖ
|
|
401
|
+
basis_vals = temp_bs.basis_functions(u_bar, span)
|
|
402
|
+
|
|
403
|
+
# The basis function values we need
|
|
404
|
+
b3_k = basis_vals[0]
|
|
405
|
+
b3_k1 = basis_vals[1]
|
|
406
|
+
b3_k2 = basis_vals[2]
|
|
407
|
+
|
|
408
|
+
# Fill the tridiagonal matrix
|
|
409
|
+
if k == 1:
|
|
410
|
+
# First row
|
|
411
|
+
main_diagonal[0] = b3_k1
|
|
412
|
+
upper_diagonal[0] = b3_k2
|
|
413
|
+
right_hand_side[0] = self.interpolation_points[k] - b3_k * control_points[1]
|
|
414
|
+
elif k == n - 1:
|
|
415
|
+
# Last row
|
|
416
|
+
lower_diagonal[k - 2] = b3_k
|
|
417
|
+
main_diagonal[k - 1] = b3_k1
|
|
418
|
+
right_hand_side[k - 1] = (
|
|
419
|
+
self.interpolation_points[k] - b3_k2 * control_points[n + 1]
|
|
420
|
+
)
|
|
421
|
+
else:
|
|
422
|
+
# Middle rows
|
|
423
|
+
lower_diagonal[k - 2] = b3_k
|
|
424
|
+
main_diagonal[k - 1] = b3_k1
|
|
425
|
+
upper_diagonal[k - 1] = b3_k2
|
|
426
|
+
right_hand_side[k - 1] = self.interpolation_points[k]
|
|
427
|
+
|
|
428
|
+
# Solve the tridiagonal system for each dimension
|
|
429
|
+
for d in range(dimension):
|
|
430
|
+
# Extract the right-hand side for this dimension
|
|
431
|
+
rhs = right_hand_side[:, d]
|
|
432
|
+
|
|
433
|
+
# Adjust the lower_diagonal for the tridiagonal solver
|
|
434
|
+
l_diag = np.zeros(n - 1, dtype=np.float64)
|
|
435
|
+
if n - 1 > 1:
|
|
436
|
+
l_diag[1:] = lower_diagonal[:-1]
|
|
437
|
+
|
|
438
|
+
# Solve the system
|
|
439
|
+
solution = solve_tridiagonal(l_diag, main_diagonal, upper_diagonal, rhs)
|
|
440
|
+
|
|
441
|
+
# Set the control points
|
|
442
|
+
control_points[2 : n + 1, d] = solution
|
|
443
|
+
|
|
444
|
+
return control_points
|