Loading...
Searching...
No Matches
bsplinebasis1d.C
Go to the documentation of this file.
1/*
2 * This file is part of the SPLINTER library.
3 * Copyright (C) 2012 Bjarne Grimstad (bjarne.grimstad@gmail.com).
4 *
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8*/
9
10#include <bsplinebasis1d.h>
11#include <knots.h>
12#include <algorithm>
13#include <utilities.h>
14#include <iostream>
15
16namespace SPLINTER
17{
18
19BSplineBasis1D::BSplineBasis1D()
20{
21}
22
23BSplineBasis1D::BSplineBasis1D(const std::vector<double>& knots,
24 unsigned int degree)
25 : degree(degree),
26 knots(knots),
27 targetNumBasisfunctions((degree + 1) + 2 * degree + 1) // Minimum p+1
28{
29 // if (degree <= 0)
30 // throw Exception("BSplineBasis1D::BSplineBasis1D: Cannot create B-spline basis functions of degree <= 0.");
31 if (!isKnotVectorRegular(knots, degree))
32 {
33 throw Exception("BSplineBasis1D::BSplineBasis1D: Knot vector is not regular.");
34 }
35}
36
37SparseVector BSplineBasis1D::eval(double x) const
38{
39 SparseVector values(getNumBasisFunctions());
40
41 if (!insideSupport(x))
42 {
43 return values;
44 }
45
46 supportHack(x);
47 std::vector<int> indexSupported = indexSupportedBasisfunctions(x);
48 values.reserve(indexSupported.size());
49
50 // Evaluate nonzero basis functions
51 for (auto it = indexSupported.begin(); it != indexSupported.end(); ++it)
52 {
53 double val = deBoorCox(x, *it, degree);
54
55 if (fabs(val) > 1e-12)
56 {
57 values.insert(*it) = val;
58 }
59 }
60
61 // Alternative evaluation using basis matrix
62 // int knotIndex = indexHalfopenInterval(x); // knot index
63 // SparseMatrix basisvalues2 = buildBsplineMatrix(x, knotIndex, 1);
64 // for (int i = 2; i <= basisDegree; i++)
65 // {
66 // SparseMatrix Ri = buildBsplineMatrix(x, knotIndex, i);
67 // basisvalues2 = basisvalues2*Ri;
68 // }
69 // basisvalues2.makeCompressed();
70 // assert(basisvalues2.rows() == 1);
71 // assert(basisvalues2.cols() == basisDegree + 1);
72 return values;
73}
74
75SparseVector BSplineBasis1D::evalDerivative(double x, int r) const
76{
77 // Evaluate rth derivative of basis functions at x
78 // Returns vector [D^(r)B_(u-p,p)(x) ... D^(r)B_(u,p)(x)]
79 // where u is the knot index and p is the degree
80 int p = degree;
81
82 // Continuity requirement
83 //assert(p > r);
84 if (p <= r)
85 {
86 // Return zero-gradient
87 SparseVector DB(getNumBasisFunctions());
88 return DB;
89 }
90
91 // Check for knot multiplicity here!
92 supportHack(x);
93 int knotIndex = indexHalfopenInterval(x);
94 // Algorithm 3.18 from Lyche and Moerken (2011)
95 SparseMatrix B(1, 1);
96 B.insert(0, 0) = 1;
97
98 for (int i = 1; i <= p - r; i++)
99 {
100 SparseMatrix R = buildBasisMatrix(x, knotIndex, i);
101 B = B * R;
102 }
103
104 for (int i = p - r + 1; i <= p; i++)
105 {
106 SparseMatrix DR = buildBasisMatrix(x, knotIndex, i, true);
107 B = B * DR;
108 }
109
110 double factorial = std::tgamma(p + 1) / std::tgamma(p - r + 1);
111 B = B * factorial;
112
113 if (B.cols() != p + 1)
114 {
115 throw Exception("BSplineBasis1D::evalDerivative: Wrong number of columns of B matrix.");
116 }
117
118 // From row vector to extended column vector
119 SparseVector DB(getNumBasisFunctions());
120 DB.reserve(p + 1);
121 int i = knotIndex - p; // First insertion index
122
123 for (int k = 0; k < B.outerSize(); ++k)
124 for (SparseMatrix::InnerIterator it(B, k); it; ++it)
125 {
126 DB.insert(i + it.col()) = it.value();
127 }
128
129 return DB;
130}
131
132// Old implementation of first derivative of basis functions
133SparseVector BSplineBasis1D::evalFirstDerivative(double x) const
134{
135 SparseVector values(getNumBasisFunctions());
136 supportHack(x);
137 std::vector<int> supportedBasisFunctions = indexSupportedBasisfunctions(x);
138
139 for (int i : supportedBasisFunctions)
140 {
141 // Differentiate basis function
142 // Equation 3.35 in Lyche & Moerken (2011)
143 double b1 = deBoorCox(x, i, degree - 1);
144 double b2 = deBoorCox(x, i + 1, degree - 1);
145 double t11 = knots.at(i);
146 double t12 = knots.at(i + degree);
147 double t21 = knots.at(i + 1);
148 double t22 = knots.at(i + degree + 1);
149 (t12 == t11) ? b1 = 0 : b1 = b1 / (t12 - t11);
150 (t22 == t21) ? b2 = 0 : b2 = b2 / (t22 - t21);
151 values.insert(i) = degree * (b1 - b2);
152 }
153
154 return values;
155}
156
157// Used to evaluate basis functions - alternative to the recursive deBoorCox
158SparseMatrix BSplineBasis1D::buildBasisMatrix(double x, unsigned int u,
159 unsigned int k, bool diff) const
160{
161 /* Build B-spline Matrix
162 * R_k in R^(k,k+1)
163 * or, if diff = true, the differentiated basis matrix
164 * DR_k in R^(k,k+1)
165 */
166 if (!(k >= 1 && k <= getBasisDegree()))
167 {
168 throw Exception("BSplineBasis1D::buildBasisMatrix: Incorrect input paramaters!");
169 }
170
171 // assert(u >= basisDegree + 1);
172 // assert(u < ks.size() - basisDegree);
173 unsigned int rows = k;
174 unsigned int cols = k + 1;
175 SparseMatrix R(rows, cols);
176 R.reserve(Eigen::VectorXi::Constant(cols, 2));
177
178 for (unsigned int i = 0; i < rows; i++)
179 {
180 double dk = knots.at(u + 1 + i) - knots.at(u + 1 + i - k);
181
182 if (dk == 0)
183 {
184 continue;
185 }
186 else
187 {
188 if (diff)
189 {
190 // Insert diagonal element
191 R.insert(i, i) = -1 / dk;
192 // Insert super-diagonal element
193 R.insert(i, i + 1) = 1 / dk;
194 }
195 else
196 {
197 // Insert diagonal element
198 double a = (knots.at(u + 1 + i) - x) / dk;
199
200 if (a != 0)
201 {
202 R.insert(i, i) = a;
203 }
204
205 // Insert super-diagonal element
206 double b = (x - knots.at(u + 1 + i - k)) / dk;
207
208 if (b != 0)
209 {
210 R.insert(i, i + 1) = b;
211 }
212 }
213 }
214 }
215
216 R.makeCompressed();
217 return R;
218}
219
220double BSplineBasis1D::deBoorCox(double x, int i, int k) const
221{
222 if (k == 0)
223 {
224 if (inHalfopenInterval(x, knots.at(i), knots.at(i + 1)))
225 {
226 return 1;
227 }
228 else
229 {
230 return 0;
231 }
232 }
233 else
234 {
235 double s1, s2, r1, r2;
236 s1 = deBoorCoxCoeff(x, knots.at(i), knots.at(i + k));
237 s2 = deBoorCoxCoeff(x, knots.at(i + 1), knots.at(i + k + 1));
238 r1 = deBoorCox(x, i, k - 1);
239 r2 = deBoorCox(x, i + 1, k - 1);
240 return s1 * r1 + (1 - s2) * r2;
241 }
242}
243
244double BSplineBasis1D::deBoorCoxCoeff(double x, double x_min,
245 double x_max) const
246{
247 if (x_min < x_max && x_min <= x && x <= x_max)
248 {
249 return (x - x_min) / (x_max - x_min);
250 }
251
252 return 0;
253}
254
255// Insert knots and compute knot insertion matrix (to update control points)
256SparseMatrix BSplineBasis1D::insertKnots(double tau, unsigned int multiplicity)
257{
258 if (!insideSupport(tau))
259 {
260 throw Exception("BSplineBasis1D::insertKnots: Cannot insert knot outside domain!");
261 }
262
263 if (knotMultiplicity(tau) + multiplicity > degree + 1)
264 {
265 throw Exception("BSplineBasis1D::insertKnots: Knot multiplicity is too high!");
266 }
267
268 // New knot vector
269 int index = indexHalfopenInterval(tau);
270 std::vector<double> extKnots = knots;
271
272 for (unsigned int i = 0; i < multiplicity; i++)
273 {
274 extKnots.insert(extKnots.begin() + index + 1, tau);
275 }
276
277 if (!isKnotVectorRegular(extKnots, degree))
278 {
279 throw Exception("BSplineBasis1D::insertKnots: New knot vector is not regular!");
280 }
281
282 // Return knot insertion matrix
283 SparseMatrix A = buildKnotInsertionMatrix(extKnots);
284 // Update knots
285 knots = extKnots;
286 return A;
287}
288
289SparseMatrix BSplineBasis1D::refineKnots()
290{
291 // Build refine knot vector
292 std::vector<double> refinedKnots = knots;
293 unsigned int targetNumKnots = targetNumBasisfunctions + degree + 1;
294
295 while (refinedKnots.size() < targetNumKnots)
296 {
297 int index = indexLongestInterval(refinedKnots);
298 double newKnot = (refinedKnots.at(index) + refinedKnots.at(index + 1)) / 2.0;
299 refinedKnots.insert(std::lower_bound(refinedKnots.begin(), refinedKnots.end(),
300 newKnot), newKnot);
301 }
302
303 if (!isKnotVectorRegular(refinedKnots, degree))
304 {
305 throw Exception("BSplineBasis1D::refineKnots: New knot vector is not regular!");
306 }
307
308 if (!isKnotVectorRefinement(knots, refinedKnots))
309 {
310 throw Exception("BSplineBasis1D::refineKnots: New knot vector is not a proper refinement!");
311 }
312
313 // Return knot insertion matrix
314 SparseMatrix A = buildKnotInsertionMatrix(refinedKnots);
315 // Update knots
316 knots = refinedKnots;
317 return A;
318}
319
320SparseMatrix BSplineBasis1D::refineKnotsLocally(double x)
321{
322 if (!insideSupport(x))
323 {
324 throw Exception("BSplineBasis1D::refineKnotsLocally: Cannot refine outside support!");
325 }
326
327 if (getNumBasisFunctions() >= getNumBasisFunctionsTarget()
328 || assertNear(knots.front(), knots.back()))
329 {
330 unsigned int n = getNumBasisFunctions();
331 DenseMatrix A = DenseMatrix::Identity(n, n);
332 return A.sparseView();
333 }
334
335 // Refined knot vector
336 std::vector<double> refinedKnots = knots;
337 auto upper = std::lower_bound(refinedKnots.begin(), refinedKnots.end(), x);
338
339 // Check left boundary
340 if (upper == refinedKnots.begin())
341 {
342 std::advance(upper, degree + 1);
343 }
344
345 // Get previous iterator
346 auto lower = std::prev(upper);
347
348 // Do not insert if upper and lower bounding knot are close
349 if (assertNear(*upper, *lower))
350 {
351 unsigned int n = getNumBasisFunctions();
352 DenseMatrix A = DenseMatrix::Identity(n, n);
353 return A.sparseView();
354 }
355
356 // Insert knot at x
357 double insertVal = x;
358
359 // Adjust x if it is on or close to a knot
360 if (knotMultiplicity(x) > 0
361 || assertNear(*upper, x, 1e-6, 1e-6)
362 || assertNear(*lower, x, 1e-6, 1e-6))
363 {
364 insertVal = (*upper + *lower) / 2.0;
365 }
366
367 // Insert new knot
368 refinedKnots.insert(upper, insertVal);
369
370 if (!isKnotVectorRegular(refinedKnots, degree))
371 {
372 throw Exception("BSplineBasis1D::refineKnotsLocally: New knot vector is not regular!");
373 }
374
375 if (!isKnotVectorRefinement(knots, refinedKnots))
376 {
377 throw Exception("BSplineBasis1D::refineKnotsLocally: New knot vector is not a proper refinement!");
378 }
379
380 // Build knot insertion matrix
381 SparseMatrix A = buildKnotInsertionMatrix(refinedKnots);
382 // Update knots
383 knots = refinedKnots;
384 return A;
385}
386
387SparseMatrix BSplineBasis1D::decomposeToBezierForm()
388{
389 // Build refine knot vector
390 std::vector<double> refinedKnots = knots;
391 // Start at first knot and add knots until all knots have multiplicity degree + 1
392 std::vector<double>::iterator knoti = refinedKnots.begin();
393
394 while (knoti != refinedKnots.end())
395 {
396 // Insert new knots
397 int mult = degree + 1 - knotMultiplicity(*knoti);
398
399 if (mult > 0)
400 {
401 std::vector<double> newKnots(mult, *knoti);
402 refinedKnots.insert(knoti, newKnots.begin(), newKnots.end());
403 }
404
405 // Advance to next knot
406 knoti = std::upper_bound(refinedKnots.begin(), refinedKnots.end(), *knoti);
407 }
408
409 if (!isKnotVectorRegular(refinedKnots, degree))
410 {
411 throw Exception("BSplineBasis1D::refineKnots: New knot vector is not regular!");
412 }
413
414 if (!isKnotVectorRefinement(knots, refinedKnots))
415 {
416 throw Exception("BSplineBasis1D::refineKnots: New knot vector is not a proper refinement!");
417 }
418
419 // Return knot insertion matrix
420 SparseMatrix A = buildKnotInsertionMatrix(refinedKnots);
421 // Update knots
422 knots = refinedKnots;
423 return A;
424}
425
426SparseMatrix BSplineBasis1D::buildKnotInsertionMatrix(const std::vector<double>&
427 refinedKnots) const
428{
429 if (!isKnotVectorRegular(refinedKnots, degree))
430 {
431 throw Exception("BSplineBasis1D::buildKnotInsertionMatrix: New knot vector is not regular!");
432 }
433
434 if (!isKnotVectorRefinement(knots, refinedKnots))
435 {
436 throw Exception("BSplineBasis1D::buildKnotInsertionMatrix: New knot vector is not a proper refinement!");
437 }
438
439 std::vector<double> knotsAug = refinedKnots;
440 unsigned int n = knots.size() - degree - 1;
441 unsigned int m = knotsAug.size() - degree - 1;
442 SparseMatrix A(m, n);
443 //A.resize(m,n);
444 A.reserve(Eigen::VectorXi::Constant(n, degree + 1));
445
446 // Build A row-by-row
447 for (unsigned int i = 0; i < m; i++)
448 {
449 int u = indexHalfopenInterval(knotsAug.at(i));
450 SparseMatrix R(1, 1);
451 R.insert(0, 0) = 1;
452
453 // For p > 0
454 for (unsigned int j = 1; j <= degree; j++)
455 {
456 SparseMatrix Ri = buildBasisMatrix(knotsAug.at(i + j), u, j);
457 R = R * Ri;
458 }
459
460 // Size check
461 if (R.rows() != 1 || R.cols() != (int)degree + 1)
462 {
463 throw Exception("BSplineBasis1D::buildKnotInsertionMatrix: Incorrect matrix dimensions!");
464 }
465
466 // Insert row values
467 int j = u - degree; // First insertion index
468
469 for (int k = 0; k < R.outerSize(); ++k)
470 for (SparseMatrix::InnerIterator it(R, k); it; ++it)
471 {
472 A.insert(i, j + it.col()) = it.value();
473 }
474 }
475
476 A.makeCompressed();
477 return A;
478}
479
480/*
481 * The B-spline domain is the half-open domain [ knots.first(), knots.end() ).
482 * The hack checks if x is at the right boundary (if x = knots.end()), if so,
483 * a small number is subtracted from x, moving x into the half-open domain.
484 */
485void BSplineBasis1D::supportHack(double& x) const
486{
487 if (x == knots.back())
488 {
489 x = std::nextafter(x, std::numeric_limits<double>::lowest());
490 }
491}
492
493/*
494 * Finds index i such that knots.at(i) <= x < knots.at(i+1).
495 * Returns false if x is outside support.
496 */
497int BSplineBasis1D::indexHalfopenInterval(double x) const
498{
499 if (x < knots.front() || x > knots.back())
500 {
501 throw Exception("BSplineBasis1D::indexHalfopenInterval: x outside knot interval!");
502 }
503
504 // Find first knot that is larger than x
505 std::vector<double>::const_iterator it = std::upper_bound(knots.begin(),
506 knots.end(), x);
507 // Return index
508 int index = it - knots.begin();
509 return index - 1;
510}
511
512SparseMatrix BSplineBasis1D::reduceSupport(double lb, double ub)
513{
514 // Check bounds
515 if (lb < knots.front() || ub > knots.back())
516 {
517 throw Exception("BSplineBasis1D::reduceSupport: Cannot increase support!");
518 }
519
520 unsigned int k = degree + 1;
521 int index_lower = indexSupportedBasisfunctions(lb).front();
522 int index_upper = indexSupportedBasisfunctions(ub).back();
523
524 // Check lower bound index
525 if (k != knotMultiplicity(knots.at(index_lower)))
526 {
527 int suggested_index = index_lower - 1;
528
529 if (0 <= suggested_index)
530 {
531 index_lower = suggested_index;
532 }
533 else
534 {
535 throw Exception("BSplineBasis1D::reduceSupport: Suggested index is negative!");
536 }
537 }
538
539 // Check upper bound index
540 if (knotMultiplicity(ub) == k && knots.at(index_upper) == ub)
541 {
542 index_upper -= k;
543 }
544
545 // New knot vector
546 std::vector<double> si;
547 si.insert(si.begin(), knots.begin() + index_lower,
548 knots.begin() + index_upper + k + 1);
549 // Construct selection matrix A
550 int numOld = knots.size() - k; // Current number of basis functions
551 int numNew = si.size() - k; // Number of basis functions after update
552
553 if (numOld < numNew)
554 {
555 throw Exception("BSplineBasis1D::reduceSupport: Number of basis functions is increased instead of reduced!");
556 }
557
558 DenseMatrix Ad = DenseMatrix::Zero(numOld, numNew);
559 Ad.block(index_lower, 0, numNew, numNew) = DenseMatrix::Identity(numNew,
560 numNew);
561 SparseMatrix A = Ad.sparseView();
562 // Update knots
563 knots = si;
564 return A;
565}
566
567double BSplineBasis1D::getKnotValue(unsigned int index) const
568{
569 return knots.at(index);
570}
571
572unsigned int BSplineBasis1D::knotMultiplicity(double tau) const
573{
574 return std::count(knots.begin(), knots.end(), tau);
575}
576
577bool BSplineBasis1D::inHalfopenInterval(double x, double x_min,
578 double x_max) const
579{
580 return (x_min <= x) && (x < x_max);
581}
582
583bool BSplineBasis1D::insideSupport(double x) const
584{
585 return (knots.front() <= x) && (x <= knots.back());
586}
587
588unsigned int BSplineBasis1D::getNumBasisFunctions() const
589{
590 return knots.size() - (degree + 1);
591}
592
593unsigned int BSplineBasis1D::getNumBasisFunctionsTarget() const
594{
595 return targetNumBasisfunctions;
596}
597
598// Return indices of supporting basis functions at x
599std::vector<int> BSplineBasis1D::indexSupportedBasisfunctions(double x) const
600{
601 std::vector<int> ret;
602
603 if (insideSupport(x))
604 {
605 int last = indexHalfopenInterval(x);
606
607 if (last < 0)
608 {
609 // NOTE: can this happen?
610 last = knots.size() - 1 - (degree + 1);
611 }
612
613 int first = std::max((int)(last - degree), 0);
614
615 for (int i = first; i <= last; i++)
616 {
617 ret.push_back(i);
618 }
619 }
620
621 return ret;
622}
623
624unsigned int BSplineBasis1D::indexLongestInterval() const
625{
626 return indexLongestInterval(knots);
627}
628
629unsigned int BSplineBasis1D::indexLongestInterval(const std::vector<double>&
630 vec) const
631{
632 double longest = 0;
633 double interval = 0;
634 unsigned int index = 0;
635
636 for (unsigned int i = 0; i < vec.size() - 1; i++)
637 {
638 interval = vec.at(i + 1) - vec.at(i);
639
640 if (longest < interval)
641 {
642 longest = interval;
643 index = i;
644 }
645 }
646
647 return index;
648}
649
650} // namespace SPLINTER
volScalarField & A
bool isKnotVectorRegular(const std::vector< double > &knots, unsigned int degree)
Definition knots.C:16
bool isKnotVectorRefinement(const std::vector< double > &knots, const std::vector< double > &refinedKnots)
Definition knots.C:62
volScalarField & p
label i
Definition pEqn.H:46
dimensionedScalar & tau