bspy 1.5.0__py3-none-any.whl → 2.1.0__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/_spline_domain.py +88 -159
- bspy/_spline_evaluation.py +15 -0
- bspy/_spline_fitting.py +203 -163
- bspy/_spline_operations.py +40 -13
- bspy/spline.py +238 -96
- {bspy-1.5.0.dist-info → bspy-2.1.0.dist-info}/METADATA +4 -3
- bspy-2.1.0.dist-info/RECORD +15 -0
- {bspy-1.5.0.dist-info → bspy-2.1.0.dist-info}/WHEEL +1 -1
- bspy-1.5.0.dist-info/RECORD +0 -15
- {bspy-1.5.0.dist-info → bspy-2.1.0.dist-info}/LICENSE +0 -0
- {bspy-1.5.0.dist-info → bspy-2.1.0.dist-info}/top_level.txt +0 -0
bspy/_spline_domain.py
CHANGED
|
@@ -49,9 +49,9 @@ def common_basis(self, splines, indMap):
|
|
|
49
49
|
|
|
50
50
|
# Step 3: Elevate and insert missing knots to each spline accordingly.
|
|
51
51
|
alignedSplines = []
|
|
52
|
-
for
|
|
52
|
+
for i, spline in enumerate(splines):
|
|
53
53
|
m = spline.nInd * [0]
|
|
54
|
-
newKnots =
|
|
54
|
+
newKnots = [[] for ix in spline.order]
|
|
55
55
|
for (map, order, multiplicities) in zip(indMap, orders, knots):
|
|
56
56
|
ind = map[i]
|
|
57
57
|
m[ind] = order - spline.order[ind]
|
|
@@ -361,165 +361,95 @@ def join(splineList):
|
|
|
361
361
|
speed1 = np.linalg.norm(workingSpline.derivative([1], workingDomain[1]))
|
|
362
362
|
speed2 = np.linalg.norm(spl.derivative([1], splDomain[0]))
|
|
363
363
|
spl = spl.reparametrize([[workingDomain[1], workingDomain[1] + speed2 * (splDomain[1] - splDomain[0]) / speed1]])
|
|
364
|
-
newKnots = [list(workingSpline.knots[0]
|
|
365
|
-
newCoefs = [list(workingCoefs) + list(splCoefs)
|
|
366
|
-
workingSpline = type(workingSpline)(1, numDep, workingSpline.order, [workingSpline.nCoef[0] + spl.nCoef[0]
|
|
364
|
+
newKnots = [list(workingSpline.knots[0]) + list(spl.knots[0][maxOrder:])]
|
|
365
|
+
newCoefs = [list(workingCoefs) + list(splCoefs) for workingCoefs, splCoefs in zip(workingSpline.coefs, spl.coefs)]
|
|
366
|
+
workingSpline = type(workingSpline)(1, numDep, workingSpline.order, [workingSpline.nCoef[0] + spl.nCoef[0]],
|
|
367
367
|
newKnots, newCoefs, max(workingSpline.accuracy, spl.accuracy), workingSpline.metadata)
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
j = last
|
|
449
|
-
while j - i > removed:
|
|
450
|
-
coefs[i] = temp[i - offset]
|
|
451
|
-
coefs[j] = temp[j - offset]
|
|
452
|
-
i += 1
|
|
453
|
-
j -= 1
|
|
454
|
-
else:
|
|
455
|
-
break # Get out of removed < maxRemovals while-loop
|
|
456
|
-
|
|
457
|
-
first -= 1
|
|
458
|
-
last += 1
|
|
459
|
-
removed += 1
|
|
460
|
-
# End of removed < maxRemovals while-loop.
|
|
461
|
-
|
|
462
|
-
if removed > 0:
|
|
463
|
-
# Knots removed. Shift coefficients down.
|
|
464
|
-
j = firstCoefOut
|
|
465
|
-
i = j
|
|
466
|
-
# Pj thru Pi will be overwritten.
|
|
467
|
-
for k in range(1, removed):
|
|
468
|
-
if k % 2 == 1:
|
|
469
|
-
i += 1
|
|
470
|
-
else:
|
|
471
|
-
j -= 1
|
|
472
|
-
for k in range(i + 1, beforeGap):
|
|
473
|
-
coefs[j] = coefs[k] # shift
|
|
474
|
-
j += 1
|
|
475
|
-
else:
|
|
476
|
-
j = beforeGap + 1
|
|
477
|
-
|
|
478
|
-
if u >= highU:
|
|
479
|
-
gap += removed # No more knots, get out of endless while-loop
|
|
368
|
+
return workingSpline.reparametrize([[0.0, 1.0]]).remove_knots()
|
|
369
|
+
|
|
370
|
+
def remove_knot(self, iKnot):
|
|
371
|
+
if self.nInd != 1: raise ValueError("Must have one independent variable")
|
|
372
|
+
myOrder = self.order[0]
|
|
373
|
+
if iKnot < myOrder or iKnot >= self.nCoef[0]: raise ValueError("Must specify interior knots for removal")
|
|
374
|
+
diag0 = []
|
|
375
|
+
diag1 = []
|
|
376
|
+
rhs = []
|
|
377
|
+
myKnots = self.knots[0]
|
|
378
|
+
thisKnot = myKnots[iKnot]
|
|
379
|
+
|
|
380
|
+
# Form the bidiagonal system
|
|
381
|
+
for ix in range(1, myOrder):
|
|
382
|
+
alpha = (myKnots[iKnot + ix] - thisKnot) / (myKnots[iKnot + ix] - myKnots[iKnot + ix - myOrder])
|
|
383
|
+
diag0.append(alpha)
|
|
384
|
+
diag1.append(1.0 - alpha)
|
|
385
|
+
rhs.append(np.ndarray.copy(self.coefs[:, iKnot - myOrder + ix]))
|
|
386
|
+
|
|
387
|
+
# Adjust right hand side because first and last coefficients are known
|
|
388
|
+
rhs = np.array(rhs)
|
|
389
|
+
rhs[0] -= diag0[0] * self.coefs[:, iKnot - myOrder]
|
|
390
|
+
rhs[-1] -= diag1[-1] * self.coefs[:, iKnot]
|
|
391
|
+
|
|
392
|
+
# Use Givens rotations to factor the matrix and track right hand side
|
|
393
|
+
for ix in range(1, myOrder - 1):
|
|
394
|
+
cos = diag1[ix - 1]
|
|
395
|
+
sin = diag0[ix]
|
|
396
|
+
denom = np.sqrt(cos ** 2 + sin ** 2)
|
|
397
|
+
cos /= denom
|
|
398
|
+
sin /= denom
|
|
399
|
+
diag1[ix - 1] = denom
|
|
400
|
+
diag0[ix - 1] = sin * diag1[ix]
|
|
401
|
+
diag1[ix] *= cos
|
|
402
|
+
tempRow = cos * rhs[ix - 1] + sin * rhs[ix]
|
|
403
|
+
rhs[ix] = cos * rhs[ix] - sin * rhs[ix - 1]
|
|
404
|
+
rhs[ix - 1] = tempRow
|
|
405
|
+
|
|
406
|
+
# Perform back substitution
|
|
407
|
+
rhs[-2] /= diag1[-2]
|
|
408
|
+
for ix in range(1, myOrder - 2):
|
|
409
|
+
rhs[-ix - 2] = (rhs[-ix - 2] - diag0[-ix - 2] * rhs[-ix - 1]) / diag1[-ix - 2]
|
|
410
|
+
|
|
411
|
+
# Create new spline
|
|
412
|
+
newNCoef = [self.nCoef[0] - 1]
|
|
413
|
+
newKnots = [list(self.knots[0][:iKnot]) + list(self.knots[0][iKnot + 1:])]
|
|
414
|
+
newCoefs = []
|
|
415
|
+
for ix in range(self.nDep):
|
|
416
|
+
oldCoefs = self.coefs[ix]
|
|
417
|
+
firstCoefs = oldCoefs[:iKnot - self.order[0] + 1]
|
|
418
|
+
lastCoefs = oldCoefs[iKnot:]
|
|
419
|
+
middleCoefs = rhs[:-1,ix]
|
|
420
|
+
newCoefs.append(list(firstCoefs) + list(middleCoefs) + list(lastCoefs))
|
|
421
|
+
withoutKnot = type(self)(self.nInd, self.nDep, self.order, newNCoef, newKnots, newCoefs)
|
|
422
|
+
return withoutKnot, abs(rhs[-1])
|
|
423
|
+
|
|
424
|
+
def remove_knots(self, tolerance):
|
|
425
|
+
scaleDep = [max(np.abs(bound[0]), np.abs(bound[1])) for bound in self.range_bounds()]
|
|
426
|
+
scaleDep = [1.0 if factor == 0.0 else factor for factor in scaleDep]
|
|
427
|
+
rScaleDep = np.array([1.0 / factor for factor in scaleDep])
|
|
428
|
+
# Remove knots one at a time until done
|
|
429
|
+
currentSpline = self.scale(rScaleDep)
|
|
430
|
+
truthSpline = currentSpline
|
|
431
|
+
indIndex = list(range(currentSpline.nInd))
|
|
432
|
+
for id in indIndex:
|
|
433
|
+
foldedIndices = list(filter(lambda x: x != id, indIndex))
|
|
434
|
+
currentFold, foldedBasis = currentSpline.fold(foldedIndices)
|
|
435
|
+
while True:
|
|
436
|
+
bestError = np.finfo(scaleDep[0].dtype).max
|
|
437
|
+
for ix in range(currentFold.order[0], currentFold.nCoef[0]):
|
|
438
|
+
newSpline, residual = currentFold.remove_knot(ix)
|
|
439
|
+
error = np.max(residual)
|
|
440
|
+
if error < bestError:
|
|
441
|
+
bestError = error
|
|
442
|
+
bestSpline = newSpline
|
|
443
|
+
if bestError > tolerance:
|
|
444
|
+
break
|
|
445
|
+
errorSpline = truthSpline - bestSpline.unfold(foldedIndices, foldedBasis)
|
|
446
|
+
maxError = [max(np.abs(bound[0]), np.abs(bound[1])) for bound in errorSpline.range_bounds()]
|
|
447
|
+
if np.max(maxError) > tolerance:
|
|
480
448
|
break
|
|
481
449
|
else:
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
i = k1
|
|
486
|
-
u = knots[k]
|
|
487
|
-
while u == knots[k]:
|
|
488
|
-
knots[i] = knots[k]
|
|
489
|
-
i += 1
|
|
490
|
-
k += 1
|
|
491
|
-
multiplicity = i - k1
|
|
492
|
-
knotIndex = i - 1
|
|
493
|
-
gap += removed
|
|
494
|
-
for k in range(0, multiplicity):
|
|
495
|
-
coefs[j] = coefs[afterGap]
|
|
496
|
-
j += 1
|
|
497
|
-
afterGap += 1
|
|
498
|
-
beforeGap = j - 1
|
|
499
|
-
firstCoefOut = (2 * knotIndex - degree - multiplicity) // 2
|
|
500
|
-
last = knotIndex - multiplicity
|
|
501
|
-
first = knotIndex - degree
|
|
502
|
-
# End of endless while-loop
|
|
503
|
-
|
|
504
|
-
# Shift remaining knots.
|
|
505
|
-
i = highSpan + 1
|
|
506
|
-
k = i - gap
|
|
507
|
-
for j in range(1, order + 1):
|
|
508
|
-
knots[k] = knots[i]
|
|
509
|
-
k += 1
|
|
510
|
-
i += 1
|
|
511
|
-
|
|
512
|
-
# Update totalRemoved, nCoef, knots, and coefs.
|
|
513
|
-
totalRemoved += gap
|
|
514
|
-
nCoef[ind] -= gap
|
|
515
|
-
knotList[ind] = knots[:order + nCoef[ind]]
|
|
516
|
-
coefs = coefs[:nCoef[ind]]
|
|
517
|
-
coefs = coefs.swapaxes(0, ind + 1)
|
|
518
|
-
temp = temp.swapaxes(0, ind + 1)
|
|
519
|
-
# End of ind loop
|
|
520
|
-
|
|
521
|
-
spline = type(self)(self.nInd, self.nDep, self.order, nCoef, knotList, coefs, self.accuracy + maxResidualError, self.metadata)
|
|
522
|
-
return spline, totalRemoved, maxResidualError
|
|
450
|
+
currentFold = bestSpline
|
|
451
|
+
currentSpline = currentFold.unfold(foldedIndices, foldedBasis)
|
|
452
|
+
return currentSpline.scale(scaleDep)
|
|
523
453
|
|
|
524
454
|
def reparametrize(self, newDomain):
|
|
525
455
|
if not(len(newDomain) == self.nInd): raise ValueError("Invalid newDomain")
|
|
@@ -540,7 +470,6 @@ def reparametrize(self, newDomain):
|
|
|
540
470
|
for i in range(1-order, 0):
|
|
541
471
|
knots[i] = max(knots[i], nD[1])
|
|
542
472
|
knotList.append(knots)
|
|
543
|
-
|
|
544
473
|
return type(self)(self.nInd, self.nDep, self.order, self.nCoef, knotList, self.coefs, self.accuracy, self.metadata)
|
|
545
474
|
|
|
546
475
|
def reverse(self, variable = 0):
|
bspy/_spline_evaluation.py
CHANGED
|
@@ -56,6 +56,21 @@ def bspline_values(knot, knots, splineOrder, u, derivativeOrder = 0, taylorCoefs
|
|
|
56
56
|
b += 1
|
|
57
57
|
return basis
|
|
58
58
|
|
|
59
|
+
def curvature(self, uv):
|
|
60
|
+
if self.nInd == 1:
|
|
61
|
+
if self.nDep == 1:
|
|
62
|
+
self = self.graph()
|
|
63
|
+
fp = self.derivative([1], uv)
|
|
64
|
+
fpp = self.derivative([2], uv)
|
|
65
|
+
fpdotfp = fp @ fp
|
|
66
|
+
fpdotfpp = fp @ fpp
|
|
67
|
+
denom = fpdotfp ** 1.5
|
|
68
|
+
if self.nDep == 2:
|
|
69
|
+
numer = fp[0] * fpp[1] - fp[1] * fpp[0]
|
|
70
|
+
else:
|
|
71
|
+
numer = np.sqrt((fpp @ fpp) * fpdotfp - fpdotfpp ** 2)
|
|
72
|
+
return numer / denom
|
|
73
|
+
|
|
59
74
|
def derivative(self, with_respect_to, uvw):
|
|
60
75
|
# Make work for scalar valued functions
|
|
61
76
|
uvw = np.atleast_1d(uvw)
|