bspy 4.0__py3-none-any.whl → 4.2__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.
bspy/spline_block.py ADDED
@@ -0,0 +1,343 @@
1
+ import numpy as np
2
+ import bspy.spline
3
+ import bspy._spline_domain
4
+ import bspy._spline_evaluation
5
+ import bspy._spline_intersection
6
+ import bspy._spline_fitting
7
+ import bspy._spline_operations
8
+
9
+ class SplineBlock:
10
+ """
11
+ A class to represent and process an array-like collection of splines.
12
+ Spline blocks are useful for efficiently manipulating and solving systems of equations with splines.
13
+
14
+ Parameters
15
+ ----------
16
+ block : an array-like collection of splines (may be a list of lists)
17
+ Splines in the same row are treated as if they are added together.
18
+ Each row need not have the same number of splines, but all splines in a row must have the same
19
+ number of dependent variables (same nDep). Corresponding independent variables must have the same domain.
20
+
21
+ For example, if F is a spline with nInd = 3 and nDep = 2, G is a spline with nInd = 1 and nDep = 2,
22
+ and h is a spline with nInd = 2 and nDep = 1, then [[F, G], [h]] is a valid block (total nDep is 3
23
+ and max nInd is 4).
24
+ """
25
+
26
+ def _block_evaluation(self, returnShape, splineFunction, args):
27
+ value = np.zeros(returnShape, self.coefsDtype)
28
+ nDep = 0
29
+ for row in self.block:
30
+ nInd = 0
31
+ for spline in row:
32
+ value[nDep:nDep + spline.nDep] += splineFunction(spline, *[arg[nInd:nInd + spline.nInd] for arg in args])
33
+ nInd += spline.nInd
34
+ nDep += spline.nDep
35
+ return value
36
+
37
+ def _block_operation(self, splineFunction, args):
38
+ newBlock = []
39
+ for row in self.block:
40
+ newRow = []
41
+ nInd = 0
42
+ for spline in row:
43
+ newRow.append(splineFunction(spline, *[arg[nInd:nInd + spline.nInd] for arg in args]))
44
+ nInd += spline.nInd
45
+ newBlock.append(newRow)
46
+ return SplineBlock(newBlock)
47
+
48
+ def __init__(self, block):
49
+ if isinstance(block, bspy.spline.Spline):
50
+ self.block = [[block]]
51
+ elif isinstance(block[0], bspy.spline.Spline):
52
+ self.block = [block]
53
+ else:
54
+ self.block = block
55
+ self.nInd = 0
56
+ self.nDep = 0
57
+ self.knotsDtype = None
58
+ self.coefsDtype = None
59
+ self.size = 0
60
+ self._domain = []
61
+ for row in self.block:
62
+ rowInd = 0
63
+ rowDep = 0
64
+ for spline in row:
65
+ if rowDep == 0:
66
+ rowDep = spline.nDep
67
+ if self.nInd == 0:
68
+ self.knotsDtype = spline.knots[0].dtype
69
+ self.coefsDtype = spline.coefs.dtype
70
+ elif rowDep != spline.nDep:
71
+ raise ValueError("All splines in the same row must have the same nDep")
72
+ d = spline.domain()
73
+ for i in range(rowInd, rowInd + spline.nInd):
74
+ ind = i - rowInd
75
+ if i < self.nInd:
76
+ if self._domain[i][0] != d[ind, 0] or self._domain[i][1] != d[ind, 1]:
77
+ raise ValueError("Domains of independent variables must match")
78
+ else:
79
+ self._domain.append(d[ind])
80
+ rowInd += spline.nInd
81
+ self.nInd = max(self.nInd, rowInd)
82
+ self.nDep += rowDep
83
+ self.size += len(row)
84
+ self._domain = np.array(self._domain, self.knotsDtype)
85
+
86
+ def __call__(self, uvw):
87
+ return self.evaluate(uvw)
88
+
89
+ def __repr__(self):
90
+ return f"SplineBlock({self.block})"
91
+
92
+ def contours(self):
93
+ """
94
+ Find all the contour curves of a block of splines whose `nInd` is one larger than its `nDep`.
95
+
96
+ Returns
97
+ -------
98
+ curves : `iterable`
99
+ A collection of `Spline` curves, `u(t)`, each of whose domain is [0, 1], whose range is
100
+ in the parameter space of the given spline, and which satisfy `self(u(t)) = 0`.
101
+
102
+ See Also
103
+ --------
104
+ `zeros` : Find the roots of a block of splines (nInd must match nDep).
105
+
106
+ Notes
107
+ -----
108
+ Uses `zeros` to find all intersection points and `Spline.contour` to find individual intersection curves.
109
+ The algorithm used to to find all intersection curves is from Grandine, Thomas A., and Frederick W. Klein IV.
110
+ "A new approach to the surface intersection problem." Computer Aided Geometric Design 14, no. 2 (1997): 111-134.
111
+ """
112
+ return bspy._spline_intersection.contours(self)
113
+
114
+ def contract(self, uvw):
115
+ """
116
+ Contract a spline block by assigning a fixed value to one or more of its independent variables.
117
+
118
+ Parameters
119
+ ----------
120
+ uvw : `iterable`
121
+ An iterable of length `nInd` that specifies the values of each independent variable to contract.
122
+ A value of `None` for an independent variable indicates that variable should remain unchanged.
123
+
124
+ Returns
125
+ -------
126
+ block : `SplineBlock`
127
+ The contracted spline block.
128
+ """
129
+ return self._block_operation(bspy._spline_operations.contract, (uvw,))
130
+
131
+ def derivative(self, with_respect_to, uvw):
132
+ """
133
+ Compute the derivative of the spline block at given parameter values.
134
+
135
+ Parameters
136
+ ----------
137
+ with_respect_to : `iterable`
138
+ An iterable of length `nInd` that specifies the integer order of derivative for each independent variable.
139
+ A zero-order derivative just evaluates the spline normally.
140
+
141
+ uvw : `iterable`
142
+ An iterable of length `nInd` that specifies the values of each independent variable (the parameter values).
143
+
144
+ Returns
145
+ -------
146
+ value : `numpy.array`
147
+ The value of the derivative of the spline block at the given parameter values (array of size nDep).
148
+ """
149
+ return self._block_evaluation(self.nDep, bspy._spline_evaluation.derivative, (with_respect_to, uvw))
150
+
151
+ def domain(self):
152
+ """
153
+ Return the domain of a spline block.
154
+
155
+ Returns
156
+ -------
157
+ bounds : `numpy.array`
158
+ nInd x 2 array of the lower and upper bounds on each of the independent variables.
159
+ """
160
+ return self._domain
161
+
162
+ def evaluate(self, uvw):
163
+ """
164
+ Compute the value of the spline block at given parameter values.
165
+
166
+ Parameters
167
+ ----------
168
+ uvw : `iterable`
169
+ An iterable of length `nInd` that specifies the values of each independent variable (the parameter values).
170
+
171
+ Returns
172
+ -------
173
+ value : `numpy.array`
174
+ The value of the spline block at the given parameter values (array of size nDep).
175
+ """
176
+ return self._block_evaluation(self.nDep, bspy._spline_evaluation.evaluate, (uvw,))
177
+
178
+ def jacobian(self, uvw):
179
+ """
180
+ Compute the value of the spline block's Jacobian at given parameter values.
181
+
182
+ Parameters
183
+ ----------
184
+ uvw : `iterable`
185
+ An iterable of length `nInd` that specifies the values of each independent variable (the parameter values).
186
+
187
+ Returns
188
+ -------
189
+ value : `numpy.array`
190
+ The value of the spline block's Jacobian at the given parameter values. The shape of the return value is (nDep, nInd).
191
+ """
192
+ jacobian = np.zeros((self.nDep, self.nInd), self.coefsDtype)
193
+ nDep = 0
194
+ for row in self.block:
195
+ nInd = 0
196
+ for spline in row:
197
+ jacobian[nDep:nDep + spline.nDep, nInd:nInd + spline.nInd] += spline.jacobian(uvw[nInd:nInd + spline.nInd])
198
+ nInd += spline.nInd
199
+ nDep += spline.nDep
200
+ return jacobian
201
+
202
+ def normal(self, uvw, normalize=True, indices=None):
203
+ """
204
+ Compute the normal of the spline block at given parameter values. The number of independent variables must be
205
+ one different than the number of dependent variables.
206
+
207
+ Parameters
208
+ ----------
209
+ uvw : `iterable`
210
+ An iterable of length `nInd` that specifies the values of each independent variable (the parameter values).
211
+
212
+ normalize : `boolean`, optional
213
+ If True the returned normal will have unit length (the default). Otherwise, the normal's length will
214
+ be the area of the tangent space (for two independent variables, its the length of the cross product of tangent vectors).
215
+
216
+ indices : `iterable`, optional
217
+ An iterable of normal indices to calculate. For example, `indices=(0, 3)` will return a vector of length 2
218
+ with the first and fourth values of the normal. If `None`, all normal values are returned (the default).
219
+
220
+ Returns
221
+ -------
222
+ normal : `numpy.array`
223
+ The normal vector of the spline block at the given parameter values.
224
+
225
+ See Also
226
+ --------
227
+ `normal_spline` : Compute a spline that evaluates to the normal of the given spline block (not normalized).
228
+
229
+ Notes
230
+ -----
231
+ Attentive readers will notice that the number of independent variables could be one more than the number of
232
+ dependent variables (instead of one less, as is typical). In that case, the normal represents the null space of
233
+ the matrix formed by the tangents of the spline. If the null space is greater than one dimension, the normal will be zero.
234
+ """
235
+ return bspy._spline_evaluation.normal(self, uvw, normalize, indices)
236
+
237
+ def normal_spline(self, indices=None):
238
+ """
239
+ Compute a spline that evaluates to the normal of the given spline block. The length of the normal
240
+ is the area of the tangent space (for two independent variables, its the length of the cross product of tangent vectors).
241
+ The number of independent variables must be one different than the number of dependent variables.
242
+ Find all the contour curves of a block of splines whose `nInd` is one larger than its `nDep`.
243
+
244
+ Parameters
245
+ ----------
246
+ indices : `iterable`, optional
247
+ An iterable of normal indices to calculate. For example, `indices=(0, 3)` will make the returned spline compute a vector of length 2
248
+ with the first and fourth values of the normal. If `None`, all normal values are returned (the default).
249
+
250
+ Returns
251
+ -------
252
+ spline : `Spline`
253
+ The spline that evaluates to the normal of the given spline block.
254
+
255
+ See Also
256
+ --------
257
+ `normal` : Compute the normal of the spline block at given parameter values.
258
+
259
+ Notes
260
+ -----
261
+ Attentive readers will notice that the number of independent variables could be one more than the number of
262
+ dependent variables (instead of one less, as is typical). In that case, the normal represents the null space of
263
+ the matrix formed by the tangents of the spline block. If the null space is greater than one dimension, the normal will be zero.
264
+ """
265
+ return bspy._spline_operations.normal_spline(self, indices)
266
+
267
+ def range_bounds(self):
268
+ """
269
+ Return the range of a spline block as lower and upper bounds on each of the
270
+ dependent variables.
271
+ """
272
+ return self._block_evaluation((self.nDep, 2), bspy._spline_evaluation.range_bounds, [])
273
+
274
+ def reparametrize(self, newDomain):
275
+ """
276
+ Reparametrize a spline block to match new domain bounds. The number of knots and coefficients remain unchanged.
277
+
278
+ Parameters
279
+ ----------
280
+ newDomain : array-like
281
+ nInd x 2 array of the new lower and upper bounds on each of the independent variables (same form as
282
+ returned from `domain`). If a bound pair is `None` then the original bound (and knots) are left unchanged.
283
+ For example, `[[0.0, 1.0], None]` will reparametrize the first independent variable and leave the second unchanged)
284
+
285
+ Returns
286
+ -------
287
+ block : `SplineBlock`
288
+ Reparametrized spline block.
289
+
290
+ See Also
291
+ --------
292
+ `domain` : Return the domain of a spline block.
293
+ """
294
+ return self._block_operation(bspy._spline_domain.reparametrize, (newDomain,))
295
+
296
+ def trim(self, newDomain):
297
+ """
298
+ Trim the domain of a spline block.
299
+
300
+ Parameters
301
+ ----------
302
+ newDomain : array-like
303
+ nInd x 2 array of the new lower and upper bounds on each of the independent variables (same form as
304
+ returned from `domain`). If a bound is None or nan then the original bound (and knots) are left unchanged.
305
+
306
+ Returns
307
+ -------
308
+ block : `SplineBlock`
309
+ Trimmed spline block.
310
+ """
311
+ return self._block_operation(bspy._spline_domain.trim, (newDomain,))
312
+
313
+ def zeros(self, epsilon=None, initialScale=None):
314
+ """
315
+ Find the roots of a block of splines (nInd must match nDep).
316
+
317
+ Parameters
318
+ ----------
319
+ epsilon : `float`, optional
320
+ Tolerance for root precision. The root will be within epsilon of the actual root.
321
+ The default is the machine epsilon.
322
+
323
+ initialScale : array-like, optional
324
+ The initial scale of each dependent variable (as opposed to the current scale of
325
+ the spline block, which may have been normalized). The default is an array of ones (size nDep).
326
+
327
+ Returns
328
+ -------
329
+ roots : `iterable`
330
+ An iterable containing the roots of the block of splines. If the block is
331
+ zero over an interval, that root will appear as a tuple of the interval.
332
+ For curves (nInd == 1), the roots are ordered.
333
+
334
+ See Also
335
+ --------
336
+ `contours` : Find all the contour curves of a spline block whose `nInd` is one larger than its `nDep`.
337
+
338
+ Notes
339
+ -----
340
+ Implements a variation of the projected-polyhedron technique from Sherbrooke, Evan C., and Nicholas M. Patrikalakis.
341
+ "Computation of the solutions of nonlinear polynomial systems." Computer Aided Geometric Design 10, no. 5 (1993): 379-405.
342
+ """
343
+ return bspy._spline_intersection.zeros_using_projected_polyhedron(self, epsilon, initialScale)