bspy 4.2__py3-none-any.whl → 4.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
bspy/__init__.py CHANGED
@@ -12,7 +12,7 @@ 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.
15
+ `bspy.spline_block` : Provides the `SplineBlock` class that processes an array-like collection of splines which represent a system of equations.
16
16
 
17
17
  `bspy.splineOpenGLFrame` : Provides the `SplineOpenGLFrame` class, a tkinter `OpenGLFrame` with shaders to display splines.
18
18
 
bspy/_spline_domain.py CHANGED
@@ -310,26 +310,73 @@ def fold(self, foldedInd):
310
310
  coefficientlessSpline = type(self)(len(coefficientlessOrder), 0, coefficientlessOrder, coefficientlessNCoef, coefficientlessKnots, coefficientlessCoefs, self.metadata)
311
311
  return foldedSpline, coefficientlessSpline
312
312
 
313
- def insert_knots(self, newKnots):
314
- if not(len(newKnots) == self.nInd): raise ValueError("Invalid newKnots")
315
- knotsList = list(self.knots)
316
- coefs = self.coefs
317
- for ind, (order, knots) in enumerate(zip(self.order, self.knots)):
318
- # We can't reference self.nCoef[ind] in this loop because we are expanding the knots and coefs arrays.
319
- for knot in newKnots[ind]:
320
- if knot < knots[order-1] or knot > knots[-order]:
321
- raise ValueError(f"Knot insertion outside domain: {knot}")
322
- if knot == knots[-order]:
323
- position = len(knots) - order
313
+ def insert_knots(self, newKnotList):
314
+ if not(len(newKnotList) == self.nInd): raise ValueError("Invalid newKnots")
315
+ knotsList = list(self.knots) # Create a new knot list
316
+ coefs = self.coefs # Set initial value for coefs to check later if it's changed
317
+
318
+ # Insert new knots into each independent variable.
319
+ for ind, (order, knots, newKnots) in enumerate(zip(self.order, self.knots, newKnotList)):
320
+ coefs = coefs.swapaxes(0, ind + 1) # Swap dependent and independent variable (swap back later)
321
+ degree = order - 1
322
+ for knot in newKnots:
323
+ # Determine new knot multiplicity.
324
+ if np.isscalar(knot):
325
+ multiplicity = 1
324
326
  else:
325
- position = np.searchsorted(knots, knot, 'right')
326
- coefs = coefs.swapaxes(0, ind + 1) # Swap dependent and independent variable (swap back later)
327
- newCoefs = np.insert(coefs, position - 1, 0.0, axis=0)
328
- for i in range(position - order + 1, position):
329
- alpha = (knot - knots[i]) / (knots[i + order - 1] - knots[i])
330
- newCoefs[i] = (1.0 - alpha) * coefs[i - 1] + alpha * coefs[i]
331
- knotsList[ind] = knots = np.insert(knots, position, knot)
332
- coefs = newCoefs.swapaxes(0, ind + 1)
327
+ multiplicity = knot[1]
328
+ knot = knot[0]
329
+ if multiplicity < 1:
330
+ continue
331
+
332
+ # Check if knot and its total multiplicity is valid.
333
+ if knot < knots[degree] or knot > knots[-order]:
334
+ raise ValueError(f"Knot insertion outside domain: {knot}")
335
+ position = np.searchsorted(knots, knot, 'right')
336
+ oldMultiplicity = 0
337
+ for k in knots[position - 1::-1]:
338
+ if knot == k:
339
+ oldMultiplicity += 1
340
+ else:
341
+ break
342
+ if oldMultiplicity + multiplicity > order:
343
+ raise ValueError("Knot multiplicity > order")
344
+
345
+ # Initialize oldCoefs and expanded coefs array with multiplicity new coefficients, as well as some indices.
346
+ oldCoefs = coefs[position - order:position].copy()
347
+ lastKnotIndex = position - oldMultiplicity
348
+ firstCoefIndex = position - degree
349
+ coefs = np.insert(coefs, firstCoefIndex, oldCoefs[:multiplicity], axis=0)
350
+ # Compute inserted coefficients (multiplicity of them) and the degree - oldMultiplicity - 1 number of changed coefficients.
351
+ for j in range(multiplicity):
352
+ # Allocate new coefficients for the current multiplicity.
353
+ size = degree - oldMultiplicity - j
354
+ if size < 1:
355
+ # Full multiplicity knot, so use oldCoefs.
356
+ coefs[firstCoefIndex + j] = oldCoefs[0]
357
+ else:
358
+ # Otherwise, allocate space for newCoefs.
359
+ newCoefs = np.empty((size, *coefs.shape[1:]), coefs.dtype)
360
+
361
+ # Compute the new coefficients.
362
+ for i, k in zip(range(size), range(lastKnotIndex - size, lastKnotIndex)):
363
+ alpha = (knot - knots[k]) / (knots[k + degree - j] - knots[k])
364
+ newCoefs[i] = (1.0 - alpha) * oldCoefs[i] + alpha * oldCoefs[i + 1]
365
+
366
+ # Assign the ends of the new coefficients into their respective positions.
367
+ coefs[firstCoefIndex + j] = newCoefs[0]
368
+ if size > 1:
369
+ coefs[lastKnotIndex + multiplicity - j - 2] = newCoefs[-1]
370
+ oldCoefs = newCoefs
371
+
372
+ # Assign remaining computed coefficients (the ones in the middle).
373
+ if size > 2:
374
+ coefs[firstCoefIndex + multiplicity:firstCoefIndex + multiplicity + size - 2] = newCoefs[1:-1]
375
+
376
+ # Insert the inserted coefficients and inserted knots.
377
+ knotsList[ind] = knots = np.insert(knots, position, (knot,) * multiplicity)
378
+
379
+ coefs = coefs.swapaxes(0, ind + 1) # Swap back
333
380
 
334
381
  if self.coefs is coefs:
335
382
  return self
@@ -518,66 +565,6 @@ def reverse(self, variable = 0):
518
565
  newFolded = type(self)(folded.nInd, folded.nDep, folded.order, folded.nCoef, (newKnots,), newCoefs, folded.metadata)
519
566
  return newFolded.unfold(myIndices, basisInfo)
520
567
 
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
-
581
568
  def transpose(self, axes=None):
582
569
  if axes is None:
583
570
  axes = range(self.nInd)[::-1]
@@ -614,17 +601,14 @@ def trim(self, newDomain):
614
601
  if unique[i] - bounds[0] < epsilon:
615
602
  bounds[0] = unique[i]
616
603
  multiplicity = order - counts[i]
617
- if i > 0:
618
- noChange = False
619
604
  elif i > 0 and bounds[0] - unique[i - 1] < epsilon:
620
605
  bounds[0] = unique[i - 1]
621
606
  multiplicity = order - counts[i - 1]
622
- if i - 1 > 0:
623
- noChange = False
624
607
  else:
625
608
  multiplicity = order
626
-
627
- newKnots += multiplicity * [bounds[0]]
609
+ if multiplicity > 0:
610
+ newKnots.append((bounds[0], multiplicity))
611
+ noChange = False
628
612
 
629
613
  if not np.isnan(bounds[1]):
630
614
  if not(knots[order - 1] <= bounds[1] <= knots[-order]): raise ValueError("Invalid newDomain")
@@ -634,19 +618,16 @@ def trim(self, newDomain):
634
618
  if unique[i] - bounds[1] < epsilon:
635
619
  bounds[1] = unique[i]
636
620
  multiplicity = order - counts[i]
637
- if i < len(unique) - 1:
638
- noChange = False
639
621
  elif i > 0 and bounds[1] - unique[i - 1] < epsilon:
640
622
  bounds[1] = unique[i - 1]
641
623
  multiplicity = order - counts[i - i]
642
- noChange = False # i < len(unique) - 1
643
624
  else:
644
625
  multiplicity = order
645
- newKnots += multiplicity * [bounds[1]]
626
+ if multiplicity > 0:
627
+ newKnots.append((bounds[1], multiplicity))
628
+ noChange = False
646
629
 
647
630
  newKnotsList.append(newKnots)
648
- if len(newKnots) > 0:
649
- noChange = False
650
631
 
651
632
  if noChange:
652
633
  return self
@@ -236,8 +236,8 @@ def normal(self, uvw, normalize=True, indices=None):
236
236
  normal = np.empty(nDep, dtype)
237
237
  else:
238
238
  normal = np.empty(len(indices), dtype)
239
- for i in indices:
240
- normal[i] = sign * ((-1) ** i) * np.linalg.det(tangentSpace[[j for j in range(nDep) if i != j]])
239
+ for ix, i in enumerate(indices):
240
+ normal[ix] = sign * ((-1) ** i) * np.linalg.det(tangentSpace[[j for j in range(nDep) if i != j]])
241
241
 
242
242
  # Normalize the result as needed.
243
243
  if normalize:
bspy/_spline_fitting.py CHANGED
@@ -904,11 +904,11 @@ def solve_ode(self, nLeft, nRight, FAndF_u, tolerance = 1.0e-6, args = ()):
904
904
  for i, knot in enumerate(uniqueKnots):
905
905
  howMany = currentGuess.order[0] - indices[i + 1] + indices[i] - nOrder
906
906
  if howMany > 0:
907
- knotsToAdd += howMany * [knot]
907
+ knotsToAdd.append((knot, howMany))
908
908
  if howMany < 0:
909
- knotsToRemove += [[knot, abs(howMany)]]
909
+ knotsToRemove.append((knot, abs(howMany)))
910
910
  currentGuess = currentGuess.insert_knots([knotsToAdd])
911
- for [knot, howMany] in knotsToRemove:
911
+ for (knot, howMany) in knotsToRemove:
912
912
  ix = np.searchsorted(currentGuess.knots[0], knot, side = 'left')
913
913
  for iy in range(howMany):
914
914
  currentGuess, residual = currentGuess.remove_knot(ix, nLeft, nRight)
@@ -1065,7 +1065,7 @@ def solve_ode(self, nLeft, nRight, FAndF_u, tolerance = 1.0e-6, args = ()):
1065
1065
  knotsToAdd = []
1066
1066
  for knot0, knot1 in zip(currentGuess.knots[0][:-1], currentGuess.knots[0][1:]):
1067
1067
  if knot0 < knot1:
1068
- knotsToAdd += (currentGuess.order[0] - nOrder) * [0.5 * (knot0 + knot1)]
1068
+ knotsToAdd.append((0.5 * (knot0 + knot1), currentGuess.order[0] - nOrder))
1069
1069
  previousGuess = currentGuess
1070
1070
  currentGuess = currentGuess.insert_knots([knotsToAdd])
1071
1071