bspy 4.0__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.0
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
@@ -32,20 +32,31 @@ Requires-Dist: pyopengltk
32
32
  # BSpy
33
33
  Library for manipulating and rendering B-spline curves, surfaces, and multidimensional manifolds with non-uniform knots in each dimension.
34
34
 
35
- The [Spline](https://ericbrec.github.io/BSpy/bspy/spline.html) class has a method to fit multidimensional data for
36
- 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.
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
+
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.
37
39
  Other methods add, subtract, and multiply splines, as well as confine spline curves to a given range.
38
- 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.
45
+
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.
47
+
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.
39
51
 
40
52
  The [SplineOpenGLFrame](https://ericbrec.github.io/BSpy/bspy/splineOpenGLFrame.html) class is an
41
53
  [OpenGLFrame](https://pypi.org/project/pyopengltk/) with custom shaders to render spline curves and surfaces. Spline surfaces with more
42
54
  than 3 dependent variables will have their added dimensions rendered as colors (up to 6 dependent variables are supported). Only tested on Windows systems.
43
55
 
44
-
45
56
  The [Viewer](https://ericbrec.github.io/BSpy/bspy/viewer.html) class is a
46
57
  [tkinter.Tk](https://docs.python.org/3/library/tkinter.html) app that hosts a
47
58
  [SplineOpenGLFrame](https://ericbrec.github.io/BSpy/bspy/splineOpenGLFrame.html),
48
- a listbox full of splines, and a set of controls to adjust and view the selected splines. Only tested on Windows systems.
59
+ a tree view full of solids and splines, and a set of controls to adjust and view the selected solids and splines. Only tested on Windows systems.
49
60
 
50
61
  The [Graphics](https://ericbrec.github.io/BSpy/bspy/viewer.html#Graphics) class is a graphics engine to display splines.
51
62
  It launches a [Viewer](https://ericbrec.github.io/BSpy/bspy/viewer.html) and issues commands to the viewer for use
bspy-4.2/README.md ADDED
@@ -0,0 +1,54 @@
1
+ # BSpy
2
+ Library for manipulating and rendering B-spline curves, surfaces, and multidimensional manifolds with non-uniform knots in each dimension.
3
+
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
+
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
+ 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, 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.
14
+
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.
16
+
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.
20
+
21
+ The [SplineOpenGLFrame](https://ericbrec.github.io/BSpy/bspy/splineOpenGLFrame.html) class is an
22
+ [OpenGLFrame](https://pypi.org/project/pyopengltk/) with custom shaders to render spline curves and surfaces. Spline surfaces with more
23
+ than 3 dependent variables will have their added dimensions rendered as colors (up to 6 dependent variables are supported). Only tested on Windows systems.
24
+
25
+ The [Viewer](https://ericbrec.github.io/BSpy/bspy/viewer.html) class is a
26
+ [tkinter.Tk](https://docs.python.org/3/library/tkinter.html) app that hosts a
27
+ [SplineOpenGLFrame](https://ericbrec.github.io/BSpy/bspy/splineOpenGLFrame.html),
28
+ a tree view full of solids and splines, and a set of controls to adjust and view the selected solids and splines. Only tested on Windows systems.
29
+
30
+ The [Graphics](https://ericbrec.github.io/BSpy/bspy/viewer.html#Graphics) class is a graphics engine to display splines.
31
+ It launches a [Viewer](https://ericbrec.github.io/BSpy/bspy/viewer.html) and issues commands to the viewer for use
32
+ in [jupyter](https://jupyter.org/) notebooks and other scripting environments. Only tested on Windows systems.
33
+
34
+ ![Viewer rendering the Utah teapot](https://ericbrec.github.io/BSpy/viewer.png "Viewer rendering the Utah teapot")
35
+
36
+ The full documentation for BSpy can be found [here](https://ericbrec.github.io/BSpy), its GitHub project can be found
37
+ [here](https://github.com/ericbrec/BSpy), a test suite can be found [here](https://github.com/ericbrec/BSpy/tree/main/tests), and
38
+ a set of examples, including a jupyter notebook, can be found [here](https://github.com/ericbrec/BSpy/tree/main/examples).
39
+
40
+ ### Release 3.0 breaking changes
41
+ * Removed accuracy as a member of Spline
42
+ * Spline.common_basis is now a static method (see documentation for details)
43
+ * Spline.least_squares changed arguments (see documentation for details)
44
+ * Spline.load always returns a list of splines
45
+
46
+ ### Release 4.0 breaking changes
47
+ * Removed Spline blossom method
48
+ * Removed DrawableSpline class
49
+ * Changed bspyApp class name to Viewer
50
+ * Changed Viewer listbox to use extended selection (shift and ctrl keys)
51
+ * Changed bspyGraphics class name to Graphics
52
+ * Moved DrawableSpine methods for adjusting spline appearance to Viewer (see documentation for details)
53
+ * Spline.bspline_values changed arguments (see documentation for details)
54
+ * Spline.intersect changed return values (see documentation for details)
@@ -0,0 +1,29 @@
1
+ """
2
+ BSpy is a python library for manipulating and rendering non-uniform B-splines.
3
+
4
+ Available subpackages
5
+ ---------------------
6
+ `bspy.solid` : Provides the `Solid` and `Boundary` classes that model solids.
7
+
8
+ `bspy.manifold` : Provides the `Manifold` base class for manifolds.
9
+
10
+ `bspy.hyperplane` : Provides the `Hyperplane` subclass of `Manifold` that models hyperplanes.
11
+
12
+ `bspy.spline` : Provides the `Spline` subclass of `Manifold` that models, represents, and processes
13
+ piecewise polynomial tensor product functions (spline functions) as linear combinations of B-splines.
14
+
15
+ `bspy.spline_block` : Provides the `SplineBlock` class that represents and processes an array-like collection of splines.
16
+
17
+ `bspy.splineOpenGLFrame` : Provides the `SplineOpenGLFrame` class, a tkinter `OpenGLFrame` with shaders to display splines.
18
+
19
+ `bspy.viewer` : Provides the `Viewer` tkinter app (`tkinter.Tk`) that hosts a `SplineOpenGLFrame`, a listbox full of
20
+ splines, and a set of controls to adjust and view the selected splines. It also provides the `Graphics` engine that creates
21
+ an associated `Viewer`, allowing you to script splines and display them in the viewer.
22
+ """
23
+ from bspy.solid import Solid, Boundary
24
+ from bspy.manifold import Manifold
25
+ from bspy.hyperplane import Hyperplane
26
+ from bspy.spline import Spline
27
+ from bspy.spline_block import SplineBlock
28
+ from bspy.splineOpenGLFrame import SplineOpenGLFrame
29
+ from bspy.viewer import Viewer, Graphics
@@ -1,4 +1,5 @@
1
1
  import numpy as np
2
+ from bspy.manifold import Manifold
2
3
 
3
4
  def clamp(self, left, right):
4
5
  bounds = [[None, None] for i in range(self.nInd)]
@@ -517,6 +518,66 @@ def reverse(self, variable = 0):
517
518
  newFolded = type(self)(folded.nInd, folded.nDep, folded.order, folded.nCoef, (newKnots,), newCoefs, folded.metadata)
518
519
  return newFolded.unfold(myIndices, basisInfo)
519
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
+
520
581
  def transpose(self, axes=None):
521
582
  if axes is None:
522
583
  axes = range(self.nInd)[::-1]
@@ -534,10 +595,12 @@ def transpose(self, axes=None):
534
595
  def trim(self, newDomain):
535
596
  if not(len(newDomain) == self.nInd): raise ValueError("Invalid newDomain")
536
597
  if self.nInd < 1: return self
537
- 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
538
600
 
539
601
  # Step 1: Determine the knots to insert at the new domain bounds.
540
602
  newKnotsList = []
603
+ noChange = True
541
604
  for (order, knots, bounds) in zip(self.order, self.knots, newDomain):
542
605
  if not(len(bounds) == 2): raise ValueError("Invalid newDomain")
543
606
  unique, counts = np.unique(knots, return_counts=True)
@@ -547,28 +610,49 @@ def trim(self, newDomain):
547
610
  if not np.isnan(bounds[0]):
548
611
  if not(knots[order - 1] <= bounds[0] <= knots[-order]): raise ValueError("Invalid newDomain")
549
612
  leftBound = True
550
- multiplicity = order
551
613
  i = np.searchsorted(unique, bounds[0])
552
- if unique[i] == bounds[0]:
553
- 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
+
554
627
  newKnots += multiplicity * [bounds[0]]
555
628
 
556
629
  if not np.isnan(bounds[1]):
557
630
  if not(knots[order - 1] <= bounds[1] <= knots[-order]): raise ValueError("Invalid newDomain")
558
631
  if leftBound:
559
632
  if not(bounds[0] < bounds[1]): raise ValueError("Invalid newDomain")
560
- multiplicity = order
561
633
  i = np.searchsorted(unique, bounds[1])
562
- if unique[i] == bounds[1]:
563
- 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
564
645
  newKnots += multiplicity * [bounds[1]]
565
646
 
566
647
  newKnotsList.append(newKnots)
648
+ if len(newKnots) > 0:
649
+ noChange = False
650
+
651
+ if noChange:
652
+ return self
567
653
 
568
654
  # Step 2: Insert the knots.
569
655
  spline = self.insert_knots(newKnotsList)
570
- if spline is self:
571
- return spline
572
656
 
573
657
  # Step 3: Trim the knots and coefficients.
574
658
  knotsList = []
@@ -582,6 +666,14 @@ def trim(self, newDomain):
582
666
 
583
667
  return type(spline)(spline.nInd, spline.nDep, spline.order, coefs.shape[1:], knotsList, coefs, spline.metadata)
584
668
 
669
+ def trimmed_range_bounds(self, domainBounds):
670
+ domainBounds = np.array(domainBounds, copy=True)
671
+ for original, trim in zip(self.domain(), domainBounds):
672
+ trim[0] = max(original[0], trim[0] - Manifold.minSeparation)
673
+ trim[1] = min(original[1], trim[1] + Manifold.minSeparation)
674
+ trimmedSpline = self.trim(domainBounds)
675
+ return trimmedSpline, trimmedSpline.range_bounds()
676
+
585
677
  def unfold(self, foldedInd, coefficientlessSpline):
586
678
  if not(len(foldedInd) == coefficientlessSpline.nInd): raise ValueError("Invalid coefficientlessSpline")
587
679
  unfoldedOrder = []
@@ -1,11 +1,14 @@
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)
5
- basis[-1] = 1.0
6
6
  if knot is None:
7
7
  knot = np.searchsorted(knots, u, side = 'right')
8
8
  knot = min(knot, len(knots) - splineOrder)
9
+ if derivativeOrder >= splineOrder:
10
+ return knot, basis
11
+ basis[-1] = 1.0
9
12
  for degree in range(1, splineOrder - derivativeOrder):
10
13
  b = splineOrder - degree
11
14
  for i in range(knot - degree, knot):
@@ -23,10 +26,61 @@ def bspline_values(knot, knots, splineOrder, u, derivativeOrder = 0, taylorCoefs
23
26
  b += 1
24
27
  return knot, basis
25
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
+
26
80
  def curvature(self, uv):
81
+ if self.nDep == 1:
82
+ self = self.graph()
27
83
  if self.nInd == 1:
28
- if self.nDep == 1:
29
- self = self.graph()
30
84
  fp = self.derivative([1], uv)
31
85
  fpp = self.derivative([2], uv)
32
86
  fpDotFp = fp @ fp
@@ -36,7 +90,21 @@ def curvature(self, uv):
36
90
  numerator = fp[0] * fpp[1] - fp[1] * fpp[0]
37
91
  else:
38
92
  numerator = np.sqrt((fpp @ fpp) * fpDotFp - fpDotFpp ** 2)
39
- 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)
40
108
 
41
109
  def derivative(self, with_respect_to, uvw):
42
110
  # Make work for scalar valued functions
@@ -105,6 +173,8 @@ def greville(self, ind = 0):
105
173
  for ix in range(1, self.order[ind]):
106
174
  knotAverages = knotAverages + myKnots[ix : ix + self.nCoef[ind]]
107
175
  knotAverages /= (self.order[ind] - 1)
176
+ domain = self.domain()[ind]
177
+ knotAverages = np.minimum(domain[1], np.maximum(domain[0], knotAverages))
108
178
  return knotAverages
109
179
 
110
180
  def integral(self, with_respect_to, uvw1, uvw2, returnSpline = False):
@@ -148,31 +218,26 @@ def normal(self, uvw, normalize=True, indices=None):
148
218
 
149
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.")
150
220
 
151
- # Evaluate the tangents at the point.
152
- tangentSpace = np.empty((self.nInd, self.nDep), self.coefs.dtype)
153
- with_respect_to = [0] * self.nInd
154
- for i in range(self.nInd):
155
- with_respect_to[i] = 1
156
- tangentSpace[i] = self.derivative(with_respect_to, uvw)
157
- with_respect_to[i] = 0
221
+ # Evaluate the Jacobian at the point.
222
+ tangentSpace = self.jacobian(uvw)
158
223
 
159
224
  # Record the larger dimension and ensure it comes first.
160
225
  if self.nInd > self.nDep:
161
226
  nDep = self.nInd
227
+ tangentSpace = tangentSpace.T
162
228
  else:
163
229
  nDep = self.nDep
164
- tangentSpace = tangentSpace.T
165
230
 
166
231
  # Compute the normal using cofactors (determinants of subsets of the tangent space).
167
- sign = 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
168
234
  if indices is None:
169
235
  indices = range(nDep)
170
- normal = np.empty(nDep, self.coefs.dtype)
236
+ normal = np.empty(nDep, dtype)
171
237
  else:
172
- normal = np.empty(len(indices), self.coefs.dtype)
238
+ normal = np.empty(len(indices), dtype)
173
239
  for i in indices:
174
- normal[i] = sign * np.linalg.det(tangentSpace[[j for j in range(nDep) if i != j]])
175
- sign *= -1
240
+ normal[i] = sign * ((-1) ** i) * np.linalg.det(tangentSpace[[j for j in range(nDep) if i != j]])
176
241
 
177
242
  # Normalize the result as needed.
178
243
  if normalize: