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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: bspy
3
- Version: 4.0
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 listbox full of splines, and a set of controls to adjust and view the selected splines. Only tested on Windows systems.
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
@@ -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 listbox full of splines, and a set of controls to adjust and view the selected splines. Only tested on Windows systems.
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 = 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
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
- z.append(0.0)
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
- rhos, gaussWeights = _legendre_polynomial_zeros(degree - 1)
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 rho in reversed(rhos):
124
- tValues[i] = t + 0.5 * dt * (1.0 - rho)
125
- GSamples[i] = 0.5 * (previousPoint + point - rho * (point - previousPoint))
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 rho in reversed(rhos):
257
- tValues[i] = t = 0.5 * (previousKnot + newKnot - rho * (newKnot - previousKnot))
258
- GSamples[i] = x = compactCoefs.T @ bspy.Spline.bspline_values(ix, knots, order, t)[1]
259
- FSamplesNorm = max(FSamplesNorm, np.linalg.norm(F(coefsMin + x * coefsMaxMinusMin), np.inf))
260
- i += 1
261
- for rho in rhos[0 if degree % 2 == 1 else 1:]:
262
- tValues[i] = t = 0.5 * (previousKnot + newKnot + rho * (newKnot - previousKnot))
263
- GSamples[i] = x = compactCoefs.T @ bspy.Spline.bspline_values(ix, knots, order, t)[1]
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[0, 0] = max(uvDomain[0, 0], min(uvDomain[0, 1], u[0, 0]))
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
- R_uuu = suu @ su
427
- R_uuv = suu @ sv
428
- R_uvu = suv @ su
429
- R_uvv = suv @ sv
430
- R_vvu = svv @ su
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
- rhoCrit = (math.sqrt(1.0 + 4.0 * (r0 + r1)) - 1.0) / (2.0 * (r0 + r1))
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
- alpha = 0.5 * (1.0 - gaussNode)
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
- continuationBest = bestGuess
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 = continuationBest
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
- continuationBest = bestGuess
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