bspy 4.1__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/__init__.py +3 -0
- bspy/_spline_domain.py +92 -9
- bspy/_spline_evaluation.py +79 -20
- bspy/_spline_fitting.py +201 -45
- bspy/_spline_intersection.py +284 -137
- bspy/_spline_operations.py +63 -48
- bspy/hyperplane.py +6 -6
- bspy/manifold.py +2 -2
- bspy/solid.py +15 -12
- bspy/spline.py +180 -50
- bspy/spline_block.py +343 -0
- bspy/viewer.py +7 -6
- {bspy-4.1.dist-info → bspy-4.2.dist-info}/METADATA +11 -5
- bspy-4.2.dist-info/RECORD +18 -0
- {bspy-4.1.dist-info → bspy-4.2.dist-info}/WHEEL +1 -1
- bspy-4.1.dist-info/RECORD +0 -17
- {bspy-4.1.dist-info → bspy-4.2.dist-info}/LICENSE +0 -0
- {bspy-4.1.dist-info → bspy-4.2.dist-info}/top_level.txt +0 -0
bspy/_spline_operations.py
CHANGED
|
@@ -105,9 +105,9 @@ def confine(self, range_bounds):
|
|
|
105
105
|
intersections.sort(key=lambda intersection: intersection[0])
|
|
106
106
|
|
|
107
107
|
# Remove repeat points at start and end.
|
|
108
|
-
|
|
108
|
+
while intersections[1][0] - intersections[0][0] < epsilon:
|
|
109
109
|
del intersections[1]
|
|
110
|
-
|
|
110
|
+
while intersections[-1][0] - intersections[-2][0] < epsilon:
|
|
111
111
|
del intersections[-2]
|
|
112
112
|
|
|
113
113
|
# Insert order-1 knots at each intersection point.
|
|
@@ -630,79 +630,94 @@ def multiplyAndConvolve(self, other, indMap = None, productType = 'S'):
|
|
|
630
630
|
|
|
631
631
|
return type(self)(nInd, nDep, order, nCoef, knots, coefs, self.metadata)
|
|
632
632
|
|
|
633
|
-
# Return a matrix of booleans whose [i,j] value indicates if self's partial wrt variable i depends on variable j.
|
|
634
|
-
def _cross_correlation_matrix(self):
|
|
635
|
-
ccm = np.empty((self.nInd, self.nInd), bool)
|
|
636
|
-
for i in range(self.nInd - 1):
|
|
637
|
-
tangent = self.differentiate(i)
|
|
638
|
-
totalCoefs = tangent.coefs.size // tangent.nDep
|
|
639
|
-
ccm[i, i] = True
|
|
640
|
-
for j in range(i + 1, self.nInd):
|
|
641
|
-
coefs = np.moveaxis(tangent.coefs, (0, j + 1), (-1, -2))
|
|
642
|
-
coefs = coefs.reshape(totalCoefs // tangent.nCoef[j], tangent.nCoef[j], tangent.nDep)
|
|
643
|
-
match = True
|
|
644
|
-
for row in coefs:
|
|
645
|
-
first = row[0]
|
|
646
|
-
for point in row[1:]:
|
|
647
|
-
match = np.allclose(point, first)
|
|
648
|
-
if not match:
|
|
649
|
-
break
|
|
650
|
-
if not match:
|
|
651
|
-
break
|
|
652
|
-
ccm[i, j] = ccm[j, i] = not match
|
|
653
|
-
ccm[-1, -1] = True
|
|
654
|
-
return ccm
|
|
655
|
-
|
|
656
633
|
def normal_spline(self, indices=None):
|
|
657
|
-
if abs(self.nInd - self.nDep) != 1: raise ValueError("The number of independent variables must be
|
|
634
|
+
if abs(self.nInd - self.nDep) != 1: raise ValueError("The number of independent variables must be different than the number of dependent variables.")
|
|
658
635
|
|
|
659
|
-
# Construct order
|
|
636
|
+
# Construct order, nCoef, knots, and sample values for generalized cross product of the tangent space.
|
|
660
637
|
newOrder = []
|
|
661
638
|
newKnots = []
|
|
662
639
|
uvwValues = []
|
|
663
640
|
nCoefs = []
|
|
664
641
|
totalCoefs = [1]
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
642
|
+
for nInd in range(self.nInd):
|
|
643
|
+
knots = None
|
|
644
|
+
counts = None
|
|
645
|
+
maxOrder = 0
|
|
646
|
+
startInd = 0
|
|
647
|
+
endInd = 0
|
|
648
|
+
# First, collect the order, knots, and number of relevant columns for this independent variable.
|
|
649
|
+
for row in self.block:
|
|
650
|
+
rowInd = 0
|
|
651
|
+
for spline in row:
|
|
652
|
+
if rowInd <= nInd < rowInd + spline.nInd:
|
|
653
|
+
ind = nInd - rowInd
|
|
654
|
+
order = spline.order[ind]
|
|
655
|
+
k, c = np.unique(spline.knots[ind][order-1:spline.nCoef[ind]+1], return_counts=True)
|
|
656
|
+
if knots:
|
|
657
|
+
if maxOrder < order:
|
|
658
|
+
counts += order - maxOrder
|
|
659
|
+
maxOrder = order
|
|
660
|
+
endInd = max(endInd, rowInd + spline.nInd)
|
|
661
|
+
for knot, count in zip(k[1:-1], c[1:-1]):
|
|
662
|
+
ix = np.searchsorted(knots, knot)
|
|
663
|
+
if knots[ix] == knot:
|
|
664
|
+
counts[ix] = max(counts[ix], count + maxOrder - order)
|
|
665
|
+
else:
|
|
666
|
+
knots = np.insert(knots, ix, knot)
|
|
667
|
+
counts = np.insert(counts, ix, count + maxOrder - order)
|
|
668
|
+
else:
|
|
669
|
+
knots = k
|
|
670
|
+
counts = c
|
|
671
|
+
maxOrder = order
|
|
672
|
+
startInd = rowInd
|
|
673
|
+
endInd = rowInd + spline.nInd
|
|
674
|
+
|
|
675
|
+
break
|
|
676
|
+
|
|
677
|
+
rowInd += spline.nInd
|
|
678
|
+
|
|
679
|
+
# Next, calculate the order of the normal for this independent variable.
|
|
668
680
|
# Note that the total order will be one less than usual, because one of
|
|
669
681
|
# the tangents is the derivative with respect to that independent variable.
|
|
670
|
-
newOrd = 0
|
|
671
682
|
if self.nInd < self.nDep:
|
|
672
683
|
# If this normal involves all tangents, simply add the degree of each,
|
|
673
|
-
# so long as that tangent contains the independent variable.
|
|
674
|
-
|
|
675
|
-
newOrd += order - 1 if ccm[i, j] else 0
|
|
684
|
+
# so long as that tangent contains the independent variable.
|
|
685
|
+
order = (maxOrder - 1) * (endInd - startInd)
|
|
676
686
|
else:
|
|
677
687
|
# If this normal doesn't involve all tangents, find the max order of
|
|
678
688
|
# each returned combination (as defined by the indices).
|
|
679
|
-
|
|
689
|
+
order = 0
|
|
690
|
+
for index in range(startInd, endInd) if indices is None else indices:
|
|
680
691
|
# The order will be one larger if this independent variable's tangent is excluded by the index.
|
|
681
|
-
ord = 0 if index !=
|
|
692
|
+
ord = 0 if index != nInd else 1
|
|
682
693
|
# Add the degree of each tangent, so long as that tangent contains the
|
|
683
694
|
# independent variable and is not excluded by the index.
|
|
684
|
-
for
|
|
685
|
-
ord +=
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
counts
|
|
691
|
-
counts[
|
|
692
|
-
|
|
695
|
+
for ind in range(startInd, endInd):
|
|
696
|
+
ord += maxOrder - 1 if index != ind else 0
|
|
697
|
+
order = max(order, ord)
|
|
698
|
+
|
|
699
|
+
# Now, record the order of this independent variable and adjust the knot counts.
|
|
700
|
+
newOrder.append(order)
|
|
701
|
+
counts += order - maxOrder + 1 # Because we're multiplying all the tangents, the knot elevation is one more
|
|
702
|
+
counts[0] = order # But not at the endpoints, which are full order as usual
|
|
703
|
+
counts[-1] = order # But not at the endpoints, which are full order as usual
|
|
704
|
+
newKnots.append(np.repeat(knots, counts))
|
|
705
|
+
|
|
693
706
|
# Also calculate the total number of coefficients, capturing how it progressively increases, and
|
|
694
707
|
# using that calculation to span uvw from the starting knot to the end for each variable.
|
|
695
708
|
nCoef = len(newKnots[-1]) - newOrder[-1]
|
|
696
709
|
totalCoefs.append(totalCoefs[-1] * nCoef)
|
|
697
|
-
knotAverages = bspy.Spline(1, 0, [
|
|
710
|
+
knotAverages = bspy.Spline(1, 0, [order], [nCoef], [newKnots[-1]], []).greville()
|
|
698
711
|
for iKnot in range(1, len(knotAverages) - 1):
|
|
699
712
|
if knotAverages[iKnot] == knotAverages[iKnot + 1]:
|
|
700
713
|
knotAverages[iKnot] = 0.5 * (knotAverages[iKnot - 1] + knotAverages[iKnot])
|
|
701
714
|
knotAverages[iKnot + 1] = 0.5 * (knotAverages[iKnot + 1] + knotAverages[iKnot + 2])
|
|
702
715
|
uvwValues.append(knotAverages)
|
|
703
716
|
nCoefs.append(nCoef)
|
|
717
|
+
|
|
718
|
+
# Construct data points for normal.
|
|
704
719
|
points = []
|
|
705
|
-
ijk = [0
|
|
720
|
+
ijk = [0] * self.nInd
|
|
706
721
|
for i in range(totalCoefs[-1]):
|
|
707
722
|
uvw = [uvwValues[j][k] for j, k in enumerate(ijk)]
|
|
708
723
|
points.append(self.normal(uvw, False, indices))
|
|
@@ -716,7 +731,7 @@ def normal_spline(self, indices=None):
|
|
|
716
731
|
nCoefs.reverse()
|
|
717
732
|
points = np.reshape(points, [nDep] + nCoefs)
|
|
718
733
|
points = np.transpose(points, [0] + list(range(self.nInd, 0, -1)))
|
|
719
|
-
return bspy.Spline.least_squares(uvwValues, points, order = newOrder, knots = newKnots
|
|
734
|
+
return bspy.Spline.least_squares(uvwValues, points, order = newOrder, knots = newKnots)
|
|
720
735
|
|
|
721
736
|
def rotate(self, vector, angle):
|
|
722
737
|
vector = np.atleast_1d(vector)
|
bspy/hyperplane.py
CHANGED
|
@@ -25,13 +25,13 @@ class Hyperplane(Manifold):
|
|
|
25
25
|
Thus the dimension of the domain is one less than that of the range.
|
|
26
26
|
"""
|
|
27
27
|
|
|
28
|
-
maxAlignment = 0.
|
|
29
|
-
""" If
|
|
28
|
+
maxAlignment = 0.9999 # 1 - 1/10^4
|
|
29
|
+
""" If the absolute value of the dot product of two unit normals is greater than maxAlignment, the manifolds are parallel."""
|
|
30
30
|
|
|
31
31
|
def __init__(self, normal, point, tangentSpace):
|
|
32
|
-
self._normal = np.atleast_1d(
|
|
33
|
-
self._point = np.atleast_1d(
|
|
34
|
-
self._tangentSpace = np.atleast_1d(
|
|
32
|
+
self._normal = np.atleast_1d(normal)
|
|
33
|
+
self._point = np.atleast_1d(point)
|
|
34
|
+
self._tangentSpace = np.atleast_1d(tangentSpace)
|
|
35
35
|
if not np.allclose(self._tangentSpace.T @ self._normal, 0.0): raise ValueError("normal must be orthogonal to tangent space")
|
|
36
36
|
|
|
37
37
|
def __repr__(self):
|
|
@@ -172,7 +172,7 @@ class Hyperplane(Manifold):
|
|
|
172
172
|
-------
|
|
173
173
|
point : `numpy.array`
|
|
174
174
|
"""
|
|
175
|
-
return np.dot(self._tangentSpace, domainPoint) + self._point
|
|
175
|
+
return np.dot(self._tangentSpace, np.atleast_1d(domainPoint)) + self._point
|
|
176
176
|
|
|
177
177
|
def flip_normal(self):
|
|
178
178
|
"""
|
bspy/manifold.py
CHANGED
|
@@ -7,8 +7,8 @@ class Manifold:
|
|
|
7
7
|
normals and tangent spaces whose range is one dimension higher than their domain.
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
|
-
minSeparation = 0.
|
|
11
|
-
"""If two points are within
|
|
10
|
+
minSeparation = 0.0001
|
|
11
|
+
"""If two points are within minSeparation of each each other, they are coincident."""
|
|
12
12
|
|
|
13
13
|
Crossing = namedtuple('Crossing', ('left','right'))
|
|
14
14
|
Coincidence = namedtuple('Coincidence', ('left', 'right', 'alignment', 'transform', 'inverse', 'translation'))
|
bspy/solid.py
CHANGED
|
@@ -113,7 +113,7 @@ class Solid:
|
|
|
113
113
|
if boundary.manifold.range_dimension() != self.dimension: raise ValueError("Dimensions don't match")
|
|
114
114
|
self.boundaries.append(boundary)
|
|
115
115
|
if self.bounds is None:
|
|
116
|
-
self.bounds = boundary.bounds
|
|
116
|
+
self.bounds = boundary.bounds.copy()
|
|
117
117
|
elif boundary.bounds is None:
|
|
118
118
|
raise ValueError("Mix of infinite and bounded boundaries")
|
|
119
119
|
else:
|
|
@@ -509,25 +509,28 @@ class Solid:
|
|
|
509
509
|
slice.add_boundary(Boundary(right, domainSlice))
|
|
510
510
|
|
|
511
511
|
elif isinstance(intersection, Manifold.Coincidence):
|
|
512
|
-
#
|
|
513
|
-
|
|
514
|
-
#
|
|
512
|
+
# Intersect domain coincidence with the boundary's domain.
|
|
513
|
+
left = left.intersection(boundary.domain)
|
|
514
|
+
# Invert the domain coincidence (which will remove it) if this is a twin or if the normals point in opposite directions.
|
|
515
|
+
#invertCoincidence = trimTwin and (isTwin or intersection.alignment < 0.0)
|
|
515
516
|
invertCoincidence = (trimTwin and isTwin) or intersection.alignment < 0.0
|
|
517
|
+
# Create the coincidence to hold the trimmed and transformed domain coincidence (left).
|
|
518
|
+
coincidence = Solid(left.dimension, left.containsInfinity)
|
|
516
519
|
if invertCoincidence:
|
|
517
520
|
coincidence.containsInfinity = not coincidence.containsInfinity
|
|
518
521
|
# Next, transform the domain coincidence from the boundary to the given manifold.
|
|
519
522
|
# Create copies of the manifolds and boundaries, since we are changing them.
|
|
520
|
-
for
|
|
521
|
-
|
|
523
|
+
for coincidenceBoundary in left.boundaries:
|
|
524
|
+
coincidenceManifold = coincidenceBoundary.manifold
|
|
522
525
|
if invertCoincidence:
|
|
523
|
-
|
|
526
|
+
coincidenceManifold = coincidenceManifold.flip_normal()
|
|
524
527
|
if isTwin:
|
|
525
|
-
|
|
526
|
-
|
|
528
|
+
coincidenceManifold = coincidenceManifold.translate(-intersection.translation)
|
|
529
|
+
coincidenceManifold = coincidenceManifold.transform(intersection.inverse, intersection.transform.T)
|
|
527
530
|
else:
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
coincidence.
|
|
531
|
+
coincidenceManifold = coincidenceManifold.transform(intersection.transform, intersection.inverse.T)
|
|
532
|
+
coincidenceManifold = coincidenceManifold.translate(intersection.translation)
|
|
533
|
+
coincidence.add_boundary(Boundary(coincidenceManifold, coincidenceBoundary.domain))
|
|
531
534
|
# Finally, add the domain coincidence to the list of coincidences.
|
|
532
535
|
coincidences.append((invertCoincidence, coincidence))
|
|
533
536
|
|
bspy/spline.py
CHANGED
|
@@ -2,6 +2,7 @@ import numpy as np
|
|
|
2
2
|
from os import path
|
|
3
3
|
import json
|
|
4
4
|
from bspy.manifold import Manifold
|
|
5
|
+
import bspy.spline_block
|
|
5
6
|
import bspy._spline_domain
|
|
6
7
|
import bspy._spline_evaluation
|
|
7
8
|
import bspy._spline_intersection
|
|
@@ -124,6 +125,9 @@ class Spline(Manifold):
|
|
|
124
125
|
else:
|
|
125
126
|
return self.scale(other)
|
|
126
127
|
|
|
128
|
+
def __neg__(self):
|
|
129
|
+
return self.scale(-1.0)
|
|
130
|
+
|
|
127
131
|
def __sub__(self, other):
|
|
128
132
|
if isinstance(other, Spline):
|
|
129
133
|
return self.subtract(other, [(ix, ix) for ix in range(min(self.nInd, other.nInd))])
|
|
@@ -343,6 +347,42 @@ class Spline(Manifold):
|
|
|
343
347
|
if slice.dimension != 1 and slice.dimension != 2: raise ValueError("Only works for nInd = 1 or 2")
|
|
344
348
|
return bspy._spline_intersection.complete_slice(self, slice, solid)
|
|
345
349
|
|
|
350
|
+
@staticmethod
|
|
351
|
+
def composition(splines, tolerance = 1.0e-6):
|
|
352
|
+
"""
|
|
353
|
+
Construct a spline approximation to a composition of splines sequence.
|
|
354
|
+
|
|
355
|
+
Parameters
|
|
356
|
+
----------
|
|
357
|
+
splines : `array-like`
|
|
358
|
+
An array of splines. The splines should have the property that the number
|
|
359
|
+
of independent variables of the ith spline should be the same as the number
|
|
360
|
+
of dependent variables of the (i+1)st spline. The number of dependent
|
|
361
|
+
variables of the first spline is arbitrary, as is the number of independent
|
|
362
|
+
variables of the last one. Moreover, the range of the ith spline should be
|
|
363
|
+
a subset of the domain of the (i-1)st spline. The interpretation of the
|
|
364
|
+
sequence is s_0(s_1(... s_(n-1)(u)))).
|
|
365
|
+
|
|
366
|
+
tolerance : `scalar`
|
|
367
|
+
The maximum 2-norm of the difference between the given function and the
|
|
368
|
+
spline fit. Defaults to 1.0e-6.
|
|
369
|
+
|
|
370
|
+
Returns
|
|
371
|
+
-------
|
|
372
|
+
spline : `Spline`
|
|
373
|
+
The spline which approximates the given composition.
|
|
374
|
+
|
|
375
|
+
Notes
|
|
376
|
+
-----
|
|
377
|
+
This currently defaults to a cubic spline. Depending on user experience, this
|
|
378
|
+
may change in the future.
|
|
379
|
+
|
|
380
|
+
See also
|
|
381
|
+
--------
|
|
382
|
+
`fit` : Fit a given function to a specified tolerance.
|
|
383
|
+
"""
|
|
384
|
+
return bspy._spline_fitting.composition(splines, tolerance)
|
|
385
|
+
|
|
346
386
|
@staticmethod
|
|
347
387
|
def cone(radius1, radius2, height, tolerance = None):
|
|
348
388
|
"""
|
|
@@ -409,6 +449,24 @@ class Spline(Manifold):
|
|
|
409
449
|
"""
|
|
410
450
|
return bspy._spline_operations.confine(self, range_bounds)
|
|
411
451
|
|
|
452
|
+
def continuity(self):
|
|
453
|
+
"""
|
|
454
|
+
Return the smoothness of the spline in each of its independent variables.
|
|
455
|
+
|
|
456
|
+
Returns
|
|
457
|
+
-------
|
|
458
|
+
`smoothness` : `iterable`
|
|
459
|
+
An array of length nInd containing the number of times the function is continuously
|
|
460
|
+
in each of the independent variables of the input spline.
|
|
461
|
+
|
|
462
|
+
Notes
|
|
463
|
+
-----
|
|
464
|
+
The value -1 is returned if the spline is discontinuous in that variable. The degree of the spline
|
|
465
|
+
is returned if the spline contains no interior knots even though the spline is an analytic function
|
|
466
|
+
of that variable.
|
|
467
|
+
"""
|
|
468
|
+
return bspy._spline_evaluation.continuity(self)
|
|
469
|
+
|
|
412
470
|
@staticmethod
|
|
413
471
|
def contour(F, knownXValues, dF = None, epsilon = None, metadata = {}):
|
|
414
472
|
"""
|
|
@@ -418,8 +476,8 @@ class Spline(Manifold):
|
|
|
418
476
|
|
|
419
477
|
Parameters
|
|
420
478
|
----------
|
|
421
|
-
F : function or `
|
|
422
|
-
A function or spline that takes an array-like argument of length `n` and returns an
|
|
479
|
+
F : function, `Spline`, or `SplineBlock`
|
|
480
|
+
A function, spline, or spline block that takes an array-like argument of length `n` and returns an
|
|
423
481
|
array-like result of length `n - 1`.
|
|
424
482
|
|
|
425
483
|
knownXValues : `iterable` of array-like
|
|
@@ -428,11 +486,12 @@ class Spline(Manifold):
|
|
|
428
486
|
All x values must be length `n` and be listed in the order they appear on the contour.
|
|
429
487
|
`F(x)` for all known x values must be a zero vector of length `n-1`.
|
|
430
488
|
|
|
431
|
-
dF : `iterable
|
|
432
|
-
|
|
489
|
+
dF : function, `iterable`, or `None`, optional
|
|
490
|
+
A function that returns the Jacobian of F as an array with shape (n - 1, n).
|
|
491
|
+
Can also be an `iterable` of `n` functions that return the `n` first derivatives of `F`.
|
|
433
492
|
If `dF` is `None` (the default), the first derivatives will be computed for you.
|
|
434
|
-
If `F` is not a spline, computing the first derivatives involves
|
|
435
|
-
and can be numerically unstable.
|
|
493
|
+
If `F` is not a spline or spline block, computing the first derivatives involves
|
|
494
|
+
multiple calls to `F` and can be numerically unstable.
|
|
436
495
|
|
|
437
496
|
epsilon : `float`, optional
|
|
438
497
|
Tolerance for contour precision. Evaluating `F` with contour values will be within epsilon
|
|
@@ -455,7 +514,7 @@ class Spline(Manifold):
|
|
|
455
514
|
Notes
|
|
456
515
|
-----
|
|
457
516
|
The returned spline has constant parametric speed (the length of its derivative is constant).
|
|
458
|
-
If `F` is a `Spline`, then the range of the returned contour is confined to the domain of `F`.
|
|
517
|
+
If `F` is a `Spline` or a `SplineBlock`, then the range of the returned contour is confined to the domain of `F`.
|
|
459
518
|
Implements the algorithm described in section 7 of Grandine, Thomas A.
|
|
460
519
|
"Applications of contouring." Siam Review 42, no. 2 (2000): 297-316.
|
|
461
520
|
"""
|
|
@@ -483,7 +542,7 @@ class Spline(Manifold):
|
|
|
483
542
|
The algorithm used to to find all intersection curves is from Grandine, Thomas A., and Frederick W. Klein IV.
|
|
484
543
|
"A new approach to the surface intersection problem." Computer Aided Geometric Design 14, no. 2 (1997): 111-134.
|
|
485
544
|
"""
|
|
486
|
-
return bspy._spline_intersection.contours(self)
|
|
545
|
+
return bspy._spline_intersection.contours(bspy.spline_block.SplineBlock(self))
|
|
487
546
|
|
|
488
547
|
def contract(self, uvw):
|
|
489
548
|
"""
|
|
@@ -581,7 +640,7 @@ class Spline(Manifold):
|
|
|
581
640
|
|
|
582
641
|
def curvature(self, uvw):
|
|
583
642
|
"""
|
|
584
|
-
Compute the curvature of a univariate spline.
|
|
643
|
+
Compute the curvature of a univariate or bivariate spline.
|
|
585
644
|
|
|
586
645
|
Parameters
|
|
587
646
|
----------
|
|
@@ -591,7 +650,8 @@ class Spline(Manifold):
|
|
|
591
650
|
Returns
|
|
592
651
|
-------
|
|
593
652
|
value : scalar
|
|
594
|
-
The value of the curvature at the given point on the curve.
|
|
653
|
+
The value of the curvature at the given point on the curve or surface. If called on a surface,
|
|
654
|
+
the value will represent the Gaussian curvature of the surface at the given point.
|
|
595
655
|
|
|
596
656
|
Notes
|
|
597
657
|
-----
|
|
@@ -895,6 +955,46 @@ class Spline(Manifold):
|
|
|
895
955
|
"""
|
|
896
956
|
return bspy._spline_domain.extrapolate(self, newDomain, continuityOrder)
|
|
897
957
|
|
|
958
|
+
@staticmethod
|
|
959
|
+
def fit(domain, f, order = None, knots = None, tolerance = 1.0e-4):
|
|
960
|
+
"""
|
|
961
|
+
Fit the function f with a spline to a given tolerance.
|
|
962
|
+
|
|
963
|
+
Parameters
|
|
964
|
+
----------
|
|
965
|
+
domain - `array-like`
|
|
966
|
+
An nInd x 2 array which specifies the rectangular domain (in nInd dimensions)
|
|
967
|
+
over which the function f is defined. The approximating spline which is the
|
|
968
|
+
output will be defined over the same rectangular domain
|
|
969
|
+
|
|
970
|
+
f : Python function
|
|
971
|
+
The function to approximate. It is a vector-valued function of nDep
|
|
972
|
+
components in nInd variables.
|
|
973
|
+
|
|
974
|
+
order : `array-like`
|
|
975
|
+
An optional integer array of length nInd which specifies the polynomial
|
|
976
|
+
order to use in each of the independent variables. It will default to order
|
|
977
|
+
4 (degree 3) if None is specified (the default)
|
|
978
|
+
|
|
979
|
+
knots : `array-like`
|
|
980
|
+
The initial knot sequence to use, if given
|
|
981
|
+
|
|
982
|
+
tolerance : `scalar`
|
|
983
|
+
The maximum 2-norm of the difference between the given function and the
|
|
984
|
+
spline fit. Defaults to 1.0e-4.
|
|
985
|
+
|
|
986
|
+
Returns
|
|
987
|
+
-------
|
|
988
|
+
spline : `Spline`
|
|
989
|
+
A spline which approximates the given function to within the specified
|
|
990
|
+
tolerance.
|
|
991
|
+
|
|
992
|
+
See Also
|
|
993
|
+
--------
|
|
994
|
+
`least_squares` : Fit a least squares approximation to given data.
|
|
995
|
+
"""
|
|
996
|
+
return bspy._spline_fitting.fit(domain, f, order, knots, tolerance)
|
|
997
|
+
|
|
898
998
|
def flip_normal(self):
|
|
899
999
|
"""
|
|
900
1000
|
Flip the direction of the normal.
|
|
@@ -993,7 +1093,7 @@ class Spline(Manifold):
|
|
|
993
1093
|
@staticmethod
|
|
994
1094
|
def from_dict(dictionary):
|
|
995
1095
|
"""
|
|
996
|
-
Create a `Spline` from
|
|
1096
|
+
Create a `Spline` from data in a `dict`.
|
|
997
1097
|
|
|
998
1098
|
Parameters
|
|
999
1099
|
----------
|
|
@@ -1128,48 +1228,46 @@ class Spline(Manifold):
|
|
|
1128
1228
|
"""
|
|
1129
1229
|
return bspy._spline_domain.insert_knots(self, newKnots)
|
|
1130
1230
|
|
|
1131
|
-
def integral(self,
|
|
1231
|
+
def integral(self, integrand = None, domain = None):
|
|
1132
1232
|
"""
|
|
1133
|
-
Compute the
|
|
1233
|
+
Compute the integral of a function composed with a spline. In particular, compute the
|
|
1234
|
+
nInd-dimensional integral over the specified domain of the quantity f(x_0, x_1, ..., x_{nDep - 1})dA,
|
|
1235
|
+
where x_i is the (i+1)-th dependent variable, and dA is the multivariate measure of the spline mapping.
|
|
1134
1236
|
|
|
1135
1237
|
Parameters
|
|
1136
1238
|
----------
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1239
|
+
integrand : Python function, optional
|
|
1240
|
+
A Python function which takes an array-like object of length nDep as input and returns a
|
|
1241
|
+
scalar. If None is specified for integrand, then the function which returns a constant value
|
|
1242
|
+
of one is used. This computes the volume measure of the spline itself (arc length, surface area,
|
|
1243
|
+
volume, etc.) depending on the dimensionality of the spline itself.
|
|
1244
|
+
|
|
1245
|
+
domain : array-like
|
|
1246
|
+
nInd x 2 array of the lower and upper limits of integration for the spline on each of the
|
|
1247
|
+
independent variables. If domain is None, then the actual domain of the spline is used.
|
|
1143
1248
|
|
|
1144
|
-
uvw2 : `iterable`
|
|
1145
|
-
An iterable of length `nInd` that specifies the upper limit of each independent variable (the parameter values).
|
|
1146
|
-
|
|
1147
|
-
returnSpline : `boolean`, optional
|
|
1148
|
-
A boolean flag that if true returns the integrated spline along with the value of its integral. Default is false.
|
|
1149
|
-
|
|
1150
1249
|
Returns
|
|
1151
1250
|
-------
|
|
1152
|
-
|
|
1153
|
-
The value of the integral
|
|
1154
|
-
|
|
1155
|
-
spline : `Spline`
|
|
1156
|
-
The integrated spline, which is only returned if `returnSpline` is `True`.
|
|
1157
|
-
|
|
1251
|
+
integral_value : `float`
|
|
1252
|
+
The computed value of the specified integral
|
|
1253
|
+
|
|
1158
1254
|
See Also
|
|
1159
1255
|
--------
|
|
1160
|
-
`integrate` : Integrate a spline with respect to one of its independent variables, returning
|
|
1161
|
-
|
|
1162
|
-
`differentiate` : Differentiate a spline with respect to one of its independent variables, returning the resulting spline.
|
|
1163
|
-
`derivative` : Compute the derivative of the spline at a given parameter value.
|
|
1256
|
+
`integrate` : Integrate a spline with respect to one of its independent variables, returning
|
|
1257
|
+
the resulting spline.
|
|
1164
1258
|
|
|
1165
1259
|
Notes
|
|
1166
1260
|
-----
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
the
|
|
1261
|
+
This function is very useful for computing mass properties of splines. If the integrand function
|
|
1262
|
+
returns one, then the volume measure of the spline itself is computed (arc length, surface area,
|
|
1263
|
+
volume, etc.). If the integrand function returns one of the dependent variable values, then the
|
|
1264
|
+
integral will be the first moment of the spline with respect to that variable, making it possible
|
|
1265
|
+
to compute centroids and center of mass. The integrand function should be smooth, but is otherwise
|
|
1266
|
+
unrestricted.
|
|
1267
|
+
|
|
1268
|
+
Attempts to compute the integral to two digits less than machine precision.
|
|
1171
1269
|
"""
|
|
1172
|
-
return bspy._spline_evaluation.
|
|
1270
|
+
return bspy._spline_evaluation.composed_integral(self, integrand, domain)
|
|
1173
1271
|
|
|
1174
1272
|
def integrate(self, with_respect_to = 0):
|
|
1175
1273
|
"""
|
|
@@ -1178,7 +1276,7 @@ class Spline(Manifold):
|
|
|
1178
1276
|
Parameters
|
|
1179
1277
|
----------
|
|
1180
1278
|
with_respect_to : integer, optional
|
|
1181
|
-
The
|
|
1279
|
+
The index of the independent variable to integrate. Default is zero.
|
|
1182
1280
|
|
|
1183
1281
|
Returns
|
|
1184
1282
|
-------
|
|
@@ -1546,7 +1644,7 @@ class Spline(Manifold):
|
|
|
1546
1644
|
dependent variables (instead of one less, as is typical). In that case, the normal represents the null space of
|
|
1547
1645
|
the matrix formed by the tangents of the spline. If the null space is greater than one dimension, the normal will be zero.
|
|
1548
1646
|
"""
|
|
1549
|
-
return bspy._spline_operations.normal_spline(self, indices)
|
|
1647
|
+
return bspy._spline_operations.normal_spline(bspy.spline_block.SplineBlock(self), indices)
|
|
1550
1648
|
|
|
1551
1649
|
@staticmethod
|
|
1552
1650
|
def point(point):
|
|
@@ -1943,6 +2041,31 @@ class Spline(Manifold):
|
|
|
1943
2041
|
`multiply` : Multiply two splines together.
|
|
1944
2042
|
"""
|
|
1945
2043
|
return bspy._spline_fitting.sphere(radius, tolerance)
|
|
2044
|
+
|
|
2045
|
+
def split(self, minContinuity = 0, breaks = None):
|
|
2046
|
+
"""
|
|
2047
|
+
Split a spline into separate spline pieces.
|
|
2048
|
+
|
|
2049
|
+
Parameters
|
|
2050
|
+
----------
|
|
2051
|
+
minContinuity : `int`, optional
|
|
2052
|
+
The minimum expected continuity of each spline piece. The default is zero, for C0 continuity.
|
|
2053
|
+
|
|
2054
|
+
breaks : `iterable` of length `nInd` or `None`, optional
|
|
2055
|
+
An iterable that specifies the breaks at which to separate the spline.
|
|
2056
|
+
len(breaks[ind]) == 0 if there the spline isn't separated for the `ind` independent variable.
|
|
2057
|
+
If breaks is `None` (the default), the spline will only be separated at discontinuities.
|
|
2058
|
+
|
|
2059
|
+
Returns
|
|
2060
|
+
-------
|
|
2061
|
+
splineArray : array of `Spline`
|
|
2062
|
+
A array of splines with nInd dimensions containing the spline pieces.
|
|
2063
|
+
|
|
2064
|
+
See Also
|
|
2065
|
+
--------
|
|
2066
|
+
`join` : Join a list of splines together into a single spline.
|
|
2067
|
+
"""
|
|
2068
|
+
return bspy._spline_domain.split(self, minContinuity, breaks)
|
|
1946
2069
|
|
|
1947
2070
|
def subtract(self, other, indMap = None):
|
|
1948
2071
|
"""
|
|
@@ -1985,7 +2108,7 @@ class Spline(Manifold):
|
|
|
1985
2108
|
|
|
1986
2109
|
def tangent_space(self, uvw):
|
|
1987
2110
|
"""
|
|
1988
|
-
Return the tangent space of the spline.
|
|
2111
|
+
Return the tangent space of the spline. (Same as Jacobian.)
|
|
1989
2112
|
|
|
1990
2113
|
Parameters
|
|
1991
2114
|
----------
|
|
@@ -1997,7 +2120,7 @@ class Spline(Manifold):
|
|
|
1997
2120
|
tangentSpace : `numpy.array`
|
|
1998
2121
|
The nDep x nInd matrix of tangent vectors (tangents are the columns).
|
|
1999
2122
|
"""
|
|
2000
|
-
return bspy._spline_evaluation.
|
|
2123
|
+
return bspy._spline_evaluation.jacobian(self, uvw)
|
|
2001
2124
|
|
|
2002
2125
|
def to_dict(self):
|
|
2003
2126
|
"""
|
|
@@ -2164,9 +2287,11 @@ class Spline(Manifold):
|
|
|
2164
2287
|
|
|
2165
2288
|
Returns
|
|
2166
2289
|
-------
|
|
2167
|
-
trimmedSpline
|
|
2168
|
-
A spline trimmed to the given domain bounds
|
|
2169
|
-
|
|
2290
|
+
trimmedSpline : `Spline`
|
|
2291
|
+
A spline trimmed to the given domain bounds.
|
|
2292
|
+
|
|
2293
|
+
rangeBounds : `np.array`
|
|
2294
|
+
The range of the trimmed spline given as lower and upper bounds on each dependent variable.
|
|
2170
2295
|
|
|
2171
2296
|
See Also
|
|
2172
2297
|
--------
|
|
@@ -2213,7 +2338,7 @@ class Spline(Manifold):
|
|
|
2213
2338
|
"""
|
|
2214
2339
|
return bspy._spline_domain.unfold(self, foldedInd, coefficientlessSpline)
|
|
2215
2340
|
|
|
2216
|
-
def zeros(self, epsilon=None):
|
|
2341
|
+
def zeros(self, epsilon=None, initialScale=None):
|
|
2217
2342
|
"""
|
|
2218
2343
|
Find the roots of a spline (nInd must match nDep).
|
|
2219
2344
|
|
|
@@ -2223,6 +2348,10 @@ class Spline(Manifold):
|
|
|
2223
2348
|
Tolerance for root precision. The root will be within epsilon of the actual root.
|
|
2224
2349
|
The default is the machine epsilon.
|
|
2225
2350
|
|
|
2351
|
+
initialScale : array-like, optional
|
|
2352
|
+
The initial scale of each dependent variable (as opposed to the current scale of
|
|
2353
|
+
the spline, which may have been normalized). The default is an array of ones (size nDep).
|
|
2354
|
+
|
|
2226
2355
|
Returns
|
|
2227
2356
|
-------
|
|
2228
2357
|
roots : `iterable`
|
|
@@ -2233,7 +2362,7 @@ class Spline(Manifold):
|
|
|
2233
2362
|
See Also
|
|
2234
2363
|
--------
|
|
2235
2364
|
`intersect` : Intersect two splines.
|
|
2236
|
-
`
|
|
2365
|
+
`contours` : Find all the contour curves of a spline whose `nInd` is one larger than its `nDep`.
|
|
2237
2366
|
|
|
2238
2367
|
Notes
|
|
2239
2368
|
-----
|
|
@@ -2246,4 +2375,5 @@ class Spline(Manifold):
|
|
|
2246
2375
|
if self.nInd <= 1:
|
|
2247
2376
|
return bspy._spline_intersection.zeros_using_interval_newton(self)
|
|
2248
2377
|
else:
|
|
2249
|
-
return bspy._spline_intersection.zeros_using_projected_polyhedron(self, epsilon)
|
|
2378
|
+
return bspy._spline_intersection.zeros_using_projected_polyhedron(bspy.spline_block.SplineBlock(self), epsilon, initialScale)
|
|
2379
|
+
|