bspy 4.1__py3-none-any.whl → 4.3__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.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
@@ -78,7 +79,7 @@ class Spline(Manifold):
78
79
 
79
80
  def __repr__(self):
80
81
  return f"Spline({self.nInd}, {self.nDep}, {self.order}, " + \
81
- f"{self.nCoef}, {self.knots} {self.coefs}, {self.metadata})"
82
+ f"{self.nCoef}, {self.knots}, {self.coefs}, {self.metadata})"
82
83
 
83
84
  def __add__(self, other):
84
85
  if isinstance(other, Spline):
@@ -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,49 @@ 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. Alternatively, it can return a spline of any
973
+ number independent variables and nDep dependent variables. In this case, the
974
+ resulting spline function will have nInd + number of independent variables
975
+ in the splines returned independent variables and nDep dependent variables.
976
+
977
+ order : `array-like`
978
+ An optional integer array of length nInd which specifies the polynomial
979
+ order to use in each of the independent variables. It will default to order
980
+ 4 (degree 3) if None is specified (the default)
981
+
982
+ knots : `array-like`
983
+ The initial knot sequence to use, if given
984
+
985
+ tolerance : `scalar`
986
+ The maximum 2-norm of the difference between the given function and the
987
+ spline fit. Defaults to 1.0e-4.
988
+
989
+ Returns
990
+ -------
991
+ spline : `Spline`
992
+ A spline which approximates the given function to within the specified
993
+ tolerance.
994
+
995
+ See Also
996
+ --------
997
+ `least_squares` : Fit a least squares approximation to given data.
998
+ """
999
+ return bspy._spline_fitting.fit(domain, f, order, knots, tolerance)
1000
+
898
1001
  def flip_normal(self):
899
1002
  """
900
1003
  Flip the direction of the normal.
@@ -993,7 +1096,7 @@ class Spline(Manifold):
993
1096
  @staticmethod
994
1097
  def from_dict(dictionary):
995
1098
  """
996
- Create a `Spline` from a data in a `dict`.
1099
+ Create a `Spline` from data in a `dict`.
997
1100
 
998
1101
  Parameters
999
1102
  ----------
@@ -1009,7 +1112,7 @@ class Spline(Manifold):
1009
1112
  `to_dict` : Return a `dict` with `Spline` data.
1010
1113
  """
1011
1114
  return Spline(dictionary["nInd"], dictionary["nDep"], dictionary["order"], dictionary["nCoef"],
1012
- [np.array(knots) for knots in dictionary["knots"]], np.array(dictionary["coefs"]), dictionary["metadata"])
1115
+ [np.array(knots) for knots in dictionary["knots"]], np.array(dictionary["coefs"]), dictionary.get("metadata", {}))
1013
1116
 
1014
1117
  def full_domain(self):
1015
1118
  """
@@ -1111,7 +1214,12 @@ class Spline(Manifold):
1111
1214
  ----------
1112
1215
  newKnots : `iterable` of length `nInd`
1113
1216
  An iterable that specifies the knots to be added to each independent variable's knots.
1114
- len(newKnots[ind]) == 0 if no knots are to be added for the `ind` independent variable.
1217
+ len(newKnots[ind]) == 0 if no knots are to be added for the `ind` independent variable.
1218
+
1219
+ Each knot may be specified as its knot value or a tuple indicating the knot value and its multiplicity.
1220
+ For example, spline.insert_knots([[0.1, (0.3, 2)], [(.5, 3), .2, .5]]) will insert 0.1 once and 0.3 twice into
1221
+ the knots of the first independent variable, and will insert 0.2 once and 0.5 four times into the knots of the
1222
+ second independent variable. Knots do not need to be sorted.
1115
1223
 
1116
1224
  Returns
1117
1225
  -------
@@ -1128,48 +1236,46 @@ class Spline(Manifold):
1128
1236
  """
1129
1237
  return bspy._spline_domain.insert_knots(self, newKnots)
1130
1238
 
1131
- def integral(self, with_respect_to, uvw1, uvw2, returnSpline = False):
1239
+ def integral(self, integrand = None, domain = None):
1132
1240
  """
1133
- Compute the derivative of the spline at given parameter values.
1241
+ Compute the integral of a function composed with a spline. In particular, compute the
1242
+ nInd-dimensional integral over the specified domain of the quantity f(x_0, x_1, ..., x_{nDep - 1})dA,
1243
+ where x_i is the (i+1)-th dependent variable, and dA is the multivariate measure of the spline mapping.
1134
1244
 
1135
1245
  Parameters
1136
1246
  ----------
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).
1247
+ integrand : Python function, optional
1248
+ A Python function which takes an array-like object of length nDep as input and returns a
1249
+ scalar. If None is specified for integrand, then the function which returns a constant value
1250
+ of one is used. This computes the volume measure of the spline itself (arc length, surface area,
1251
+ volume, etc.) depending on the dimensionality of the spline itself.
1252
+
1253
+ domain : array-like
1254
+ nInd x 2 array of the lower and upper limits of integration for the spline on each of the
1255
+ independent variables. If domain is None, then the actual domain of the spline is used.
1143
1256
 
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
1257
  Returns
1151
1258
  -------
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
-
1259
+ integral_value : `float`
1260
+ The computed value of the specified integral
1261
+
1158
1262
  See Also
1159
1263
  --------
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.
1264
+ `integrate` : Integrate a spline with respect to one of its independent variables, returning
1265
+ the resulting spline.
1164
1266
 
1165
1267
  Notes
1166
1268
  -----
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.
1269
+ This function is very useful for computing mass properties of splines. If the integrand function
1270
+ returns one, then the volume measure of the spline itself is computed (arc length, surface area,
1271
+ volume, etc.). If the integrand function returns one of the dependent variable values, then the
1272
+ integral will be the first moment of the spline with respect to that variable, making it possible
1273
+ to compute centroids and center of mass. The integrand function should be smooth, but is otherwise
1274
+ unrestricted.
1275
+
1276
+ Attempts to compute the integral to two digits less than machine precision.
1171
1277
  """
1172
- return bspy._spline_evaluation.integral(self, with_respect_to, uvw1, uvw2, returnSpline)
1278
+ return bspy._spline_evaluation.composed_integral(self, integrand, domain)
1173
1279
 
1174
1280
  def integrate(self, with_respect_to = 0):
1175
1281
  """
@@ -1178,7 +1284,7 @@ class Spline(Manifold):
1178
1284
  Parameters
1179
1285
  ----------
1180
1286
  with_respect_to : integer, optional
1181
- The number of the independent variable to integrate. Default is zero.
1287
+ The index of the independent variable to integrate. Default is zero.
1182
1288
 
1183
1289
  Returns
1184
1290
  -------
@@ -1546,7 +1652,7 @@ class Spline(Manifold):
1546
1652
  dependent variables (instead of one less, as is typical). In that case, the normal represents the null space of
1547
1653
  the matrix formed by the tangents of the spline. If the null space is greater than one dimension, the normal will be zero.
1548
1654
  """
1549
- return bspy._spline_operations.normal_spline(self, indices)
1655
+ return bspy._spline_operations.normal_spline(bspy.spline_block.SplineBlock(self), indices)
1550
1656
 
1551
1657
  @staticmethod
1552
1658
  def point(point):
@@ -1943,6 +2049,35 @@ class Spline(Manifold):
1943
2049
  `multiply` : Multiply two splines together.
1944
2050
  """
1945
2051
  return bspy._spline_fitting.sphere(radius, tolerance)
2052
+
2053
+ def split(self, minContinuity = 0, breaks = None):
2054
+ """
2055
+ Split a spline into separate spline pieces.
2056
+
2057
+ Parameters
2058
+ ----------
2059
+ minContinuity : `int`, optional
2060
+ The minimum expected continuity of each spline piece. The default is zero, for C0 continuity.
2061
+
2062
+ breaks : `iterable` of length `nInd` or `None`, optional
2063
+ An iterable that specifies the breaks at which to separate the spline.
2064
+ len(breaks[ind]) == 0 if there the spline isn't separated for the `ind` independent variable.
2065
+ If breaks is `None` (the default), the spline will only be separated at discontinuities.
2066
+
2067
+ Returns
2068
+ -------
2069
+ splineArray : array of `Spline`
2070
+ A array of splines with nInd dimensions containing the spline pieces.
2071
+
2072
+ See Also
2073
+ --------
2074
+ `join` : Join a list of splines together into a single spline.
2075
+ """
2076
+ splineArray = bspy.spline_block.SplineBlock(self).split(minContinuity, breaks)
2077
+ splines = splineArray.ravel()
2078
+ for i, block in enumerate(splines):
2079
+ splines[i] = block.block[0][0][1]
2080
+ return splines.reshape(splineArray.shape)
1946
2081
 
1947
2082
  def subtract(self, other, indMap = None):
1948
2083
  """
@@ -1985,7 +2120,7 @@ class Spline(Manifold):
1985
2120
 
1986
2121
  def tangent_space(self, uvw):
1987
2122
  """
1988
- Return the tangent space of the spline.
2123
+ Return the tangent space of the spline. (Same as Jacobian.)
1989
2124
 
1990
2125
  Parameters
1991
2126
  ----------
@@ -1997,7 +2132,7 @@ class Spline(Manifold):
1997
2132
  tangentSpace : `numpy.array`
1998
2133
  The nDep x nInd matrix of tangent vectors (tangents are the columns).
1999
2134
  """
2000
- return bspy._spline_evaluation.tangent_space(self, uvw)
2135
+ return bspy._spline_evaluation.jacobian(self, uvw)
2001
2136
 
2002
2137
  def to_dict(self):
2003
2138
  """
@@ -2164,9 +2299,11 @@ class Spline(Manifold):
2164
2299
 
2165
2300
  Returns
2166
2301
  -------
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.
2302
+ trimmedSpline : `Spline`
2303
+ A spline trimmed to the given domain bounds.
2304
+
2305
+ rangeBounds : `np.array`
2306
+ The range of the trimmed spline given as lower and upper bounds on each dependent variable.
2170
2307
 
2171
2308
  See Also
2172
2309
  --------
@@ -2213,7 +2350,7 @@ class Spline(Manifold):
2213
2350
  """
2214
2351
  return bspy._spline_domain.unfold(self, foldedInd, coefficientlessSpline)
2215
2352
 
2216
- def zeros(self, epsilon=None):
2353
+ def zeros(self, epsilon=None, initialScale=None):
2217
2354
  """
2218
2355
  Find the roots of a spline (nInd must match nDep).
2219
2356
 
@@ -2223,6 +2360,10 @@ class Spline(Manifold):
2223
2360
  Tolerance for root precision. The root will be within epsilon of the actual root.
2224
2361
  The default is the machine epsilon.
2225
2362
 
2363
+ initialScale : array-like, optional
2364
+ The initial scale of each dependent variable (as opposed to the current scale of
2365
+ the spline, which may have been normalized). The default is an array of ones (size nDep).
2366
+
2226
2367
  Returns
2227
2368
  -------
2228
2369
  roots : `iterable`
@@ -2233,7 +2374,7 @@ class Spline(Manifold):
2233
2374
  See Also
2234
2375
  --------
2235
2376
  `intersect` : Intersect two splines.
2236
- `contour` : Fit a spline to the contour defined by `F(x) = 0`.
2377
+ `contours` : Find all the contour curves of a spline whose `nInd` is one larger than its `nDep`.
2237
2378
 
2238
2379
  Notes
2239
2380
  -----
@@ -2246,4 +2387,5 @@ class Spline(Manifold):
2246
2387
  if self.nInd <= 1:
2247
2388
  return bspy._spline_intersection.zeros_using_interval_newton(self)
2248
2389
  else:
2249
- return bspy._spline_intersection.zeros_using_projected_polyhedron(self, epsilon)
2390
+ return bspy._spline_intersection.zeros_using_projected_polyhedron(bspy.spline_block.SplineBlock(self), epsilon, initialScale)
2391
+