bspy 3.0.1__py3-none-any.whl → 4.1__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/__init__.py +16 -9
- bspy/_spline_domain.py +83 -47
- bspy/_spline_evaluation.py +44 -62
- bspy/_spline_fitting.py +353 -75
- bspy/_spline_intersection.py +332 -59
- bspy/_spline_operations.py +33 -38
- bspy/hyperplane.py +540 -0
- bspy/manifold.py +391 -0
- bspy/solid.py +839 -0
- bspy/spline.py +310 -77
- bspy/splineOpenGLFrame.py +683 -19
- bspy/viewer.py +795 -0
- {bspy-3.0.1.dist-info → bspy-4.1.dist-info}/METADATA +25 -13
- bspy-4.1.dist-info/RECORD +17 -0
- {bspy-3.0.1.dist-info → bspy-4.1.dist-info}/WHEEL +1 -1
- bspy/bspyApp.py +0 -426
- bspy/drawableSpline.py +0 -585
- bspy-3.0.1.dist-info/RECORD +0 -15
- {bspy-3.0.1.dist-info → bspy-4.1.dist-info}/LICENSE +0 -0
- {bspy-3.0.1.dist-info → bspy-4.1.dist-info}/top_level.txt +0 -0
bspy/__init__.py
CHANGED
|
@@ -1,19 +1,26 @@
|
|
|
1
1
|
"""
|
|
2
|
-
|
|
2
|
+
BSpy is a python library for manipulating and rendering non-uniform B-splines.
|
|
3
3
|
|
|
4
4
|
Available subpackages
|
|
5
5
|
---------------------
|
|
6
|
-
`bspy.
|
|
7
|
-
functions (spline functions) as linear combinations of B-splines.
|
|
6
|
+
`bspy.solid` : Provides the `Solid` and `Boundary` classes that model solids.
|
|
8
7
|
|
|
9
|
-
`bspy.
|
|
8
|
+
`bspy.manifold` : Provides the `Manifold` base class for manifolds.
|
|
10
9
|
|
|
11
|
-
`bspy.
|
|
10
|
+
`bspy.hyperplane` : Provides the `Hyperplane` subclass of `Manifold` that models hyperplanes.
|
|
12
11
|
|
|
13
|
-
`bspy.
|
|
14
|
-
|
|
12
|
+
`bspy.spline` : Provides the `Spline` subclass of `Manifold` that models, represents, and processes
|
|
13
|
+
piecewise polynomial tensor product functions (spline functions) as linear combinations of B-splines.
|
|
14
|
+
|
|
15
|
+
`bspy.splineOpenGLFrame` : Provides the `SplineOpenGLFrame` class, a tkinter `OpenGLFrame` with shaders to display splines.
|
|
16
|
+
|
|
17
|
+
`bspy.viewer` : Provides the `Viewer` tkinter app (`tkinter.Tk`) that hosts a `SplineOpenGLFrame`, a listbox full of
|
|
18
|
+
splines, and a set of controls to adjust and view the selected splines. It also provides the `Graphics` engine that creates
|
|
19
|
+
an associated `Viewer`, allowing you to script splines and display them in the viewer.
|
|
15
20
|
"""
|
|
21
|
+
from bspy.solid import Solid, Boundary
|
|
22
|
+
from bspy.manifold import Manifold
|
|
23
|
+
from bspy.hyperplane import Hyperplane
|
|
16
24
|
from bspy.spline import Spline
|
|
17
|
-
from bspy.drawableSpline import DrawableSpline
|
|
18
25
|
from bspy.splineOpenGLFrame import SplineOpenGLFrame
|
|
19
|
-
from bspy.
|
|
26
|
+
from bspy.viewer import Viewer, Graphics
|
bspy/_spline_domain.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import numpy as np
|
|
2
|
+
from bspy.manifold import Manifold
|
|
2
3
|
|
|
3
4
|
def clamp(self, left, right):
|
|
4
5
|
bounds = [[None, None] for i in range(self.nInd)]
|
|
@@ -12,10 +13,15 @@ def clamp(self, left, right):
|
|
|
12
13
|
return self.trim(bounds)
|
|
13
14
|
|
|
14
15
|
def common_basis(splines, indMap):
|
|
15
|
-
#
|
|
16
|
-
orders = []
|
|
16
|
+
# Fill out the default indMap.
|
|
17
17
|
if indMap is None:
|
|
18
18
|
indMap = [len(splines) * [iInd] for iInd in range(splines[0].nInd)]
|
|
19
|
+
|
|
20
|
+
# Ensure all splines are clamped at both ends.
|
|
21
|
+
splines = [spline.clamp(tuple(range(spline.nInd)), tuple(range(spline.nInd))) for spline in splines]
|
|
22
|
+
|
|
23
|
+
# Step 1: Compute the order for each aligned independent variable.
|
|
24
|
+
orders = []
|
|
19
25
|
for map in indMap:
|
|
20
26
|
if not(len(map) == len(splines)): raise ValueError("Invalid map")
|
|
21
27
|
order = 0
|
|
@@ -269,7 +275,7 @@ def extrapolate(self, newDomain, continuityOrder):
|
|
|
269
275
|
# Swap dependent and independent variables back.
|
|
270
276
|
dCoefs = dCoefs.swapaxes(1, ind + 2)
|
|
271
277
|
|
|
272
|
-
return type(self)(self.nInd, self.nDep, self.order, nCoef, knots, dCoefs[0], self.metadata)
|
|
278
|
+
return type(self)(self.nInd, self.nDep, self.order, nCoef, knots, dCoefs[0], self.metadata).remove_knots()
|
|
273
279
|
|
|
274
280
|
def fold(self, foldedInd):
|
|
275
281
|
if not(0 <= len(foldedInd) <= self.nInd): raise ValueError("Invalid foldedInd")
|
|
@@ -306,29 +312,29 @@ def fold(self, foldedInd):
|
|
|
306
312
|
|
|
307
313
|
def insert_knots(self, newKnots):
|
|
308
314
|
if not(len(newKnots) == self.nInd): raise ValueError("Invalid newKnots")
|
|
309
|
-
|
|
315
|
+
knotsList = list(self.knots)
|
|
310
316
|
coefs = self.coefs
|
|
311
|
-
for ind in
|
|
317
|
+
for ind, (order, knots) in enumerate(zip(self.order, self.knots)):
|
|
312
318
|
# We can't reference self.nCoef[ind] in this loop because we are expanding the knots and coefs arrays.
|
|
313
319
|
for knot in newKnots[ind]:
|
|
314
|
-
if knot < knots[
|
|
320
|
+
if knot < knots[order-1] or knot > knots[-order]:
|
|
315
321
|
raise ValueError(f"Knot insertion outside domain: {knot}")
|
|
316
|
-
if knot == knots[
|
|
317
|
-
position = len(knots
|
|
322
|
+
if knot == knots[-order]:
|
|
323
|
+
position = len(knots) - order
|
|
318
324
|
else:
|
|
319
|
-
position = np.searchsorted(knots
|
|
325
|
+
position = np.searchsorted(knots, knot, 'right')
|
|
320
326
|
coefs = coefs.swapaxes(0, ind + 1) # Swap dependent and independent variable (swap back later)
|
|
321
327
|
newCoefs = np.insert(coefs, position - 1, 0.0, axis=0)
|
|
322
|
-
for i in range(position -
|
|
323
|
-
alpha = (knot - knots[
|
|
328
|
+
for i in range(position - order + 1, position):
|
|
329
|
+
alpha = (knot - knots[i]) / (knots[i + order - 1] - knots[i])
|
|
324
330
|
newCoefs[i] = (1.0 - alpha) * coefs[i - 1] + alpha * coefs[i]
|
|
325
|
-
|
|
331
|
+
knotsList[ind] = knots = np.insert(knots, position, knot)
|
|
326
332
|
coefs = newCoefs.swapaxes(0, ind + 1)
|
|
327
333
|
|
|
328
334
|
if self.coefs is coefs:
|
|
329
335
|
return self
|
|
330
336
|
else:
|
|
331
|
-
return type(self)(self.nInd, self.nDep, self.order, coefs.shape[1:],
|
|
337
|
+
return type(self)(self.nInd, self.nDep, self.order, coefs.shape[1:], knotsList, coefs, self.metadata)
|
|
332
338
|
|
|
333
339
|
def join(splineList):
|
|
334
340
|
# Make sure all the splines in the list are curves
|
|
@@ -368,13 +374,13 @@ def join(splineList):
|
|
|
368
374
|
newKnots, newCoefs, workingSpline.metadata)
|
|
369
375
|
return workingSpline.reparametrize([[0.0, 1.0]]).remove_knots()
|
|
370
376
|
|
|
371
|
-
def remove_knot(self, iKnot):
|
|
377
|
+
def remove_knot(self, iKnot, nLeft = 0, nRight = 0):
|
|
372
378
|
if self.nInd != 1: raise ValueError("Must have one independent variable")
|
|
373
379
|
myOrder = self.order[0]
|
|
374
380
|
if iKnot < myOrder or iKnot >= self.nCoef[0]: raise ValueError("Must specify interior knots for removal")
|
|
375
381
|
diag0 = []
|
|
376
|
-
diag1 = []
|
|
377
|
-
rhs = []
|
|
382
|
+
diag1 = [1.0]
|
|
383
|
+
rhs = [np.ndarray.copy(self.coefs[:, iKnot - myOrder])]
|
|
378
384
|
myKnots = self.knots[0]
|
|
379
385
|
thisKnot = myKnots[iKnot]
|
|
380
386
|
|
|
@@ -384,45 +390,57 @@ def remove_knot(self, iKnot):
|
|
|
384
390
|
diag0.append(alpha)
|
|
385
391
|
diag1.append(1.0 - alpha)
|
|
386
392
|
rhs.append(np.ndarray.copy(self.coefs[:, iKnot - myOrder + ix]))
|
|
387
|
-
|
|
388
|
-
|
|
393
|
+
diag0.append(1.0)
|
|
394
|
+
diag1.append(0.0)
|
|
395
|
+
rhs.append(np.ndarray.copy(self.coefs[:, iKnot]))
|
|
389
396
|
rhs = np.array(rhs)
|
|
390
|
-
|
|
391
|
-
|
|
397
|
+
|
|
398
|
+
# Take care of the extra known conditions on the left
|
|
399
|
+
extraLeft = max(0, nLeft - iKnot + myOrder)
|
|
400
|
+
for ix in range(extraLeft):
|
|
401
|
+
rhs[ix] /= diag1[ix]
|
|
402
|
+
rhs[ix + 1] -= diag0[ix] * rhs[ix]
|
|
403
|
+
|
|
404
|
+
# Take care of the extra known conditions on the right
|
|
405
|
+
extraRight = max(0, nRight - self.nCoef[0] + iKnot + 1)
|
|
406
|
+
for ix in range(extraRight):
|
|
407
|
+
rhs[-1 - ix] /= diag0[-1 - ix]
|
|
408
|
+
rhs[-2 - ix] -= diag1[-2 - ix] * rhs[-1 - ix]
|
|
392
409
|
|
|
393
410
|
# Use Givens rotations to factor the matrix and track right hand side
|
|
394
|
-
for ix in range(
|
|
395
|
-
cos = diag1[ix
|
|
411
|
+
for ix in range(extraLeft, myOrder - extraRight):
|
|
412
|
+
cos = diag1[ix]
|
|
396
413
|
sin = diag0[ix]
|
|
397
414
|
denom = np.sqrt(cos ** 2 + sin ** 2)
|
|
398
415
|
cos /= denom
|
|
399
416
|
sin /= denom
|
|
400
|
-
diag1[ix
|
|
401
|
-
diag0[ix
|
|
402
|
-
diag1[ix] *= cos
|
|
403
|
-
tempRow = cos * rhs[ix
|
|
404
|
-
rhs[ix] = cos * rhs[ix] - sin * rhs[ix
|
|
405
|
-
rhs[ix
|
|
417
|
+
diag1[ix] = denom
|
|
418
|
+
diag0[ix] = sin * diag1[ix + 1]
|
|
419
|
+
diag1[ix + 1] *= cos
|
|
420
|
+
tempRow = cos * rhs[ix] + sin * rhs[ix + 1]
|
|
421
|
+
rhs[ix + 1] = cos * rhs[ix + 1] - sin * rhs[ix]
|
|
422
|
+
rhs[ix] = tempRow
|
|
406
423
|
|
|
407
424
|
# Perform back substitution
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
rhs[-
|
|
425
|
+
for ix in range(1 + extraRight, myOrder - extraLeft):
|
|
426
|
+
rhs[-1 - ix] /= diag1[-1 - ix]
|
|
427
|
+
rhs[-2 - ix] -= diag0[-1 - ix] * rhs[-1 - ix]
|
|
428
|
+
rhs[-1 - myOrder + extraLeft] /= diag1[-1 - myOrder + extraLeft]
|
|
429
|
+
|
|
430
|
+
# Save residual and adjust solution
|
|
431
|
+
residual = abs(rhs[-1 - extraRight])
|
|
432
|
+
for ix in range(extraRight):
|
|
433
|
+
rhs[-1 - extraRight+ ix] = rhs[-extraRight + ix]
|
|
411
434
|
|
|
412
435
|
# Create new spline
|
|
413
436
|
newNCoef = [self.nCoef[0] - 1]
|
|
414
|
-
newKnots = [
|
|
415
|
-
newCoefs = []
|
|
416
|
-
|
|
417
|
-
oldCoefs = self.coefs[ix]
|
|
418
|
-
firstCoefs = oldCoefs[:iKnot - self.order[0] + 1]
|
|
419
|
-
lastCoefs = oldCoefs[iKnot:]
|
|
420
|
-
middleCoefs = rhs[:-1,ix]
|
|
421
|
-
newCoefs.append(list(firstCoefs) + list(middleCoefs) + list(lastCoefs))
|
|
437
|
+
newKnots = [np.delete(self.knots[0], iKnot)]
|
|
438
|
+
newCoefs = np.delete(self.coefs, iKnot - self.order[0] + 1, 1)
|
|
439
|
+
newCoefs[: , iKnot - self.order[0] : iKnot] = rhs[: -1].T
|
|
422
440
|
withoutKnot = type(self)(self.nInd, self.nDep, self.order, newNCoef, newKnots, newCoefs)
|
|
423
|
-
return withoutKnot,
|
|
441
|
+
return withoutKnot, residual
|
|
424
442
|
|
|
425
|
-
def remove_knots(self, tolerance):
|
|
443
|
+
def remove_knots(self, tolerance, nLeft = 0, nRight = 0):
|
|
426
444
|
scaleDep = [max(np.abs(bound[0]), np.abs(bound[1])) for bound in self.range_bounds()]
|
|
427
445
|
scaleDep = [1.0 if factor == 0.0 else factor for factor in scaleDep]
|
|
428
446
|
rScaleDep = np.array([1.0 / factor for factor in scaleDep])
|
|
@@ -435,12 +453,20 @@ def remove_knots(self, tolerance):
|
|
|
435
453
|
currentFold, foldedBasis = currentSpline.fold(foldedIndices)
|
|
436
454
|
while True:
|
|
437
455
|
bestError = np.finfo(scaleDep[0].dtype).max
|
|
438
|
-
|
|
439
|
-
|
|
456
|
+
bestSpline = currentFold
|
|
457
|
+
ix = currentFold.order[0]
|
|
458
|
+
while ix < currentFold.nCoef[0]:
|
|
459
|
+
newSpline, residual = currentFold.remove_knot(ix, nLeft, nRight)
|
|
440
460
|
error = np.max(residual)
|
|
461
|
+
if error < 0.001 * tolerance:
|
|
462
|
+
currentFold = newSpline
|
|
463
|
+
continue
|
|
441
464
|
if error < bestError:
|
|
442
465
|
bestError = error
|
|
443
466
|
bestSpline = newSpline
|
|
467
|
+
ix += 1
|
|
468
|
+
if currentFold.nCoef[0] < bestSpline.nCoef[0]:
|
|
469
|
+
continue
|
|
444
470
|
if bestError > tolerance:
|
|
445
471
|
break
|
|
446
472
|
errorSpline = truthSpline - bestSpline.unfold(foldedIndices, foldedBasis)
|
|
@@ -508,6 +534,8 @@ def transpose(self, axes=None):
|
|
|
508
534
|
|
|
509
535
|
def trim(self, newDomain):
|
|
510
536
|
if not(len(newDomain) == self.nInd): raise ValueError("Invalid newDomain")
|
|
537
|
+
if self.nInd < 1: return self
|
|
538
|
+
newDomain = np.array(newDomain, self.knots[0].dtype) # Force dtype and convert None to nan
|
|
511
539
|
|
|
512
540
|
# Step 1: Determine the knots to insert at the new domain bounds.
|
|
513
541
|
newKnotsList = []
|
|
@@ -517,7 +545,7 @@ def trim(self, newDomain):
|
|
|
517
545
|
leftBound = False # Do we have a left bound?
|
|
518
546
|
newKnots = []
|
|
519
547
|
|
|
520
|
-
if
|
|
548
|
+
if not np.isnan(bounds[0]):
|
|
521
549
|
if not(knots[order - 1] <= bounds[0] <= knots[-order]): raise ValueError("Invalid newDomain")
|
|
522
550
|
leftBound = True
|
|
523
551
|
multiplicity = order
|
|
@@ -526,7 +554,7 @@ def trim(self, newDomain):
|
|
|
526
554
|
multiplicity -= counts[i]
|
|
527
555
|
newKnots += multiplicity * [bounds[0]]
|
|
528
556
|
|
|
529
|
-
if
|
|
557
|
+
if not np.isnan(bounds[1]):
|
|
530
558
|
if not(knots[order - 1] <= bounds[1] <= knots[-order]): raise ValueError("Invalid newDomain")
|
|
531
559
|
if leftBound:
|
|
532
560
|
if not(bounds[0] < bounds[1]): raise ValueError("Invalid newDomain")
|
|
@@ -547,14 +575,22 @@ def trim(self, newDomain):
|
|
|
547
575
|
knotsList = []
|
|
548
576
|
coefIndex = [slice(None)] # First index is for nDep
|
|
549
577
|
for (order, knots, bounds) in zip(spline.order, spline.knots, newDomain):
|
|
550
|
-
leftIndex = 0 if
|
|
551
|
-
rightIndex = len(knots) - order if
|
|
578
|
+
leftIndex = 0 if np.isnan(bounds[0]) else np.searchsorted(knots, bounds[0])
|
|
579
|
+
rightIndex = len(knots) - order if np.isnan(bounds[1]) else np.searchsorted(knots, bounds[1])
|
|
552
580
|
knotsList.append(knots[leftIndex:rightIndex + order])
|
|
553
581
|
coefIndex.append(slice(leftIndex, rightIndex))
|
|
554
582
|
coefs = spline.coefs[tuple(coefIndex)]
|
|
555
583
|
|
|
556
584
|
return type(spline)(spline.nInd, spline.nDep, spline.order, coefs.shape[1:], knotsList, coefs, spline.metadata)
|
|
557
585
|
|
|
586
|
+
def trimmed_range_bounds(self, domainBounds):
|
|
587
|
+
domainBounds = np.array(domainBounds, copy=True)
|
|
588
|
+
for original, trim in zip(self.domain(), domainBounds):
|
|
589
|
+
trim[0] = max(original[0], trim[0] - Manifold.minSeparation)
|
|
590
|
+
trim[1] = min(original[1], trim[1] + Manifold.minSeparation)
|
|
591
|
+
trimmedSpline = self.trim(domainBounds)
|
|
592
|
+
return trimmedSpline, trimmedSpline.range_bounds()
|
|
593
|
+
|
|
558
594
|
def unfold(self, foldedInd, coefficientlessSpline):
|
|
559
595
|
if not(len(foldedInd) == coefficientlessSpline.nInd): raise ValueError("Invalid coefficientlessSpline")
|
|
560
596
|
unfoldedOrder = []
|
bspy/_spline_evaluation.py
CHANGED
|
@@ -1,43 +1,12 @@
|
|
|
1
1
|
import numpy as np
|
|
2
2
|
|
|
3
|
-
def blossom(self, uvw):
|
|
4
|
-
def blossom_values(knot, knots, order, u):
|
|
5
|
-
basis = np.zeros(order, knots.dtype)
|
|
6
|
-
basis[-1] = 1.0
|
|
7
|
-
for degree in range(1, order):
|
|
8
|
-
b = order - degree
|
|
9
|
-
for i in range(knot - degree, knot):
|
|
10
|
-
alpha = (u[degree - 1] - knots[i]) / (knots[i + degree] - knots[i])
|
|
11
|
-
basis[b - 1] += (1.0 - alpha) * basis[b]
|
|
12
|
-
basis[b] *= alpha
|
|
13
|
-
b += 1
|
|
14
|
-
return basis
|
|
15
|
-
|
|
16
|
-
# Make work for scalar valued functions
|
|
17
|
-
uvw = np.atleast_1d(uvw)
|
|
18
|
-
|
|
19
|
-
# Check for evaluation point inside domain
|
|
20
|
-
dom = self.domain()
|
|
21
|
-
for ix in range(self.nInd):
|
|
22
|
-
if uvw[ix][0] < dom[ix][0] or uvw[ix][self.order[ix]-2] > dom[ix][1]:
|
|
23
|
-
raise ValueError(f"Spline evaluation outside domain: {uvw}")
|
|
24
|
-
|
|
25
|
-
# Grab all of the appropriate coefficients
|
|
26
|
-
mySection = [slice(0, self.nDep)]
|
|
27
|
-
myIndices = []
|
|
28
|
-
for iv in range(self.nInd):
|
|
29
|
-
ix = np.searchsorted(self.knots[iv], uvw[iv][0], 'right')
|
|
30
|
-
ix = min(ix, self.nCoef[iv])
|
|
31
|
-
myIndices.append(ix)
|
|
32
|
-
mySection.append(slice(ix - self.order[iv], ix))
|
|
33
|
-
myCoefs = self.coefs[tuple(mySection)]
|
|
34
|
-
for iv in range(self.nInd - 1, -1, -1):
|
|
35
|
-
bValues = blossom_values(myIndices[iv], self.knots[iv], self.order[iv], uvw[iv])
|
|
36
|
-
myCoefs = myCoefs @ bValues
|
|
37
|
-
return myCoefs
|
|
38
|
-
|
|
39
3
|
def bspline_values(knot, knots, splineOrder, u, derivativeOrder = 0, taylorCoefs = False):
|
|
40
4
|
basis = np.zeros(splineOrder, knots.dtype)
|
|
5
|
+
if knot is None:
|
|
6
|
+
knot = np.searchsorted(knots, u, side = 'right')
|
|
7
|
+
knot = min(knot, len(knots) - splineOrder)
|
|
8
|
+
if derivativeOrder >= splineOrder:
|
|
9
|
+
return knot, basis
|
|
41
10
|
basis[-1] = 1.0
|
|
42
11
|
for degree in range(1, splineOrder - derivativeOrder):
|
|
43
12
|
b = splineOrder - degree
|
|
@@ -54,7 +23,7 @@ def bspline_values(knot, knots, splineOrder, u, derivativeOrder = 0, taylorCoefs
|
|
|
54
23
|
basis[b - 1] += -alpha * basis[b]
|
|
55
24
|
basis[b] *= alpha
|
|
56
25
|
b += 1
|
|
57
|
-
return basis
|
|
26
|
+
return knot, basis
|
|
58
27
|
|
|
59
28
|
def curvature(self, uv):
|
|
60
29
|
if self.nInd == 1:
|
|
@@ -87,16 +56,14 @@ def derivative(self, with_respect_to, uvw):
|
|
|
87
56
|
|
|
88
57
|
# Grab all of the appropriate coefficients
|
|
89
58
|
mySection = [slice(0, self.nDep)]
|
|
90
|
-
|
|
59
|
+
bValues = []
|
|
91
60
|
for iv in range(self.nInd):
|
|
92
|
-
ix =
|
|
93
|
-
|
|
94
|
-
myIndices.append(ix)
|
|
61
|
+
ix, indValues = bspline_values(None, self.knots[iv], self.order[iv], uvw[iv], with_respect_to[iv])
|
|
62
|
+
bValues.append(indValues)
|
|
95
63
|
mySection.append(slice(ix - self.order[iv], ix))
|
|
96
64
|
myCoefs = self.coefs[tuple(mySection)]
|
|
97
65
|
for iv in range(self.nInd - 1, -1, -1):
|
|
98
|
-
|
|
99
|
-
myCoefs = myCoefs @ bValues
|
|
66
|
+
myCoefs = myCoefs @ bValues[iv]
|
|
100
67
|
return myCoefs
|
|
101
68
|
|
|
102
69
|
def domain(self):
|
|
@@ -120,18 +87,28 @@ def evaluate(self, uvw):
|
|
|
120
87
|
|
|
121
88
|
# Grab all of the appropriate coefficients
|
|
122
89
|
mySection = [slice(0, self.nDep)]
|
|
123
|
-
|
|
90
|
+
bValues = []
|
|
124
91
|
for iv in range(self.nInd):
|
|
125
|
-
ix =
|
|
126
|
-
|
|
127
|
-
myIndices.append(ix)
|
|
92
|
+
ix, indValues = bspline_values(None, self.knots[iv], self.order[iv], uvw[iv])
|
|
93
|
+
bValues.append(indValues)
|
|
128
94
|
mySection.append(slice(ix - self.order[iv], ix))
|
|
129
95
|
myCoefs = self.coefs[tuple(mySection)]
|
|
130
96
|
for iv in range(self.nInd - 1, -1, -1):
|
|
131
|
-
|
|
132
|
-
myCoefs = myCoefs @ bValues
|
|
97
|
+
myCoefs = myCoefs @ bValues[iv]
|
|
133
98
|
return myCoefs
|
|
134
99
|
|
|
100
|
+
def greville(self, ind = 0):
|
|
101
|
+
if ind < 0 or ind >= self.nInd: raise ValueError("Invalid independent variable")
|
|
102
|
+
myKnots = self.knots[ind]
|
|
103
|
+
knotAverages = 0
|
|
104
|
+
if self.order[ind] == 1:
|
|
105
|
+
knotAverages = 0.5 * (myKnots[1:] + myKnots[:-1])
|
|
106
|
+
else:
|
|
107
|
+
for ix in range(1, self.order[ind]):
|
|
108
|
+
knotAverages = knotAverages + myKnots[ix : ix + self.nCoef[ind]]
|
|
109
|
+
knotAverages /= (self.order[ind] - 1)
|
|
110
|
+
return knotAverages
|
|
111
|
+
|
|
135
112
|
def integral(self, with_respect_to, uvw1, uvw2, returnSpline = False):
|
|
136
113
|
# Make work for scalar valued functions
|
|
137
114
|
uvw1 = np.atleast_1d(uvw1)
|
|
@@ -174,28 +151,24 @@ def normal(self, uvw, normalize=True, indices=None):
|
|
|
174
151
|
if abs(self.nInd - self.nDep) != 1: raise ValueError("The number of independent variables must be one different than the number of dependent variables.")
|
|
175
152
|
|
|
176
153
|
# Evaluate the tangents at the point.
|
|
177
|
-
tangentSpace =
|
|
178
|
-
with_respect_to = [0] * self.nInd
|
|
179
|
-
for i in range(self.nInd):
|
|
180
|
-
with_respect_to[i] = 1
|
|
181
|
-
tangentSpace[i] = self.derivative(with_respect_to, uvw)
|
|
182
|
-
with_respect_to[i] = 0
|
|
154
|
+
tangentSpace = self.tangent_space(uvw)
|
|
183
155
|
|
|
184
|
-
#
|
|
185
|
-
|
|
186
|
-
if self.nInd > nDep:
|
|
187
|
-
tangentSpace = tangentSpace.T
|
|
156
|
+
# Record the larger dimension and ensure it comes first.
|
|
157
|
+
if self.nInd > self.nDep:
|
|
188
158
|
nDep = self.nInd
|
|
159
|
+
tangentSpace = tangentSpace.T
|
|
160
|
+
else:
|
|
161
|
+
nDep = self.nDep
|
|
189
162
|
|
|
190
163
|
# Compute the normal using cofactors (determinants of subsets of the tangent space).
|
|
191
|
-
sign = 1
|
|
164
|
+
sign = -1 if self.metadata.get("flipNormal", False) else 1
|
|
192
165
|
if indices is None:
|
|
193
166
|
indices = range(nDep)
|
|
194
167
|
normal = np.empty(nDep, self.coefs.dtype)
|
|
195
168
|
else:
|
|
196
169
|
normal = np.empty(len(indices), self.coefs.dtype)
|
|
197
170
|
for i in indices:
|
|
198
|
-
normal[i] = sign * np.linalg.det(
|
|
171
|
+
normal[i] = sign * np.linalg.det(tangentSpace[[j for j in range(nDep) if i != j]])
|
|
199
172
|
sign *= -1
|
|
200
173
|
|
|
201
174
|
# Normalize the result as needed.
|
|
@@ -207,4 +180,13 @@ def normal(self, uvw, normalize=True, indices=None):
|
|
|
207
180
|
def range_bounds(self):
|
|
208
181
|
# Assumes self.nDep is the first value in self.coefs.shape
|
|
209
182
|
bounds = [[coefficient.min(), coefficient.max()] for coefficient in self.coefs]
|
|
210
|
-
return np.array(bounds, self.coefs.dtype)
|
|
183
|
+
return np.array(bounds, self.coefs.dtype)
|
|
184
|
+
|
|
185
|
+
def tangent_space(self, uvw):
|
|
186
|
+
tangentSpace = np.empty((self.nDep, self.nInd), self.coefs.dtype)
|
|
187
|
+
wrt = [0] * self.nInd
|
|
188
|
+
for i in range(self.nInd):
|
|
189
|
+
wrt[i] = 1
|
|
190
|
+
tangentSpace[:, i] = self.derivative(wrt, uvw)
|
|
191
|
+
wrt[i] = 0
|
|
192
|
+
return tangentSpace
|