Loading...
Searching...
No Matches
bspline.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 "bspline.h"
11#include "bsplinebasis.h"
12#include "mykroneckerproduct.h"
13#include <unsupported/Eigen/KroneckerProduct>
14#include <linearsolvers.h>
15#include <serializer.h>
16#include <iostream>
17#include <utilities.h>
18
19namespace SPLINTER
20{
21
22BSpline::BSpline()
23 : Function(1)
24{}
25
26BSpline::BSpline(unsigned int numVariables)
27 : Function(numVariables)
28{}
29
30/*
31 * Constructors for multivariate B-spline using explicit data
32 */
33BSpline::BSpline(std::vector<std::vector<double>> knotVectors,
34 std::vector<unsigned int> basisDegrees)
35 : Function(knotVectors.size()),
36 basis(BSplineBasis(knotVectors, basisDegrees)),
37 coefficients(DenseVector::Zero(1)),
38 knotaverages(computeKnotAverages())
39{
40 // Initialize coefficients to ones
41 setCoefficients(DenseVector::Ones(basis.getNumBasisFunctions()));
42 checkControlPoints();
43}
44
45BSpline::BSpline(std::vector<double> coefficients,
46 std::vector<std::vector<double>> knotVectors,
47 std::vector<unsigned int> basisDegrees)
48 : BSpline(vectorToDenseVector(coefficients), knotVectors, basisDegrees)
49{
50}
51
52BSpline::BSpline(DenseVector coefficients,
53 std::vector<std::vector<double>> knotVectors,
54 std::vector<unsigned int> basisDegrees)
55 : Function(knotVectors.size()),
56 basis(BSplineBasis(knotVectors, basisDegrees)),
57 coefficients(coefficients),
58 knotaverages(computeKnotAverages())
59{
60 setCoefficients(coefficients);
61 checkControlPoints();
62}
63
64/*
65 * Construct from saved data
66 */
67BSpline::BSpline(const char* fileName)
68 : BSpline(std::string(fileName))
69{
70}
71
72BSpline::BSpline(const std::string& fileName)
73 : Function(1)
74{
75 load(fileName);
76}
77
81double BSpline::eval(DenseVector x) const
82{
83 checkInput(x);
84 // NOTE: casting to DenseVector to allow accessing as res(0)
85 DenseVector res = coefficients.transpose() * evalBasis(x);
86 return res(0);
87}
88
92DenseMatrix BSpline::evalJacobian(DenseVector x) const
93{
94 checkInput(x);
95 return coefficients.transpose() * evalBasisJacobian(x);
96}
97
98/*
99 * Returns the Hessian evaluated at x.
100 * The Hessian is an n x n matrix,
101 * where n is the dimension of x.
102 */
103DenseMatrix BSpline::evalHessian(DenseVector x) const
104{
105 checkInput(x);
106#ifndef NDEBUG
107
108 if (!pointInDomain(x))
109 {
110 throw Exception("BSpline::evalHessian: Evaluation at point outside domain.");
111 }
112
113#endif // NDEBUG
114 DenseMatrix H;
115 H.setZero(1, 1);
116 DenseMatrix identity = DenseMatrix::Identity(numVariables, numVariables);
117 DenseMatrix caug = kroneckerProduct(identity, coefficients.transpose());
118 DenseMatrix DB = basis.evalBasisHessian(x);
119 H = caug * DB;
120
121 // Fill in upper triangular of Hessian
122 for (size_t i = 0; i < numVariables; ++i)
123 for (size_t j = i + 1; j < numVariables; ++j)
124 {
125 H(i, j) = H(j, i);
126 }
127
128 return H;
129}
130
131// Evaluation of B-spline basis functions
132SparseVector BSpline::evalBasis(DenseVector x) const
133{
134#ifndef NDEBUG
135
136 if (!pointInDomain(x))
137 {
138 throw Exception("BSpline::evalBasis: Evaluation at point outside domain.");
139 }
140
141#endif // NDEBUG
142 return basis.eval(x);
143}
144
145SparseMatrix BSpline::evalBasisJacobian(DenseVector x) const
146{
147#ifndef NDEBUG
148
149 if (!pointInDomain(x))
150 {
151 throw Exception("BSpline::evalBasisJacobian: Evaluation at point outside domain.");
152 }
153
154#endif // NDEBUG
155 //SparseMatrix Bi = basis.evalBasisJacobian(x); // Sparse Jacobian implementation
156 //SparseMatrix Bi = basis.evalBasisJacobian2(x); // Sparse Jacobian implementation
157 DenseMatrix Bi = basis.evalBasisJacobianOld(x); // Old Jacobian implementation
158 return Bi.sparseView();
159}
160
161std::vector<unsigned int> BSpline::getNumBasisFunctionsPerVariable() const
162{
163 std::vector<unsigned int> ret;
164
165 for (unsigned int i = 0; i < numVariables; i++)
166 {
167 ret.push_back(basis.getNumBasisFunctions(i));
168 }
169
170 return ret;
171}
172
173std::vector< std::vector<double>> BSpline::getKnotVectors() const
174{
175 return basis.getKnotVectors();
176}
177
178std::vector<unsigned int> BSpline::getBasisDegrees() const
179{
180 return basis.getBasisDegrees();
181}
182
183std::vector<double> BSpline::getDomainUpperBound() const
184{
185 return basis.getSupportUpperBound();
186}
187
188std::vector<double> BSpline::getDomainLowerBound() const
189{
190 return basis.getSupportLowerBound();
191}
192
193DenseMatrix BSpline::getControlPoints() const
194{
195 int nc = coefficients.size();
196 DenseMatrix controlPoints(nc, numVariables + 1);
197 controlPoints.block(0, 0, nc, numVariables) = knotaverages;
198 controlPoints.block(0, numVariables, nc, 1) = coefficients;
199 return controlPoints;
200}
201
202void BSpline::setCoefficients(const DenseVector& coefficients)
203{
204 if (coefficients.size() != getNumBasisFunctions())
205 {
206 throw Exception("BSpline::setControlPoints: Incompatible size of coefficient vector.");
207 }
208
209 this->coefficients = coefficients;
210 checkControlPoints();
211}
212
213void BSpline::setControlPoints(const DenseMatrix& controlPoints)
214{
215 if (controlPoints.cols() != numVariables + 1)
216 {
217 throw Exception("BSpline::setControlPoints: Incompatible size of control point matrix.");
218 }
219
220 int nc = controlPoints.rows();
221 knotaverages = controlPoints.block(0, 0, nc, numVariables);
222 coefficients = controlPoints.block(0, numVariables, nc, 1);
223 checkControlPoints();
224}
225
226void BSpline::updateControlPoints(const DenseMatrix& A)
227{
228 if (A.cols() != coefficients.rows() || A.cols() != knotaverages.rows())
229 {
230 throw Exception("BSpline::updateControlPoints: Incompatible size of linear transformation matrix.");
231 }
232
233 coefficients = A * coefficients;
234 knotaverages = A * knotaverages;
235}
236
237void BSpline::checkControlPoints() const
238{
239 if (coefficients.rows() != knotaverages.rows())
240 {
241 throw Exception("BSpline::checkControlPoints: Inconsistent size of coefficients and knot averages matrices.");
242 }
243
244 if (knotaverages.cols() != numVariables)
245 {
246 throw Exception("BSpline::checkControlPoints: Inconsistent size of knot averages matrix.");
247 }
248}
249
250bool BSpline::pointInDomain(DenseVector x) const
251{
252 return basis.insideSupport(x);
253}
254
255void BSpline::reduceSupport(std::vector<double> lb, std::vector<double> ub,
256 bool doRegularizeKnotVectors)
257{
258 if (lb.size() != numVariables || ub.size() != numVariables)
259 {
260 throw Exception("BSpline::reduceSupport: Inconsistent vector sizes!");
261 }
262
263 std::vector<double> sl = basis.getSupportLowerBound();
264 std::vector<double> su = basis.getSupportUpperBound();
265
266 for (unsigned int dim = 0; dim < numVariables; dim++)
267 {
268 // Check if new domain is empty
269 if (ub.at(dim) <= lb.at(dim) || lb.at(dim) >= su.at(dim)
270 || ub.at(dim) <= sl.at(dim))
271 {
272 throw Exception("BSpline::reduceSupport: Cannot reduce B-spline domain to empty set!");
273 }
274
275 // Check if new domain is a strict subset
276 if (su.at(dim) < ub.at(dim) || sl.at(dim) > lb.at(dim))
277 {
278 throw Exception("BSpline::reduceSupport: Cannot expand B-spline domain!");
279 }
280
281 // Tightest possible
282 sl.at(dim) = lb.at(dim);
283 su.at(dim) = ub.at(dim);
284 }
285
286 if (doRegularizeKnotVectors)
287 {
288 regularizeKnotVectors(sl, su);
289 }
290
291 // Remove knots and control points that are unsupported with the new bounds
292 if (!removeUnsupportedBasisFunctions(sl, su))
293 {
294 throw Exception("BSpline::reduceSupport: Failed to remove unsupported basis functions!");
295 }
296}
297
298void BSpline::globalKnotRefinement()
299{
300 // Compute knot insertion matrix
301 SparseMatrix A = basis.refineKnots();
302 // Update control points
303 updateControlPoints(A);
304}
305
306void BSpline::localKnotRefinement(DenseVector x)
307{
308 // Compute knot insertion matrix
309 SparseMatrix A = basis.refineKnotsLocally(x);
310 // Update control points
311 updateControlPoints(A);
312}
313
314void BSpline::decomposeToBezierForm()
315{
316 // Compute knot insertion matrix
317 SparseMatrix A = basis.decomposeToBezierForm();
318 // Update control points
319 updateControlPoints(A);
320}
321
322// Computes knot averages: assumes that basis is initialized!
323DenseMatrix BSpline::computeKnotAverages() const
324{
325 // Calculate knot averages for each knot vector
326 std::vector<DenseVector> mu_vectors;
327
328 for (unsigned int i = 0; i < numVariables; i++)
329 {
330 std::vector<double> knots = basis.getKnotVector(i);
331 DenseVector mu = DenseVector::Zero(basis.getNumBasisFunctions(i));
332
333 for (unsigned int j = 0; j < basis.getNumBasisFunctions(i); j++)
334 {
335 double knotAvg = 0;
336
337 for (unsigned int k = j + 1; k <= j + basis.getBasisDegree(i); k++)
338 {
339 knotAvg += knots.at(k);
340 }
341
342 mu(j) = knotAvg / basis.getBasisDegree(i);
343 }
344
345 mu_vectors.push_back(mu);
346 }
347
348 // Calculate vectors of ones (with same length as corresponding knot average vector)
349 std::vector<DenseVector> knotOnes;
350
351 for (unsigned int i = 0; i < numVariables; i++)
352 {
353 knotOnes.push_back(DenseVector::Ones(mu_vectors.at(i).rows()));
354 }
355
356 // Fill knot average matrix one column at the time
357 DenseMatrix knot_averages = DenseMatrix::Zero(basis.getNumBasisFunctions(),
358 numVariables);
359
360 for (unsigned int i = 0; i < numVariables; i++)
361 {
362 DenseMatrix mu_ext(1, 1);
363 mu_ext(0, 0) = 1;
364
365 for (unsigned int j = 0; j < numVariables; j++)
366 {
367 DenseMatrix temp = mu_ext;
368
369 if (i == j)
370 {
371 mu_ext = Eigen::kroneckerProduct(temp, mu_vectors.at(j));
372 }
373 else
374 {
375 mu_ext = Eigen::kroneckerProduct(temp, knotOnes.at(j));
376 }
377 }
378
379 if (mu_ext.rows() != basis.getNumBasisFunctions())
380 {
381 throw Exception("BSpline::computeKnotAverages: Incompatible size of knot average matrix.");
382 }
383
384 knot_averages.block(0, i, basis.getNumBasisFunctions(), 1) = mu_ext;
385 }
386
387 return knot_averages;
388}
389
390void BSpline::insertKnots(double tau, unsigned int dim,
391 unsigned int multiplicity)
392{
393 // Insert knots and compute knot insertion matrix
394 SparseMatrix A = basis.insertKnots(tau, dim, multiplicity);
395 // Update control points
396 updateControlPoints(A);
397}
398
399void BSpline::regularizeKnotVectors(std::vector<double>& lb,
400 std::vector<double>& ub)
401{
402 // Add and remove controlpoints and knots to make the b-spline p-regular with support [lb, ub]
403 if (!(lb.size() == numVariables && ub.size() == numVariables))
404 {
405 throw Exception("BSpline::regularizeKnotVectors: Inconsistent vector sizes.");
406 }
407
408 for (unsigned int dim = 0; dim < numVariables; dim++)
409 {
410 unsigned int multiplicityTarget = basis.getBasisDegree(dim) + 1;
411 // Inserting many knots at the time (to save number of B-spline coefficient calculations)
412 // NOTE: This method generates knot insertion matrices with more nonzero elements than
413 // the method that inserts one knot at the time. This causes the preallocation of
414 // kronecker product matrices to become too small and the speed deteriorates drastically
415 // in higher dimensions because reallocation is necessary. This can be prevented by
416 // precomputing the number of nonzeros when preallocating memory (see myKroneckerProduct).
417 int numKnotsLB = multiplicityTarget - basis.getKnotMultiplicity(dim,
418 lb.at(dim));
419
420 if (numKnotsLB > 0)
421 {
422 insertKnots(lb.at(dim), dim, numKnotsLB);
423 }
424
425 int numKnotsUB = multiplicityTarget - basis.getKnotMultiplicity(dim,
426 ub.at(dim));
427
428 if (numKnotsUB > 0)
429 {
430 insertKnots(ub.at(dim), dim, numKnotsUB);
431 }
432 }
433}
434
435bool BSpline::removeUnsupportedBasisFunctions(std::vector<double>& lb,
436 std::vector<double>& ub)
437{
438 if (lb.size() != numVariables || ub.size() != numVariables)
439 {
440 throw Exception("BSpline::removeUnsupportedBasisFunctions: Incompatible dimension of domain bounds.");
441 }
442
443 SparseMatrix A = basis.reduceSupport(lb, ub);
444
445 if (coefficients.size() != A.rows())
446 {
447 return false;
448 }
449
450 // Remove unsupported control points (basis functions)
451 updateControlPoints(A.transpose());
452 return true;
453}
454
455void BSpline::save(const std::string& fileName) const
456{
457 Serializer s;
458 s.serialize(*this);
459 s.saveToFile(fileName);
460}
461
462void BSpline::load(const std::string& fileName)
463{
464 Serializer s(fileName);
465 s.deserialize(*this);
466}
467
468std::string BSpline::getDescription() const
469{
470 std::string description("BSpline of degree");
471 auto degrees = getBasisDegrees();
472 // See if all degrees are the same.
473 bool equal = true;
474
475 for (size_t i = 1; i < degrees.size(); ++i)
476 {
477 equal = equal && (degrees.at(i) == degrees.at(i - 1));
478 }
479
480 if (equal)
481 {
482 description.append(" ");
483 description.append(std::to_string(degrees.at(0)));
484 }
485 else
486 {
487 description.append("s (");
488
489 for (size_t i = 0; i < degrees.size(); ++i)
490 {
491 description.append(std::to_string(degrees.at(i)));
492
493 if (i + 1 < degrees.size())
494 {
495 description.append(", ");
496 }
497 }
498
499 description.append(")");
500 }
501
502 return description;
503}
504
505} // namespace SPLINTER
volScalarField & A
DenseVector vectorToDenseVector(const std::vector< double > &vec)
Definition utilities.C:27
label i
Definition pEqn.H:46
dimensionedScalar & tau