bspy 4.1__tar.gz → 4.2__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: bspy
3
- Version: 4.1
3
+ Version: 4.2
4
4
  Summary: Library for manipulating and rendering non-uniform B-splines
5
5
  Home-page: http://github.com/ericbrec/BSpy
6
6
  Author: Eric Brechner
@@ -34,14 +34,20 @@ Library for manipulating and rendering B-spline curves, surfaces, and multidimen
34
34
 
35
35
  The [Manifold](https://ericbrec.github.io/BSpy/bspy/manifold.html) abstract base class for [Hyperplane](https://ericbrec.github.io/BSpy/bspy/hyperplane.html) and [Spline](https://ericbrec.github.io/BSpy/bspy/spline.html).
36
36
 
37
- The [Spline](https://ericbrec.github.io/BSpy/bspy/spline.html) class has a method to fit multidimensional data for
38
- scalar and vector functions of single and multiple variables. It also has methods to create points, lines, circular arcs, spheres, cones, cylinders, tori, ruled surfaces, surfaces of revolution, and four-sided patches.
37
+ The [Spline](https://ericbrec.github.io/BSpy/bspy/spline.html) class has a method to fit multidimensional data for scalar and vector functions of single and multiple variables. It also can fit splines to functions, to solutions for ordinary differential equations (ODEs), and to geodesics.
38
+ Spline has methods to create points, lines, circular arcs, spheres, cones, cylinders, tori, ruled surfaces, surfaces of revolution, and four-sided patches.
39
39
  Other methods add, subtract, and multiply splines, as well as confine spline curves to a given range.
40
- There are methods to evaluate spline values, derivatives, integrals, normals, curvature, and the Jacobian, as well as methods that return spline representations of derivatives, normals, integrals, graphs, and convolutions. In addition, there are methods to manipulate the domain of splines, including trim, join, reparametrize, transpose, reverse, add and remove knots, elevate and extrapolate, and fold and unfold. There are methods to manipulate the range of splines, including dot product, cross product, translate, rotate, scale, and transform. Finally, there are methods to compute the zeros and contours of a spline and to intersect two splines.
40
+ There are methods to evaluate spline values, derivatives, normals, integrals, continuity, curvature, and the Jacobian, as well as methods that return spline representations of derivatives, normals, integrals, graphs, and convolutions.
41
+ In addition, there are methods to manipulate the domain of splines, including trim, join, split, reparametrize, transpose, reverse, add and remove knots, elevate and extrapolate, and fold and unfold.
42
+ There are methods to manipulate the range of splines, including dot product, cross product, translate, rotate, scale, and transform.
43
+ Finally, there are methods to compute the zeros and contours of a spline and to intersect two splines.
44
+ Splines can be saved and loaded in json format.
41
45
 
42
46
  The [Hyperplane](https://ericbrec.github.io/BSpy/bspy/hyperplane.html) class has methods to create individual hyperplanes in any dimension, along with axis-aligned hyperplanes and hypercubes.
43
47
 
44
- The [Solid](https://ericbrec.github.io/BSpy/bspy/solid.html) class has methods to construct n-dimensional solids from trimmed [Manifold](https://ericbrec.github.io/BSpy/bspy/manifold.html) boundaries. Each solid consists of a list of boundaries and a Boolean value that indicates if the solid contains infinity. Each [Boundary](https://ericbrec.github.io/BSpy/bspy/solid.html) consists of a manifold (currently a [Hyperplane](https://ericbrec.github.io/BSpy/bspy/hyperplane.html) or [Spline](https://ericbrec.github.io/BSpy/bspy/spline.html)) and a domain solid that trims the manifold. Solids have methods to form the intersection, union, difference, and complement of solids. There are methods to compute point containment, winding numbers, surface integrals, and volume integrals. There are also methods to translate, transform, and slice solids.
48
+ The [Solid](https://ericbrec.github.io/BSpy/bspy/solid.html) class has methods to construct n-dimensional solids from trimmed [Manifold](https://ericbrec.github.io/BSpy/bspy/manifold.html) boundaries. Each solid consists of a list of boundaries and a Boolean value that indicates if the solid contains infinity. Each [Boundary](https://ericbrec.github.io/BSpy/bspy/solid.html) consists of a manifold (currently a [Hyperplane](https://ericbrec.github.io/BSpy/bspy/hyperplane.html) or [Spline](https://ericbrec.github.io/BSpy/bspy/spline.html)) and a domain solid that trims the manifold. Solids have methods to form the intersection, union, difference, and complement of solids. There are methods to compute point containment, winding numbers, surface integrals, and volume integrals. There are also methods to translate, transform, and slice solids. Solids can be saved and loaded in json format.
49
+
50
+ The [SplineBlock](https://ericbrec.github.io/BSpy/bspy/spline_block.html) class has methods to represent and process an array-like collection of splines, including ones to compute the contours and zeros of a spline block, as well as a variety of methods to evaluate a spline block and its derivatives. Spline blocks are useful for efficiently manipulating and solving systems of equations with splines.
45
51
 
46
52
  The [SplineOpenGLFrame](https://ericbrec.github.io/BSpy/bspy/splineOpenGLFrame.html) class is an
47
53
  [OpenGLFrame](https://pypi.org/project/pyopengltk/) with custom shaders to render spline curves and surfaces. Spline surfaces with more
@@ -3,14 +3,20 @@ Library for manipulating and rendering B-spline curves, surfaces, and multidimen
3
3
 
4
4
  The [Manifold](https://ericbrec.github.io/BSpy/bspy/manifold.html) abstract base class for [Hyperplane](https://ericbrec.github.io/BSpy/bspy/hyperplane.html) and [Spline](https://ericbrec.github.io/BSpy/bspy/spline.html).
5
5
 
6
- The [Spline](https://ericbrec.github.io/BSpy/bspy/spline.html) class has a method to fit multidimensional data for
7
- scalar and vector functions of single and multiple variables. It also has methods to create points, lines, circular arcs, spheres, cones, cylinders, tori, ruled surfaces, surfaces of revolution, and four-sided patches.
6
+ The [Spline](https://ericbrec.github.io/BSpy/bspy/spline.html) class has a method to fit multidimensional data for scalar and vector functions of single and multiple variables. It also can fit splines to functions, to solutions for ordinary differential equations (ODEs), and to geodesics.
7
+ Spline has methods to create points, lines, circular arcs, spheres, cones, cylinders, tori, ruled surfaces, surfaces of revolution, and four-sided patches.
8
8
  Other methods add, subtract, and multiply splines, as well as confine spline curves to a given range.
9
- There are methods to evaluate spline values, derivatives, integrals, normals, curvature, and the Jacobian, as well as methods that return spline representations of derivatives, normals, integrals, graphs, and convolutions. In addition, there are methods to manipulate the domain of splines, including trim, join, reparametrize, transpose, reverse, add and remove knots, elevate and extrapolate, and fold and unfold. There are methods to manipulate the range of splines, including dot product, cross product, translate, rotate, scale, and transform. Finally, there are methods to compute the zeros and contours of a spline and to intersect two splines.
9
+ There are methods to evaluate spline values, derivatives, normals, integrals, continuity, curvature, and the Jacobian, as well as methods that return spline representations of derivatives, normals, integrals, graphs, and convolutions.
10
+ In addition, there are methods to manipulate the domain of splines, including trim, join, split, reparametrize, transpose, reverse, add and remove knots, elevate and extrapolate, and fold and unfold.
11
+ There are methods to manipulate the range of splines, including dot product, cross product, translate, rotate, scale, and transform.
12
+ Finally, there are methods to compute the zeros and contours of a spline and to intersect two splines.
13
+ Splines can be saved and loaded in json format.
10
14
 
11
15
  The [Hyperplane](https://ericbrec.github.io/BSpy/bspy/hyperplane.html) class has methods to create individual hyperplanes in any dimension, along with axis-aligned hyperplanes and hypercubes.
12
16
 
13
- The [Solid](https://ericbrec.github.io/BSpy/bspy/solid.html) class has methods to construct n-dimensional solids from trimmed [Manifold](https://ericbrec.github.io/BSpy/bspy/manifold.html) boundaries. Each solid consists of a list of boundaries and a Boolean value that indicates if the solid contains infinity. Each [Boundary](https://ericbrec.github.io/BSpy/bspy/solid.html) consists of a manifold (currently a [Hyperplane](https://ericbrec.github.io/BSpy/bspy/hyperplane.html) or [Spline](https://ericbrec.github.io/BSpy/bspy/spline.html)) and a domain solid that trims the manifold. Solids have methods to form the intersection, union, difference, and complement of solids. There are methods to compute point containment, winding numbers, surface integrals, and volume integrals. There are also methods to translate, transform, and slice solids.
17
+ The [Solid](https://ericbrec.github.io/BSpy/bspy/solid.html) class has methods to construct n-dimensional solids from trimmed [Manifold](https://ericbrec.github.io/BSpy/bspy/manifold.html) boundaries. Each solid consists of a list of boundaries and a Boolean value that indicates if the solid contains infinity. Each [Boundary](https://ericbrec.github.io/BSpy/bspy/solid.html) consists of a manifold (currently a [Hyperplane](https://ericbrec.github.io/BSpy/bspy/hyperplane.html) or [Spline](https://ericbrec.github.io/BSpy/bspy/spline.html)) and a domain solid that trims the manifold. Solids have methods to form the intersection, union, difference, and complement of solids. There are methods to compute point containment, winding numbers, surface integrals, and volume integrals. There are also methods to translate, transform, and slice solids. Solids can be saved and loaded in json format.
18
+
19
+ The [SplineBlock](https://ericbrec.github.io/BSpy/bspy/spline_block.html) class has methods to represent and process an array-like collection of splines, including ones to compute the contours and zeros of a spline block, as well as a variety of methods to evaluate a spline block and its derivatives. Spline blocks are useful for efficiently manipulating and solving systems of equations with splines.
14
20
 
15
21
  The [SplineOpenGLFrame](https://ericbrec.github.io/BSpy/bspy/splineOpenGLFrame.html) class is an
16
22
  [OpenGLFrame](https://pypi.org/project/pyopengltk/) with custom shaders to render spline curves and surfaces. Spline surfaces with more
@@ -12,6 +12,8 @@ Available subpackages
12
12
  `bspy.spline` : Provides the `Spline` subclass of `Manifold` that models, represents, and processes
13
13
  piecewise polynomial tensor product functions (spline functions) as linear combinations of B-splines.
14
14
 
15
+ `bspy.spline_block` : Provides the `SplineBlock` class that represents and processes an array-like collection of splines.
16
+
15
17
  `bspy.splineOpenGLFrame` : Provides the `SplineOpenGLFrame` class, a tkinter `OpenGLFrame` with shaders to display splines.
16
18
 
17
19
  `bspy.viewer` : Provides the `Viewer` tkinter app (`tkinter.Tk`) that hosts a `SplineOpenGLFrame`, a listbox full of
@@ -22,5 +24,6 @@ from bspy.solid import Solid, Boundary
22
24
  from bspy.manifold import Manifold
23
25
  from bspy.hyperplane import Hyperplane
24
26
  from bspy.spline import Spline
27
+ from bspy.spline_block import SplineBlock
25
28
  from bspy.splineOpenGLFrame import SplineOpenGLFrame
26
29
  from bspy.viewer import Viewer, Graphics
@@ -518,6 +518,66 @@ def reverse(self, variable = 0):
518
518
  newFolded = type(self)(folded.nInd, folded.nDep, folded.order, folded.nCoef, (newKnots,), newCoefs, folded.metadata)
519
519
  return newFolded.unfold(myIndices, basisInfo)
520
520
 
521
+ def split(self, minContinuity = 0, breaks = None):
522
+ if minContinuity < 0: raise ValueError("minContinuity must be >= 0")
523
+ if breaks is not None and len(breaks) != self.nInd: raise ValueError("Invalid breaks")
524
+ if self.nInd < 1: return self
525
+
526
+ # Step 1: Determine the knots to insert.
527
+ newKnotsList = []
528
+ for i, order, knots in zip(range(self.nInd), self.order, self.knots):
529
+ unique, counts = np.unique(knots, return_counts=True)
530
+ newKnots = []
531
+ for knot, count in zip(unique, counts):
532
+ assert count <= order
533
+ if count > order - 1 - minContinuity:
534
+ newKnots += [knot] * (order - count)
535
+ if breaks is not None:
536
+ for knot in breaks[i]:
537
+ if knot not in unique:
538
+ newKnots += [knot] * order
539
+ newKnotsList.append(newKnots)
540
+
541
+ # Step 2: Insert the knots.
542
+ spline = self.insert_knots(newKnotsList)
543
+ if spline is self:
544
+ return np.full((1,) * spline.nInd, spline)
545
+
546
+ # Step 3: Store the indices of the full order knots.
547
+ indexList = []
548
+ splineCount = []
549
+ totalSplineCount = 1
550
+ for order, knots in zip(spline.order, spline.knots):
551
+ unique, counts = np.unique(knots, return_counts=True)
552
+ indices = np.searchsorted(knots, unique)
553
+ fullOrder = []
554
+ for ix, count in zip(indices, counts):
555
+ if count == order:
556
+ fullOrder.append(ix)
557
+ indexList.append(fullOrder)
558
+ splines = len(fullOrder) - 1
559
+ splineCount.append(splines)
560
+ totalSplineCount *= splines
561
+
562
+ # Step 4: Slice up the spline.
563
+ splineArray = np.empty(totalSplineCount, object)
564
+ for i in range(totalSplineCount):
565
+ knotsList = []
566
+ coefIndex = [slice(None)] # First index is for nDep
567
+ ix = i
568
+ for order, knots, splines, indices in zip(spline.order, spline.knots, splineCount, indexList):
569
+ j = ix % splines
570
+ ix = ix // splines
571
+ leftIndex = indices[j]
572
+ rightIndex = indices[j + 1]
573
+ knotsList.append(knots[leftIndex:rightIndex + order])
574
+ coefIndex.append(slice(leftIndex, rightIndex))
575
+ coefs = spline.coefs[tuple(coefIndex)]
576
+ splineArray[i] = type(spline)(spline.nInd, spline.nDep, spline.order, coefs.shape[1:], knotsList, coefs, spline.metadata)
577
+
578
+ # Return the transpose because we put the splines into splineArray dimensions in reverse order.
579
+ return splineArray.reshape(tuple(reversed(splineCount))).T
580
+
521
581
  def transpose(self, axes=None):
522
582
  if axes is None:
523
583
  axes = range(self.nInd)[::-1]
@@ -535,10 +595,12 @@ def transpose(self, axes=None):
535
595
  def trim(self, newDomain):
536
596
  if not(len(newDomain) == self.nInd): raise ValueError("Invalid newDomain")
537
597
  if self.nInd < 1: return self
538
- newDomain = np.array(newDomain, self.knots[0].dtype) # Force dtype and convert None to nan
598
+ newDomain = np.array(newDomain, self.knots[0].dtype, copy=True) # Force dtype and convert None to nan
599
+ epsilon = np.finfo(newDomain.dtype).eps
539
600
 
540
601
  # Step 1: Determine the knots to insert at the new domain bounds.
541
602
  newKnotsList = []
603
+ noChange = True
542
604
  for (order, knots, bounds) in zip(self.order, self.knots, newDomain):
543
605
  if not(len(bounds) == 2): raise ValueError("Invalid newDomain")
544
606
  unique, counts = np.unique(knots, return_counts=True)
@@ -548,28 +610,49 @@ def trim(self, newDomain):
548
610
  if not np.isnan(bounds[0]):
549
611
  if not(knots[order - 1] <= bounds[0] <= knots[-order]): raise ValueError("Invalid newDomain")
550
612
  leftBound = True
551
- multiplicity = order
552
613
  i = np.searchsorted(unique, bounds[0])
553
- if unique[i] == bounds[0]:
554
- multiplicity -= counts[i]
614
+ if unique[i] - bounds[0] < epsilon:
615
+ bounds[0] = unique[i]
616
+ multiplicity = order - counts[i]
617
+ if i > 0:
618
+ noChange = False
619
+ elif i > 0 and bounds[0] - unique[i - 1] < epsilon:
620
+ bounds[0] = unique[i - 1]
621
+ multiplicity = order - counts[i - 1]
622
+ if i - 1 > 0:
623
+ noChange = False
624
+ else:
625
+ multiplicity = order
626
+
555
627
  newKnots += multiplicity * [bounds[0]]
556
628
 
557
629
  if not np.isnan(bounds[1]):
558
630
  if not(knots[order - 1] <= bounds[1] <= knots[-order]): raise ValueError("Invalid newDomain")
559
631
  if leftBound:
560
632
  if not(bounds[0] < bounds[1]): raise ValueError("Invalid newDomain")
561
- multiplicity = order
562
633
  i = np.searchsorted(unique, bounds[1])
563
- if unique[i] == bounds[1]:
564
- multiplicity -= counts[i]
634
+ if unique[i] - bounds[1] < epsilon:
635
+ bounds[1] = unique[i]
636
+ multiplicity = order - counts[i]
637
+ if i < len(unique) - 1:
638
+ noChange = False
639
+ elif i > 0 and bounds[1] - unique[i - 1] < epsilon:
640
+ bounds[1] = unique[i - 1]
641
+ multiplicity = order - counts[i - i]
642
+ noChange = False # i < len(unique) - 1
643
+ else:
644
+ multiplicity = order
565
645
  newKnots += multiplicity * [bounds[1]]
566
646
 
567
647
  newKnotsList.append(newKnots)
648
+ if len(newKnots) > 0:
649
+ noChange = False
650
+
651
+ if noChange:
652
+ return self
568
653
 
569
654
  # Step 2: Insert the knots.
570
655
  spline = self.insert_knots(newKnotsList)
571
- if spline is self:
572
- return spline
573
656
 
574
657
  # Step 3: Trim the knots and coefficients.
575
658
  knotsList = []
@@ -1,4 +1,5 @@
1
1
  import numpy as np
2
+ import scipy as sp
2
3
 
3
4
  def bspline_values(knot, knots, splineOrder, u, derivativeOrder = 0, taylorCoefs = False):
4
5
  basis = np.zeros(splineOrder, knots.dtype)
@@ -25,10 +26,61 @@ def bspline_values(knot, knots, splineOrder, u, derivativeOrder = 0, taylorCoefs
25
26
  b += 1
26
27
  return knot, basis
27
28
 
29
+ def composed_integral(self, integrand = None, domain = None):
30
+ # Determine domain and check its validity
31
+ actualDomain = self.domain()
32
+ if domain is None:
33
+ domain = actualDomain
34
+ else:
35
+ for iInd in range(self.nInd):
36
+ if domain[iInd, 0] < actualDomain[iInd, 0] or \
37
+ domain[iInd, 1] > actualDomain[iInd, 1]:
38
+ raise ValueError("Can't integrate beyond the domain of the spline")
39
+
40
+ # Determine breakpoints for quadrature intervals; require functions to be analytic
41
+
42
+ uniqueKnots = []
43
+ for iInd in range(self.nInd):
44
+ iStart = np.searchsorted(self.knots[iInd], domain[iInd, 0], side = 'right')
45
+ iEnd = np.searchsorted(self.knots[iInd], domain[iInd, 1], side = 'right')
46
+ uniqueKnots.append(np.unique(np.insert(self.knots[iInd], [iStart, iEnd], domain[iInd])[iStart : iEnd + 2]))
47
+
48
+ # Set integrand function if none is given
49
+ if integrand is None:
50
+ integrand = lambda x : 1.0
51
+
52
+ # Set tolerance
53
+ tolerance = 1.0e-13 / self.nInd
54
+
55
+ # Establish the callback function
56
+ def composedIntegrand(u, nIndSoFar, uValues):
57
+ uValues[nIndSoFar] = u
58
+ nIndSoFar += 1
59
+ if self.nInd == nIndSoFar:
60
+ total = integrand(self(uValues)) * \
61
+ np.prod(np.linalg.svd(self.jacobian(uValues), compute_uv = False))
62
+ else:
63
+ total = 0.0
64
+ for ix in range(len(uniqueKnots[nIndSoFar]) - 1):
65
+ value = sp.integrate.quad(composedIntegrand, uniqueKnots[nIndSoFar][ix],
66
+ uniqueKnots[nIndSoFar][ix + 1], (nIndSoFar, uValues),
67
+ epsabs = tolerance, epsrel = tolerance)
68
+ total += value[0]
69
+ return total
70
+
71
+ # Compute the value by calling the callback routine
72
+ total = composedIntegrand(0.0, -1, self.nInd * [0.0])
73
+ return total
74
+
75
+ def continuity(self):
76
+ multiplicity = np.array([np.max(np.unique(knots, return_counts = True)[1][1 : -1]) for knots in self.knots])
77
+ continuity = self.order - multiplicity - 1
78
+ return continuity
79
+
28
80
  def curvature(self, uv):
81
+ if self.nDep == 1:
82
+ self = self.graph()
29
83
  if self.nInd == 1:
30
- if self.nDep == 1:
31
- self = self.graph()
32
84
  fp = self.derivative([1], uv)
33
85
  fpp = self.derivative([2], uv)
34
86
  fpDotFp = fp @ fp
@@ -38,7 +90,21 @@ def curvature(self, uv):
38
90
  numerator = fp[0] * fpp[1] - fp[1] * fpp[0]
39
91
  else:
40
92
  numerator = np.sqrt((fpp @ fpp) * fpDotFp - fpDotFpp ** 2)
41
- return numerator / denom
93
+ return numerator / denom
94
+ if self.nInd == 2:
95
+ su = self.derivative([1, 0], uv)
96
+ sv = self.derivative([0, 1], uv)
97
+ normal = self.normal(uv)
98
+ suu = self.derivative([2, 0], uv)
99
+ suv = self.derivative([1, 1], uv)
100
+ svv = self.derivative([0, 2], uv)
101
+ E = su @ su
102
+ F = su @ sv
103
+ G = sv @ sv
104
+ L = suu @ normal
105
+ M = suv @ normal
106
+ N = svv @ normal
107
+ return (L * N - M ** 2) / (E * G - F ** 2)
42
108
 
43
109
  def derivative(self, with_respect_to, uvw):
44
110
  # Make work for scalar valued functions
@@ -107,6 +173,8 @@ def greville(self, ind = 0):
107
173
  for ix in range(1, self.order[ind]):
108
174
  knotAverages = knotAverages + myKnots[ix : ix + self.nCoef[ind]]
109
175
  knotAverages /= (self.order[ind] - 1)
176
+ domain = self.domain()[ind]
177
+ knotAverages = np.minimum(domain[1], np.maximum(domain[0], knotAverages))
110
178
  return knotAverages
111
179
 
112
180
  def integral(self, with_respect_to, uvw1, uvw2, returnSpline = False):
@@ -150,8 +218,8 @@ def normal(self, uvw, normalize=True, indices=None):
150
218
 
151
219
  if abs(self.nInd - self.nDep) != 1: raise ValueError("The number of independent variables must be one different than the number of dependent variables.")
152
220
 
153
- # Evaluate the tangents at the point.
154
- tangentSpace = self.tangent_space(uvw)
221
+ # Evaluate the Jacobian at the point.
222
+ tangentSpace = self.jacobian(uvw)
155
223
 
156
224
  # Record the larger dimension and ensure it comes first.
157
225
  if self.nInd > self.nDep:
@@ -161,15 +229,15 @@ def normal(self, uvw, normalize=True, indices=None):
161
229
  nDep = self.nDep
162
230
 
163
231
  # Compute the normal using cofactors (determinants of subsets of the tangent space).
164
- sign = -1 if self.metadata.get("flipNormal", False) else 1
232
+ sign = -1 if hasattr(self, "metadata") and self.metadata.get("flipNormal", False) else 1
233
+ dtype = self.coefs.dtype if hasattr(self, "coefs") else self.coefsDtype
165
234
  if indices is None:
166
235
  indices = range(nDep)
167
- normal = np.empty(nDep, self.coefs.dtype)
236
+ normal = np.empty(nDep, dtype)
168
237
  else:
169
- normal = np.empty(len(indices), self.coefs.dtype)
238
+ normal = np.empty(len(indices), dtype)
170
239
  for i in indices:
171
- normal[i] = sign * np.linalg.det(tangentSpace[[j for j in range(nDep) if i != j]])
172
- sign *= -1
240
+ normal[i] = sign * ((-1) ** i) * np.linalg.det(tangentSpace[[j for j in range(nDep) if i != j]])
173
241
 
174
242
  # Normalize the result as needed.
175
243
  if normalize:
@@ -180,13 +248,4 @@ def normal(self, uvw, normalize=True, indices=None):
180
248
  def range_bounds(self):
181
249
  # Assumes self.nDep is the first value in self.coefs.shape
182
250
  bounds = [[coefficient.min(), coefficient.max()] for coefficient in self.coefs]
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
251
+ return np.array(bounds, self.coefs.dtype)