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.
@@ -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
- if intersections[1][0] - intersections[0][0] < epsilon:
108
+ while intersections[1][0] - intersections[0][0] < epsilon:
109
109
  del intersections[1]
110
- if intersections[-1][0] - intersections[-2][0] < epsilon:
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 one less than the number of dependent variables.")
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 and knots for generalized cross product of the tangent space.
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
- ccm = _cross_correlation_matrix(self)
666
- for i, (order, knots) in enumerate(zip(self.order, self.knots)):
667
- # First, calculate the order of the normal for this independent variable.
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
- for j in range(self.nInd):
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
- for index in range(self.nInd) if indices is None else indices:
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 != i else 1
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 j in range(self.nInd):
685
- ord += order - 1 if ccm[i, j] and index != j else 0
686
- newOrd = max(newOrd, ord)
687
- newOrder.append(newOrd)
688
- uniqueKnots, counts = np.unique(knots[order - 1:self.nCoef[i] + 1], return_counts=True)
689
- counts += newOrd - order + 1 # Because we're multiplying all the tangents, the knot elevation is one more
690
- counts[0] = newOrd # But not at the endpoints, which are full order as usual
691
- counts[-1] = newOrd # But not at the endpoints, which are full order as usual
692
- newKnots.append(np.repeat(uniqueKnots, counts))
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, [newOrd], [nCoef], [newKnots[-1]], []).greville()
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 for order in self.order]
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, metadata = self.metadata)
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.99 # 1 - 1/10^2
29
- """ If a shift of 1 in the normal direction of one manifold yields a shift of 10 in the tangent plane intersection, the manifolds are parallel."""
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(np.array(normal))
33
- self._point = np.atleast_1d(np.array(point))
34
- self._tangentSpace = np.atleast_1d(np.array(tangentSpace))
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.01
11
- """If two points are within 0.01 of each each other, they are coincident."""
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
- # First, intersect domain coincidence with the domain boundary.
513
- coincidence = left.intersection(boundary.domain)
514
- # Next, invert the domain coincidence (which will remove it) if this is a twin or if the normals point in opposite directions.
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 i in range(len(coincidence.boundaries)):
521
- domainManifold = coincidence.boundaries[i].manifold
523
+ for coincidenceBoundary in left.boundaries:
524
+ coincidenceManifold = coincidenceBoundary.manifold
522
525
  if invertCoincidence:
523
- domainManifold = domainManifold.flip_normal()
526
+ coincidenceManifold = coincidenceManifold.flip_normal()
524
527
  if isTwin:
525
- domainManifold = domainManifold.translate(-intersection.translation)
526
- domainManifold = domainManifold.transform(intersection.inverse, intersection.transform.T)
528
+ coincidenceManifold = coincidenceManifold.translate(-intersection.translation)
529
+ coincidenceManifold = coincidenceManifold.transform(intersection.inverse, intersection.transform.T)
527
530
  else:
528
- domainManifold = domainManifold.transform(intersection.transform, intersection.inverse.T)
529
- domainManifold = domainManifold.translate(intersection.translation)
530
- coincidence.boundaries[i] = Boundary(domainManifold, coincidence.boundaries[i].domain)
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 `Spline`
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` or `None`, optional
432
- An `iterable` of the `n` functions representing the `n` first derivatives of `F`.
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 multiple calls to `F`
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 a data in a `dict`.
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, with_respect_to, uvw1, uvw2, returnSpline = False):
1231
+ def integral(self, integrand = None, domain = None):
1132
1232
  """
1133
- Compute the derivative of the spline at given parameter values.
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
- with_respect_to : `iterable`
1138
- An iterable of length `nInd` that specifies the integer order of integral for each independent variable.
1139
- A zero-order integral just evaluates the spline normally.
1140
-
1141
- uvw1 : `iterable`
1142
- An iterable of length `nInd` that specifies the lower limit of each independent variable (the parameter values).
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
- value : `numpy.ndarray`
1153
- The value of the integral of the spline at the given parameter limits.
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 the resulting spline.
1161
- `evaluate` : Compute the value of the spline at a given parameter value.
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
- The integral method uses the integrate method to integrate the spline `with_respect_to` times for each independent variable.
1168
- Then the method returns that integrated spline's value at `uw2` minus its value at `uw1` (optionally along with the spline).
1169
- The method doesn't calculate the integral directly because the number of operations required is nearly the same as constructing
1170
- the integrated spline.
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.integral(self, with_respect_to, uvw1, uvw2, returnSpline)
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 number of the independent variable to integrate. Default is zero.
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.tangent_space(self, uvw)
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, rangeBounds : `Spline`, `np.array`
2168
- A spline trimmed to the given domain bounds, and the range of the trimmed spline given as
2169
- lower and upper bounds on each dependent variable.
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
- `contour` : Fit a spline to the contour defined by `F(x) = 0`.
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
+