bspy 4.0__tar.gz → 4.1__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.
- {bspy-4.0 → bspy-4.1}/PKG-INFO +8 -3
- {bspy-4.0 → bspy-4.1}/README.md +7 -2
- bspy-4.1/bspy/__init__.py +26 -0
- {bspy-4.0 → bspy-4.1}/bspy/_spline_domain.py +9 -0
- {bspy-4.0 → bspy-4.1}/bspy/_spline_evaluation.py +16 -10
- {bspy-4.0 → bspy-4.1}/bspy/_spline_fitting.py +56 -82
- {bspy-4.0 → bspy-4.1}/bspy/_spline_intersection.py +187 -23
- {bspy-4.0 → bspy-4.1}/bspy/_spline_operations.py +8 -2
- bspy-4.1/bspy/hyperplane.py +540 -0
- bspy-4.1/bspy/manifold.py +391 -0
- bspy-4.1/bspy/solid.py +839 -0
- {bspy-4.0 → bspy-4.1}/bspy/spline.py +176 -27
- {bspy-4.0 → bspy-4.1}/bspy/splineOpenGLFrame.py +262 -14
- {bspy-4.0 → bspy-4.1}/bspy/viewer.py +130 -87
- {bspy-4.0 → bspy-4.1}/bspy.egg-info/PKG-INFO +8 -3
- {bspy-4.0 → bspy-4.1}/bspy.egg-info/SOURCES.txt +2 -0
- {bspy-4.0 → bspy-4.1}/setup.cfg +1 -1
- bspy-4.0/bspy/__init__.py +0 -16
- bspy-4.0/bspy/manifold.py +0 -88
- {bspy-4.0 → bspy-4.1}/LICENSE +0 -0
- {bspy-4.0 → bspy-4.1}/bspy.egg-info/dependency_links.txt +0 -0
- {bspy-4.0 → bspy-4.1}/bspy.egg-info/requires.txt +0 -0
- {bspy-4.0 → bspy-4.1}/bspy.egg-info/top_level.txt +0 -0
- {bspy-4.0 → bspy-4.1}/pyproject.toml +0 -0
{bspy-4.0 → bspy-4.1}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: bspy
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.1
|
|
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,25 @@ 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 [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
|
+
|
|
35
37
|
The [Spline](https://ericbrec.github.io/BSpy/bspy/spline.html) class has a method to fit multidimensional data for
|
|
36
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
39
|
Other methods add, subtract, and multiply splines, as well as confine spline curves to a given range.
|
|
38
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.
|
|
39
41
|
|
|
42
|
+
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
|
+
|
|
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.
|
|
45
|
+
|
|
40
46
|
The [SplineOpenGLFrame](https://ericbrec.github.io/BSpy/bspy/splineOpenGLFrame.html) class is an
|
|
41
47
|
[OpenGLFrame](https://pypi.org/project/pyopengltk/) with custom shaders to render spline curves and surfaces. Spline surfaces with more
|
|
42
48
|
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
49
|
|
|
44
|
-
|
|
45
50
|
The [Viewer](https://ericbrec.github.io/BSpy/bspy/viewer.html) class is a
|
|
46
51
|
[tkinter.Tk](https://docs.python.org/3/library/tkinter.html) app that hosts a
|
|
47
52
|
[SplineOpenGLFrame](https://ericbrec.github.io/BSpy/bspy/splineOpenGLFrame.html),
|
|
48
|
-
a
|
|
53
|
+
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
54
|
|
|
50
55
|
The [Graphics](https://ericbrec.github.io/BSpy/bspy/viewer.html#Graphics) class is a graphics engine to display splines.
|
|
51
56
|
It launches a [Viewer](https://ericbrec.github.io/BSpy/bspy/viewer.html) and issues commands to the viewer for use
|
{bspy-4.0 → bspy-4.1}/README.md
RENAMED
|
@@ -1,20 +1,25 @@
|
|
|
1
1
|
# BSpy
|
|
2
2
|
Library for manipulating and rendering B-spline curves, surfaces, and multidimensional manifolds with non-uniform knots in each dimension.
|
|
3
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
|
+
|
|
4
6
|
The [Spline](https://ericbrec.github.io/BSpy/bspy/spline.html) class has a method to fit multidimensional data for
|
|
5
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
8
|
Other methods add, subtract, and multiply splines, as well as confine spline curves to a given range.
|
|
7
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.
|
|
8
10
|
|
|
11
|
+
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
|
+
|
|
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.
|
|
14
|
+
|
|
9
15
|
The [SplineOpenGLFrame](https://ericbrec.github.io/BSpy/bspy/splineOpenGLFrame.html) class is an
|
|
10
16
|
[OpenGLFrame](https://pypi.org/project/pyopengltk/) with custom shaders to render spline curves and surfaces. Spline surfaces with more
|
|
11
17
|
than 3 dependent variables will have their added dimensions rendered as colors (up to 6 dependent variables are supported). Only tested on Windows systems.
|
|
12
18
|
|
|
13
|
-
|
|
14
19
|
The [Viewer](https://ericbrec.github.io/BSpy/bspy/viewer.html) class is a
|
|
15
20
|
[tkinter.Tk](https://docs.python.org/3/library/tkinter.html) app that hosts a
|
|
16
21
|
[SplineOpenGLFrame](https://ericbrec.github.io/BSpy/bspy/splineOpenGLFrame.html),
|
|
17
|
-
a
|
|
22
|
+
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.
|
|
18
23
|
|
|
19
24
|
The [Graphics](https://ericbrec.github.io/BSpy/bspy/viewer.html#Graphics) class is a graphics engine to display splines.
|
|
20
25
|
It launches a [Viewer](https://ericbrec.github.io/BSpy/bspy/viewer.html) and issues commands to the viewer for use
|
|
@@ -0,0 +1,26 @@
|
|
|
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.splineOpenGLFrame` : Provides the `SplineOpenGLFrame` class, a tkinter `OpenGLFrame` with shaders to display splines.
|
|
16
|
+
|
|
17
|
+
`bspy.viewer` : Provides the `Viewer` tkinter app (`tkinter.Tk`) that hosts a `SplineOpenGLFrame`, a listbox full of
|
|
18
|
+
splines, and a set of controls to adjust and view the selected splines. It also provides the `Graphics` engine that creates
|
|
19
|
+
an associated `Viewer`, allowing you to script splines and display them in the viewer.
|
|
20
|
+
"""
|
|
21
|
+
from bspy.solid import Solid, Boundary
|
|
22
|
+
from bspy.manifold import Manifold
|
|
23
|
+
from bspy.hyperplane import Hyperplane
|
|
24
|
+
from bspy.spline import Spline
|
|
25
|
+
from bspy.splineOpenGLFrame import SplineOpenGLFrame
|
|
26
|
+
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)]
|
|
@@ -582,6 +583,14 @@ def trim(self, newDomain):
|
|
|
582
583
|
|
|
583
584
|
return type(spline)(spline.nInd, spline.nDep, spline.order, coefs.shape[1:], knotsList, coefs, spline.metadata)
|
|
584
585
|
|
|
586
|
+
def trimmed_range_bounds(self, domainBounds):
|
|
587
|
+
domainBounds = np.array(domainBounds, copy=True)
|
|
588
|
+
for original, trim in zip(self.domain(), domainBounds):
|
|
589
|
+
trim[0] = max(original[0], trim[0] - Manifold.minSeparation)
|
|
590
|
+
trim[1] = min(original[1], trim[1] + Manifold.minSeparation)
|
|
591
|
+
trimmedSpline = self.trim(domainBounds)
|
|
592
|
+
return trimmedSpline, trimmedSpline.range_bounds()
|
|
593
|
+
|
|
585
594
|
def unfold(self, foldedInd, coefficientlessSpline):
|
|
586
595
|
if not(len(foldedInd) == coefficientlessSpline.nInd): raise ValueError("Invalid coefficientlessSpline")
|
|
587
596
|
unfoldedOrder = []
|
|
@@ -2,10 +2,12 @@ import numpy as np
|
|
|
2
2
|
|
|
3
3
|
def bspline_values(knot, knots, splineOrder, u, derivativeOrder = 0, taylorCoefs = False):
|
|
4
4
|
basis = np.zeros(splineOrder, knots.dtype)
|
|
5
|
-
basis[-1] = 1.0
|
|
6
5
|
if knot is None:
|
|
7
6
|
knot = np.searchsorted(knots, u, side = 'right')
|
|
8
7
|
knot = min(knot, len(knots) - splineOrder)
|
|
8
|
+
if derivativeOrder >= splineOrder:
|
|
9
|
+
return knot, basis
|
|
10
|
+
basis[-1] = 1.0
|
|
9
11
|
for degree in range(1, splineOrder - derivativeOrder):
|
|
10
12
|
b = splineOrder - degree
|
|
11
13
|
for i in range(knot - degree, knot):
|
|
@@ -149,22 +151,17 @@ def normal(self, uvw, normalize=True, indices=None):
|
|
|
149
151
|
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
152
|
|
|
151
153
|
# Evaluate the tangents at the point.
|
|
152
|
-
tangentSpace =
|
|
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
|
|
154
|
+
tangentSpace = self.tangent_space(uvw)
|
|
158
155
|
|
|
159
156
|
# Record the larger dimension and ensure it comes first.
|
|
160
157
|
if self.nInd > self.nDep:
|
|
161
158
|
nDep = self.nInd
|
|
159
|
+
tangentSpace = tangentSpace.T
|
|
162
160
|
else:
|
|
163
161
|
nDep = self.nDep
|
|
164
|
-
tangentSpace = tangentSpace.T
|
|
165
162
|
|
|
166
163
|
# Compute the normal using cofactors (determinants of subsets of the tangent space).
|
|
167
|
-
sign = 1
|
|
164
|
+
sign = -1 if self.metadata.get("flipNormal", False) else 1
|
|
168
165
|
if indices is None:
|
|
169
166
|
indices = range(nDep)
|
|
170
167
|
normal = np.empty(nDep, self.coefs.dtype)
|
|
@@ -183,4 +180,13 @@ def normal(self, uvw, normalize=True, indices=None):
|
|
|
183
180
|
def range_bounds(self):
|
|
184
181
|
# Assumes self.nDep is the first value in self.coefs.shape
|
|
185
182
|
bounds = [[coefficient.min(), coefficient.max()] for coefficient in self.coefs]
|
|
186
|
-
return np.array(bounds, self.coefs.dtype)
|
|
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
|
|
@@ -23,7 +23,7 @@ def cone(radius1, radius2, height, tolerance = None):
|
|
|
23
23
|
return bspy.Spline.ruled_surface(bottom, top)
|
|
24
24
|
|
|
25
25
|
# Courtesy of Michael Epton - Translated from his F77 code lgnzro
|
|
26
|
-
def _legendre_polynomial_zeros(degree):
|
|
26
|
+
def _legendre_polynomial_zeros(degree, mapToZeroOne = True):
|
|
27
27
|
def legendre(degree, x):
|
|
28
28
|
p = [1.0, x]
|
|
29
29
|
pd = [0.0, 1.0]
|
|
@@ -35,6 +35,7 @@ def _legendre_polynomial_zeros(degree):
|
|
|
35
35
|
return p, pd
|
|
36
36
|
zval = 1.0
|
|
37
37
|
z = []
|
|
38
|
+
zNegative = []
|
|
38
39
|
for iRoot in range(degree // 2):
|
|
39
40
|
done = False
|
|
40
41
|
while True:
|
|
@@ -49,21 +50,28 @@ def _legendre_polynomial_zeros(degree):
|
|
|
49
50
|
if dz < 1.0e-10:
|
|
50
51
|
done = True
|
|
51
52
|
z.append(zval)
|
|
53
|
+
zNegative.append(-zval)
|
|
52
54
|
zval -= 0.001
|
|
53
55
|
if degree % 2 == 1:
|
|
54
|
-
|
|
56
|
+
zNegative.append(0.0)
|
|
55
57
|
z.reverse()
|
|
58
|
+
z = np.array(zNegative + z)
|
|
56
59
|
w = []
|
|
57
60
|
for zval in z:
|
|
58
61
|
p, pd = legendre(degree, zval)
|
|
59
62
|
w.append(2.0 / ((1.0 - zval ** 2) * pd[-1] ** 2))
|
|
63
|
+
w = np.array(w)
|
|
64
|
+
if mapToZeroOne:
|
|
65
|
+
z = 0.5 * (1.0 + z)
|
|
66
|
+
w = 0.5 * w
|
|
60
67
|
return z, w
|
|
61
68
|
|
|
62
69
|
def contour(F, knownXValues, dF = None, epsilon = None, metadata = {}):
|
|
63
70
|
# Set up parameters for initial guess of x(t) and validate arguments.
|
|
64
71
|
order = 4
|
|
65
72
|
degree = order - 1
|
|
66
|
-
|
|
73
|
+
gaussNodes, gaussWeights = _legendre_polynomial_zeros(degree - 1)
|
|
74
|
+
|
|
67
75
|
if not(len(knownXValues) >= 2): raise ValueError("There must be at least 2 known x values.")
|
|
68
76
|
m = len(knownXValues) - 1
|
|
69
77
|
nCoef = m * (degree - 1) + 2
|
|
@@ -96,6 +104,7 @@ def contour(F, knownXValues, dF = None, epsilon = None, metadata = {}):
|
|
|
96
104
|
wrt[i] = 1
|
|
97
105
|
return F.derivative(wrt, x)
|
|
98
106
|
dF.append(splineDerivative)
|
|
107
|
+
FDomain = F.domain().T
|
|
99
108
|
else:
|
|
100
109
|
for i in range(nDep):
|
|
101
110
|
def fDerivative(x, i=i):
|
|
@@ -107,6 +116,7 @@ def contour(F, knownXValues, dF = None, epsilon = None, metadata = {}):
|
|
|
107
116
|
xShift[i] += h2
|
|
108
117
|
return (np.array(F(xShift)) - fLeft) / h2
|
|
109
118
|
dF.append(fDerivative)
|
|
119
|
+
FDomain = np.array(nDep * [[-np.inf, np.inf]]).T
|
|
110
120
|
else:
|
|
111
121
|
if not(len(dF) == nDep): raise ValueError(f"Must provide {nDep} first derivatives.")
|
|
112
122
|
|
|
@@ -120,13 +130,9 @@ def contour(F, knownXValues, dF = None, epsilon = None, metadata = {}):
|
|
|
120
130
|
for point in knownXValues[1:]:
|
|
121
131
|
dt = np.linalg.norm(point - previousPoint)
|
|
122
132
|
if not(dt > epsilon): raise ValueError("Points must be separated by at least epsilon.")
|
|
123
|
-
for
|
|
124
|
-
tValues[i] = t +
|
|
125
|
-
GSamples[i] = 0
|
|
126
|
-
i += 1
|
|
127
|
-
for rho in rhos[0 if degree % 2 == 1 else 1:]:
|
|
128
|
-
tValues[i] = t + 0.5 * dt * (1.0 + rho)
|
|
129
|
-
GSamples[i] = 0.5 * (previousPoint + point + rho * (point - previousPoint))
|
|
133
|
+
for gaussNode in gaussNodes:
|
|
134
|
+
tValues[i] = t + gaussNode * dt
|
|
135
|
+
GSamples[i] = (1.0 - gaussNode) * previousPoint + gaussNode * point
|
|
130
136
|
i += 1
|
|
131
137
|
t += dt
|
|
132
138
|
knots += [t] * (order - 2)
|
|
@@ -184,6 +190,7 @@ def contour(F, knownXValues, dF = None, epsilon = None, metadata = {}):
|
|
|
184
190
|
|
|
185
191
|
# Do the same for F(x(t)).
|
|
186
192
|
x = coefsMin + (compactCoefs.T @ bValues) * coefsMaxMinusMin
|
|
193
|
+
x = np.maximum(FDomain[0], np.minimum(FDomain[1], x))
|
|
187
194
|
FValues = F(x)
|
|
188
195
|
FSamplesNorm = max(FSamplesNorm, np.linalg.norm(FValues, np.inf))
|
|
189
196
|
if previousFSamplesNorm > 0.0 and FSamplesNorm > previousFSamplesNorm * (1.0 - evaluationEpsilon):
|
|
@@ -253,26 +260,14 @@ def contour(F, knownXValues, dF = None, epsilon = None, metadata = {}):
|
|
|
253
260
|
newKnot = 0.5 * (previousKnot + knot)
|
|
254
261
|
|
|
255
262
|
# Place tValues at Gauss points for the intervals [previousKnot, newKnot] and [newKnot, knot].
|
|
256
|
-
for
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
FSamplesNorm = max(FSamplesNorm, np.linalg.norm(F(coefsMin + x * coefsMaxMinusMin), np.inf))
|
|
265
|
-
i += 1
|
|
266
|
-
for rho in reversed(rhos):
|
|
267
|
-
tValues[i] = t = 0.5 * (newKnot + knot - rho * (knot - newKnot))
|
|
268
|
-
GSamples[i] = x = compactCoefs.T @ bspy.Spline.bspline_values(ix, knots, order, t)[1]
|
|
269
|
-
FSamplesNorm = max(FSamplesNorm, np.linalg.norm(F(coefsMin + x * coefsMaxMinusMin), np.inf))
|
|
270
|
-
i += 1
|
|
271
|
-
for rho in rhos[0 if degree % 2 == 1 else 1:]:
|
|
272
|
-
tValues[i] = t = 0.5 * (newKnot + knot + rho * (knot - newKnot))
|
|
273
|
-
GSamples[i] = x = compactCoefs.T @ bspy.Spline.bspline_values(ix, knots, order, t)[1]
|
|
274
|
-
FSamplesNorm = max(FSamplesNorm, np.linalg.norm(F(coefsMin + x * coefsMaxMinusMin), np.inf))
|
|
275
|
-
i += 1
|
|
263
|
+
for knotInterval in [[previousKnot, newKnot], [newKnot, knot]]:
|
|
264
|
+
for gaussNode in gaussNodes:
|
|
265
|
+
tValues[i] = t = (1.0 - gaussNode) * knotInterval[0] + gaussNode * knotInterval[1]
|
|
266
|
+
x = compactCoefs.T @ bspy.Spline.bspline_values(ix, knots, order, t)[1]
|
|
267
|
+
x = np.array([max(0.0, min(1.0, xi)) for xi in x])
|
|
268
|
+
GSamples[i] = x
|
|
269
|
+
FSamplesNorm = max(FSamplesNorm, np.linalg.norm(F(coefsMin + x * coefsMaxMinusMin), np.inf))
|
|
270
|
+
i += 1
|
|
276
271
|
|
|
277
272
|
newKnots += [newKnot] * (order - 2) # C1 continuity
|
|
278
273
|
newKnots += [knot] * (order - 2) # C1 continuity
|
|
@@ -408,68 +403,47 @@ def geodesic(self, uvStart, uvEnd, tolerance = 1.0e-6):
|
|
|
408
403
|
# Define the callback function for the ODE solver
|
|
409
404
|
def geodesicCallback(t, u, surface, uvDomain):
|
|
410
405
|
# Evaluate the surface information needed for the Christoffel symbols
|
|
411
|
-
u[
|
|
412
|
-
u[1, 0] = max(uvDomain[1, 0], min(uvDomain[1, 1], u[1, 0]))
|
|
406
|
+
u[:, 0] = np.maximum(uvDomain[:, 0], np.minimum(uvDomain[:, 1], u[:, 0]))
|
|
413
407
|
su = surface.derivative([1, 0], u[:, 0])
|
|
414
408
|
sv = surface.derivative([0, 1], u[:, 0])
|
|
415
409
|
suu = surface.derivative([2, 0], u[:, 0])
|
|
416
410
|
suv = surface.derivative([1, 1], u[:, 0])
|
|
417
411
|
svv = surface.derivative([0, 2], u[:, 0])
|
|
412
|
+
suuu = surface.derivative([3, 0], u[:, 0])
|
|
413
|
+
suuv = surface.derivative([2, 1], u[:, 0])
|
|
414
|
+
suvv = surface.derivative([1, 2], u[:, 0])
|
|
415
|
+
svvv = surface.derivative([0, 3], u[:, 0])
|
|
418
416
|
|
|
419
|
-
# Calculate the first fundamental form
|
|
417
|
+
# Calculate the first fundamental form and derivatives
|
|
420
418
|
E = su @ su
|
|
419
|
+
E_u = 2.0 * suu @ su
|
|
420
|
+
E_v = 2.0 * suv @ su
|
|
421
421
|
F = su @ sv
|
|
422
|
+
F_u = suu @ sv + suv @ su
|
|
423
|
+
F_v = suv @ sv + svv @ su
|
|
422
424
|
G = sv @ sv
|
|
425
|
+
G_u = 2.0 * suv @ sv
|
|
426
|
+
G_v = 2.0 * svv @ sv
|
|
423
427
|
A = np.array([[E, F], [F, G]])
|
|
428
|
+
A_u = np.array([[E_u, F_u], [F_u, G_u]])
|
|
429
|
+
A_v = np.array([[E_v, F_v], [F_v, G_v]])
|
|
424
430
|
|
|
425
431
|
# Compute right hand side entries
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
R_vvv = svv @ sv
|
|
432
|
-
R = np.array([[R_uuu, R_uvu, R_vvu], [R_uuv, R_uvv, R_vvv]])
|
|
432
|
+
R = np.array([[suu @ su, suv @ su, svv @ su], [suu @ sv, suv @ sv, svv @ sv]])
|
|
433
|
+
R_u = np.array([[suuu @ su + suu @ suu, suuv @ su + suv @ suu, suvv @ su + svv @ suu],
|
|
434
|
+
[suuu @ sv + suu @ suv, suuv @ sv + suv @ suv, suvv @ sv + svv @ suv]])
|
|
435
|
+
R_v = np.array([[suuv @ su + suu @ suv, suvv @ su + suv @ suv, suvv @ su + svv @ suv],
|
|
436
|
+
[suuv @ sv + suu @ svv, suvv @ sv + suv @ svv, svvv @ sv + svv @ svv]])
|
|
433
437
|
|
|
434
438
|
# Solve for the Christoffel symbols
|
|
435
439
|
luAndPivot = sp.linalg.lu_factor(A)
|
|
436
440
|
Gamma = sp.linalg.lu_solve(luAndPivot, R)
|
|
441
|
+
Gamma_u = sp.linalg.lu_solve(luAndPivot, R_u - A_u @ Gamma)
|
|
442
|
+
Gamma_v = sp.linalg.lu_solve(luAndPivot, R_v - A_v @ Gamma)
|
|
437
443
|
|
|
438
444
|
# Compute the right hand side for the ODE
|
|
439
445
|
rhs = -np.array([Gamma[0, 0] * u[0, 1] ** 2 + 2.0 * Gamma[0, 1] * u[0, 1] * u[1, 1] + Gamma[0, 2] * u[1, 1] ** 2,
|
|
440
446
|
Gamma[1, 0] * u[0, 1] ** 2 + 2.0 * Gamma[1, 1] * u[0, 1] * u[1, 1] + Gamma[1, 2] * u[1, 1] ** 2])
|
|
441
|
-
|
|
442
|
-
# Now we need the derivatives of the Christoffel symbols with respect to u
|
|
443
|
-
suuu = surface.derivative([3, 0], u[:, 0])
|
|
444
|
-
suuv = surface.derivative([2, 1], u[:, 0])
|
|
445
|
-
suvv = surface.derivative([1, 2], u[:, 0])
|
|
446
|
-
svvv = surface.derivative([0, 3], u[:, 0])
|
|
447
|
-
E_u = 2.0 * R_uuu
|
|
448
|
-
F_u = R_uuv + R_uvu
|
|
449
|
-
G_u = 2.0 * R_uvv
|
|
450
|
-
A_u = np.array([[E_u, F_u], [F_u, G_u]])
|
|
451
|
-
R_uuu_u = suuu @ su + suu @ suu
|
|
452
|
-
R_uuv_u = suuu @ sv + suu @ suv
|
|
453
|
-
R_uvu_u = suuv @ su + suv @ suu
|
|
454
|
-
R_uvv_u = suuv @ sv + suv @ suv
|
|
455
|
-
R_vvu_u = suvv @ su + svv @ suu
|
|
456
|
-
R_vvv_u = suvv @ sv + svv @ suv
|
|
457
|
-
R_u = np.array([[R_uuu_u, R_uvu_u, R_vvu_u], [R_uuv_u, R_uvv_u, R_vvv_u]])
|
|
458
|
-
Gamma_u = sp.linalg.lu_solve(luAndPivot, R_u - A_u @ Gamma)
|
|
459
|
-
|
|
460
|
-
# . . . And the derivatives of the Christoffel symbols with respect to v
|
|
461
|
-
E_v = 2.0 * R_uvu
|
|
462
|
-
F_v = R_uvv + R_vvu
|
|
463
|
-
G_v = 2.0 * R_vvv
|
|
464
|
-
A_v = np.array([[E_v, F_v], [F_v, G_v]])
|
|
465
|
-
R_uuu_v = suuv @ su + suu @ suv
|
|
466
|
-
R_uuv_v = suuv @ sv + suu @ svv
|
|
467
|
-
R_uvu_v = suvv @ su + suv @ suv
|
|
468
|
-
R_uvv_v = suvv @ sv + suv @ svv
|
|
469
|
-
R_vvu_v = svvv @ su + svv @ suv
|
|
470
|
-
R_vvv_v = svvv @ sv + svv @ svv
|
|
471
|
-
R_v = np.array([[R_uuu_v, R_uvu_v, R_vvu_v], [R_uuv_v, R_uvv_v, R_vvv_v]])
|
|
472
|
-
Gamma_v = sp.linalg.lu_solve(luAndPivot, R_v - A_v @ Gamma)
|
|
473
447
|
|
|
474
448
|
# Compute the Jacobian matrix of the right hand side of the ODE
|
|
475
449
|
jacobian = -np.array([[[Gamma_u[0, 0] * u[0, 1] ** 2 + 2.0 * Gamma_u[0, 1] * u[0, 1] * u[1, 1] + Gamma_u[0, 2] * u[1, 1] ** 2,
|
|
@@ -707,7 +681,10 @@ def section(xytk):
|
|
|
707
681
|
onePlusCosTheta = 1.0 + math.cos(theta)
|
|
708
682
|
r0 = 4.0 * startKappa * tangentDistances[0] ** 2 / (3.0 * tangentDistances[1] * crossTangents)
|
|
709
683
|
r1 = 4.0 * endKappa * tangentDistances[1] ** 2 / (3.0 * tangentDistances[0] * crossTangents)
|
|
710
|
-
|
|
684
|
+
if r0 != 0.0 or r1 != 0.0:
|
|
685
|
+
rhoCrit = (math.sqrt(1.0 + 4.0 * (r0 + r1)) - 1.0) / (2.0 * (r0 + r1))
|
|
686
|
+
else:
|
|
687
|
+
rhoCrit = 1.0
|
|
711
688
|
rhoCritOfTheta = 3.0 * (math.sqrt(1.0 + 32.0 / (3.0 * onePlusCosTheta)) - 1.0) * onePlusCosTheta / 16.0
|
|
712
689
|
|
|
713
690
|
# Determine quadratic polynomial
|
|
@@ -797,11 +774,7 @@ def solve_ode(self, nLeft, nRight, FAndF_u, tolerance = 1.0e-6, args = ()):
|
|
|
797
774
|
for knot0, knot1 in zip(currentGuess.knots[0][:-1], currentGuess.knots[0][1:]):
|
|
798
775
|
if knot0 < knot1:
|
|
799
776
|
for gaussNode in gaussNodes:
|
|
800
|
-
|
|
801
|
-
collocationPoints.append((1.0 - alpha) * knot0 + alpha * knot1)
|
|
802
|
-
if abs(gaussNode) > 1.0e-5:
|
|
803
|
-
collocationPoints.append(alpha * knot0 + (1.0 - alpha) * knot1)
|
|
804
|
-
collocationPoints = np.sort(collocationPoints)
|
|
777
|
+
collocationPoints.append((1.0 - gaussNode) * knot0 + gaussNode * knot1)
|
|
805
778
|
n = nDep * currentGuess.nCoef[0]
|
|
806
779
|
bestGuess = np.reshape(currentGuess.coefs.T, (n,))
|
|
807
780
|
|
|
@@ -826,7 +799,7 @@ def solve_ode(self, nLeft, nRight, FAndF_u, tolerance = 1.0e-6, args = ()):
|
|
|
826
799
|
done = linear
|
|
827
800
|
continuation = 1.0
|
|
828
801
|
bestContinuation = 0.0
|
|
829
|
-
|
|
802
|
+
inCaseOfEmergency = bestGuess.copy()
|
|
830
803
|
previous = 0.5 * np.finfo(bestGuess[0]).max
|
|
831
804
|
iteration = 0
|
|
832
805
|
|
|
@@ -869,9 +842,10 @@ def solve_ode(self, nLeft, nRight, FAndF_u, tolerance = 1.0e-6, args = ()):
|
|
|
869
842
|
update = sp.linalg.solve_banded((bandWidth, bandWidth), collocationMatrix, residuals)
|
|
870
843
|
bestGuess[nDep * (iFirstPoint + nLeft) : nDep * (iNextPoint + nLeft)] += update[nDep * nLeft : nDep * (iNextPoint - iFirstPoint + nLeft)]
|
|
871
844
|
updateSize = np.linalg.norm(update)
|
|
872
|
-
if updateSize > 1.25 * previous and iteration >= 4
|
|
845
|
+
if updateSize > 1.25 * previous and iteration >= 4 or \
|
|
846
|
+
updateSize > 0.01 and iteration > 50:
|
|
873
847
|
continuation = 0.5 * (continuation + bestContinuation)
|
|
874
|
-
bestGuess =
|
|
848
|
+
bestGuess = inCaseOfEmergency.copy()
|
|
875
849
|
if continuation - bestContinuation < 0.01:
|
|
876
850
|
break
|
|
877
851
|
previous = 0.5 * np.finfo(bestGuess[0]).max
|
|
@@ -886,7 +860,7 @@ def solve_ode(self, nLeft, nRight, FAndF_u, tolerance = 1.0e-6, args = ()):
|
|
|
886
860
|
if updateSize < math.sqrt(n) * scale * math.sqrt(np.finfo(update.dtype).eps):
|
|
887
861
|
if continuation < 1.0:
|
|
888
862
|
bestContinuation = continuation
|
|
889
|
-
|
|
863
|
+
inCaseOfEmergency = bestGuess.copy()
|
|
890
864
|
continuation = min(1.0, 1.2 * continuation)
|
|
891
865
|
previous = 0.5 * np.finfo(bestGuess[0]).max
|
|
892
866
|
iteration = 0
|