preCICE v3.2.0
Loading...
Searching...
No Matches
RadialBasisFctSolver.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <Eigen/Cholesky>
4#include <Eigen/QR>
5#include <Eigen/SVD>
6#include <boost/range/adaptor/indexed.hpp>
7#include <boost/range/irange.hpp>
8#include <numeric>
9#include <type_traits>
12#include "mesh/Mesh.hpp"
14#include "profiling/Event.hpp"
15
16namespace precice::mapping {
17
24template <typename RADIAL_BASIS_FUNCTION_T>
26public:
27 using DecompositionType = std::conditional_t<RADIAL_BASIS_FUNCTION_T::isStrictlyPositiveDefinite(), Eigen::LLT<Eigen::MatrixXd>, Eigen::ColPivHouseholderQR<Eigen::MatrixXd>>;
28 using BASIS_FUNCTION_T = RADIAL_BASIS_FUNCTION_T;
31
39 template <typename IndexContainer>
40 RadialBasisFctSolver(RADIAL_BASIS_FUNCTION_T basisFunction, const mesh::Mesh &inputMesh, const IndexContainer &inputIDs,
41 const mesh::Mesh &outputMesh, const IndexContainer &outputIDs, std::vector<bool> deadAxis, Polynomial polynomial);
42
44 Eigen::VectorXd solveConsistent(Eigen::VectorXd &inputData, Polynomial polynomial) const;
45
46 void computeCacheData(Eigen::MatrixXd &inputData, Polynomial polynomial, Eigen::MatrixXd &polyOut, Eigen::MatrixXd &coeffsOut) const;
47
49 Eigen::VectorXd solveConservative(const Eigen::VectorXd &inputData, Polynomial polynomial) const;
50
51 // Clear all stored matrices
52 void clear();
53
54 // Returns the size of the input data
55 Eigen::Index getInputSize() const;
56
57 // Returns the size of the input data
58 Eigen::Index getOutputSize() const;
59
60 // Returns the number of active axis in use
61 Eigen::Index getNumberOfPolynomials() const;
62
63 template <typename IndexContainer>
64 Eigen::VectorXd interpolateAt(const mesh::Vertex &v, const Eigen::MatrixXd &poly, const Eigen::MatrixXd &coeffs,
65 const RADIAL_BASIS_FUNCTION_T &function, const IndexContainer &inputIDs, const mesh::Mesh &inMesh) const;
66
67 template <typename IndexContainer>
68 void addWriteDataToCache(const mesh::Vertex &v, const Eigen::VectorXd &load, Eigen::MatrixXd &epsilon, Eigen::MatrixXd &Au,
69 const RADIAL_BASIS_FUNCTION_T &basisFunction, const IndexContainer &inputIDs, const mesh::Mesh &inMesh) const;
70
71 void evaluateConservativeCache(Eigen::MatrixXd &epsilon, const Eigen::MatrixXd &Au, Eigen::MatrixXd &result) const;
72
73private:
74 mutable precice::logging::Logger _log{"mapping::RadialBasisFctSolver"};
75
76 double evaluateRippaLOOCVerror(const Eigen::VectorXd &lambda) const;
79
81 Eigen::VectorXd _inverseDiagonal;
82
84 Eigen::ColPivHouseholderQR<Eigen::MatrixXd> _qrMatrixQ;
85
87 Eigen::MatrixXd _matrixQ;
88
90 Eigen::MatrixXd _matrixV;
91
93 Eigen::MatrixXd _matrixA;
94
97};
98
99// ------- Non-Member Functions ---------
100
103 const std::array<double, 3> &u,
105 const std::array<bool, 3> &activeAxis = {{true, true, true}})
106{
107 // Subtract the values and multiply out dead dimensions
108 for (unsigned int d = 0; d < v.size(); ++d) {
109 v[d] = (u[d] - v[d]) * static_cast<int>(activeAxis[d]);
110 }
111 // @todo: this can be replaced by std::hypot when moving to C++17
112 return std::accumulate(v.begin(), v.end(), static_cast<double>(0.), [](auto &res, auto &val) { return res + val * val; });
113}
114
116template <typename IndexContainer>
117constexpr void reduceActiveAxis(const mesh::Mesh &mesh, const IndexContainer &IDs, std::array<bool, 3> &axis)
118{
119 // make a pair of the axis and the difference
120 std::array<std::pair<int, double>, 3> differences;
121
122 // Compute the difference magnitude per direction
123 for (std::size_t d = 0; d < axis.size(); ++d) {
124 // Ignore dead axis here, i.e., apply the max value such that they are sorted on the last position(s)
125 if (axis[d] == false) {
127 } else {
128 auto res = std::minmax_element(IDs.begin(), IDs.end(), [&](const auto &a, const auto &b) { return mesh.vertex(a).coord(d) < mesh.vertex(b).coord(d); });
129 // Check if we are above or below the threshold
130 differences[d] = std::make_pair<int, double>(d, std::abs(mesh.vertex(*res.second).coord(d) - mesh.vertex(*res.first).coord(d)));
131 }
132 }
133
134 std::sort(differences.begin(), differences.end(), [](const auto &d1, const auto &d2) { return d1.second < d2.second; });
135 // Disable the axis having the smallest expansion
136 axis[differences[0].first] = false;
137}
138
139// Fill in the polynomial entries
140template <typename IndexContainer>
141inline void fillPolynomialEntries(Eigen::MatrixXd &matrix, const mesh::Mesh &mesh, const IndexContainer &IDs, Eigen::Index startIndex, std::array<bool, 3> activeAxis)
142{
143 // Loop over all vertices in the mesh
144 for (const auto &i : IDs | boost::adaptors::indexed()) {
145
146 // 1. the constant contribution
147 matrix(i.index(), startIndex) = 1.0;
148
149 // 2. the linear contribution
150 const auto &u = mesh.vertex(i.value()).rawCoords();
151 unsigned int k = 0;
152 // Loop over all three space dimension and ignore dead axis
153 for (unsigned int d = 0; d < activeAxis.size(); ++d) {
154 if (activeAxis[d]) {
155 PRECICE_ASSERT(matrix.rows() > i.index(), matrix.rows(), i.index());
156 PRECICE_ASSERT(matrix.cols() > startIndex + 1 + k, matrix.cols(), startIndex + 1 + k);
157 matrix(i.index(), startIndex + 1 + k) = u[d];
158 ++k;
159 }
160 }
161 }
162}
163
164template <typename RADIAL_BASIS_FUNCTION_T, typename IndexContainer>
165Eigen::MatrixXd buildMatrixCLU(RADIAL_BASIS_FUNCTION_T basisFunction, const mesh::Mesh &inputMesh, const IndexContainer &inputIDs,
166 std::array<bool, 3> activeAxis, Polynomial polynomial)
167{
168 // Treat the 2D case as 3D case with dead axis
169 const unsigned int deadDimensions = std::count(activeAxis.begin(), activeAxis.end(), false);
170 const unsigned int dimensions = 3;
171 const unsigned int polyparams = polynomial == Polynomial::ON ? 1 + dimensions - deadDimensions : 0;
172
173 // Add linear polynom degrees if polynomial requires this
174 const auto inputSize = inputIDs.size();
175 const auto n = inputSize + polyparams;
176
177 PRECICE_ASSERT((inputMesh.getDimensions() == 3) || activeAxis[2] == false);
178 PRECICE_ASSERT((inputSize >= 1 + polyparams) || polynomial != Polynomial::ON, inputSize);
179
180 Eigen::MatrixXd matrixCLU(n, n);
181
182 // Required to fill the poly -> poly entries in the matrix, which remain otherwise untouched
183 if (polynomial == Polynomial::ON) {
184 matrixCLU.setZero();
185 }
186
187 // Compute RBF matrix entries
188 auto i_iter = inputIDs.begin();
189 Eigen::Index i_index = 0;
190 for (; i_iter != inputIDs.end(); ++i_iter, ++i_index) {
191 const auto &u = inputMesh.vertex(*i_iter).rawCoords();
192 auto j_iter = i_iter;
193 auto j_index = i_index;
194 for (; j_iter != inputIDs.end(); ++j_iter, ++j_index) {
195 const auto &v = inputMesh.vertex(*j_iter).rawCoords();
196 double squaredDifference = computeSquaredDifference(u, v, activeAxis);
197 matrixCLU(i_index, j_index) = basisFunction.evaluate(std::sqrt(squaredDifference));
198 }
199 }
200
201 // Add potentially the polynomial contribution in the matrix
202 if (polynomial == Polynomial::ON) {
203 fillPolynomialEntries(matrixCLU, inputMesh, inputIDs, inputSize, activeAxis);
204 }
205 matrixCLU.triangularView<Eigen::Lower>() = matrixCLU.transpose();
206 return matrixCLU;
207}
208
209template <typename RADIAL_BASIS_FUNCTION_T, typename IndexContainer>
210Eigen::MatrixXd buildMatrixA(RADIAL_BASIS_FUNCTION_T basisFunction, const mesh::Mesh &inputMesh, const IndexContainer &inputIDs,
211 const mesh::Mesh &outputMesh, const IndexContainer outputIDs, std::array<bool, 3> activeAxis, Polynomial polynomial)
212{
213 // Treat the 2D case as 3D case with dead axis
214 const unsigned int deadDimensions = std::count(activeAxis.begin(), activeAxis.end(), false);
215 const unsigned int dimensions = 3;
216 const unsigned int polyparams = polynomial == Polynomial::ON ? 1 + dimensions - deadDimensions : 0;
217
218 const auto inputSize = inputIDs.size();
219 const auto outputSize = outputIDs.size();
220 const auto n = inputSize + polyparams;
221
222 PRECICE_ASSERT((inputMesh.getDimensions() == 3) || activeAxis[2] == false);
223 PRECICE_ASSERT((inputSize >= 1 + polyparams) || polynomial != Polynomial::ON, inputSize);
224
225 Eigen::MatrixXd matrixA(outputSize, n);
226
227 // Compute RBF values for matrix A
228 for (const auto &i : outputIDs | boost::adaptors::indexed()) {
229 const auto &u = outputMesh.vertex(i.value()).rawCoords();
230 for (const auto &j : inputIDs | boost::adaptors::indexed()) {
231 const auto &v = inputMesh.vertex(j.value()).rawCoords();
232 double squaredDifference = computeSquaredDifference(u, v, activeAxis);
233 matrixA(i.index(), j.index()) = basisFunction.evaluate(std::sqrt(squaredDifference));
234 }
235 }
236
237 // Add potentially the polynomial contribution in the matrix
238 if (polynomial == Polynomial::ON) {
239 fillPolynomialEntries(matrixA, outputMesh, outputIDs, inputSize, activeAxis);
240 }
241 return matrixA;
242}
243
244// Variant operating on the Cholesky decopmosition
245inline Eigen::VectorXd computeInverseDiagonal(Eigen::LLT<Eigen::MatrixXd> decMatrixC)
246{
247 // 1. Compute the diagonal entries of the inverse kernel matrix:
248 // We already have the Cholesky decomposition. So instead of solving for the
249 // kernel matrix directly, we invert the lower triangular matrix of the
250 // decomposition:
251 // using A^{-1} = (L^T)^{-1}L^{-1} enables the computation of the diagonal
252 // entries of A^{-1} by evaluating the product above:
253 // A^{-1}_{ii} = sum_{k=1}^n L^{-T}_{ik} L^{-1}_{ki}
254
255 // 1a: Compute the inverse of the lower triangular matrix L
256 // Eigen::MatrixXd L_inv = L.inverse(); is not supported by Eigen (linker errors)
257 // However, Eigen provides triangular solver (LAPACK::trsm), which can be used
258 // to solve L * Linv = I
259 // Eigen::MatrixXd L_inv = Eigen::MatrixXd::Identity(inSize, inSize);
260 // _decMatrixC.matrixL().solveInPlace(L_inv);
261 // which yields cubic complexity (BLAS level 3).
262
263 // Here, we use our own implementation to compute the triangular inverse
264 // (similar to LAPACK:trtri) more efficient, given that the RHS is also
265 // triangular was unfortunately slower.
266
267 // Solve L * Linv = I
268 // Eigen::MatrixXd L_inv = Eigen::MatrixXd::Identity(n, n);
269 // decMatrixC.matrixL().solveInPlace(L_inv);
270 Eigen::MatrixXd L_inv = utils::invertLowerTriangularBlockwise(decMatrixC.matrixL());
271
272 // 1b: Compute the diagonal elements of A^{-1} by evaluating (L^T)^{-1}L^{-1}
273 Eigen::VectorXd inverseDiagonal = (L_inv.array().square().colwise().sum()).transpose();
274
275 return inverseDiagonal;
276}
277
278// Variant operating on the QR decomposition
279inline Eigen::VectorXd computeInverseDiagonal(Eigen::ColPivHouseholderQR<Eigen::MatrixXd> decMatrixC)
280{
281 // 1. Compute the diagonal entries of the inverse kernel matrix:
282 // We could use
283 // diag_inv_A= _decMatrixC.inverse().diagonal();
284 // as Eigen offers this for the QR decomposition, but the inverse() call is
285 // more expensive than it has to be (as we compute all entries of the inverse). On the
286 // other hand, using non strictily-positive definite functions is less relevant anyway.
287 // Still, let's try the following:
288
289 // We already have the QR decomposition. So instead of solving for the
290 // kernel matrix directly, make use of the following:
291 // A^{-1} = R^{-1}Q^{-1}
292 // Since Q is orthogonal, Q^{-1} = Q^T
293 // R is upper triangular and we need to compute the inverse (using backwards substitution)
294
295 // enables the computation of the diagonal
296 // entries of A^{-1} by evaluating the product above:
297 // A^{-1} = R^{-1} Q^T
298 // A^{-1}_{ii} = sum_{k=1}^n R^{-1}_{ik} Q^{T}_{ki}
299
300 Eigen::VectorXd inverseDiagonal;
301 const Eigen::Index n = decMatrixC.matrixR().cols();
302
303 // 1a: Compute the inverse of the lower triangular matrix L
304 // Solve R * Rinv = I
305 Eigen::MatrixXd R_inv = Eigen::MatrixXd::Identity(n, n);
306 decMatrixC.matrixR().template triangularView<Eigen::Upper>().solveInPlace(R_inv);
307 Eigen::VectorXi P = decMatrixC.colsPermutation().indices();
308 Eigen::MatrixXd Q = decMatrixC.householderQ();
309
310 // Now evaluate, caution with the column permutation
311 inverseDiagonal.resize(n);
312 for (Eigen::Index i = 0; i < n; ++i) {
313 inverseDiagonal(P(i)) = R_inv.row(i) * Q.transpose().col(P(i));
314 }
315 return inverseDiagonal;
316}
317
318template <typename RADIAL_BASIS_FUNCTION_T>
320{
321 // Implementation of LOOCV according to Rippa(1999), DOI: 10.1023/a:1018975909870
322 double loocv = 0;
323 const Eigen::Index n = lambda.size();
324 // 2: Next, compute the RBF coefficient. These are exactly the same as computed during
325 // the solve_consistent and we could also pass them into the function, depending on how
326 // often wen want to compute the LOOCV. We omit the polynomial contribution here, i.e.,
327 // the input data remains untouched
328 // Eigen::VectorXd lambda = _decMatrixC.solve(inputData);
329
330 // 3: Evaluate the Rippa formula:
331 // The error estimate is given by a component-wise division: lambda/A^{-1}_{ii}.
332 // We then compute the RMS of all LOOCV error entries (other options for the
333 // aggregation should be possible)
334 // TODO:Consider storing the reciprocal inverse diagonal entries
335 loocv = std::sqrt((lambda.array() / _inverseDiagonal.array()).array().square().sum() / n);
336
337 return loocv;
338}
339
340template <typename RADIAL_BASIS_FUNCTION_T>
341template <typename IndexContainer>
342RadialBasisFctSolver<RADIAL_BASIS_FUNCTION_T>::RadialBasisFctSolver(RADIAL_BASIS_FUNCTION_T basisFunction, const mesh::Mesh &inputMesh, const IndexContainer &inputIDs,
343 const mesh::Mesh &outputMesh, const IndexContainer &outputIDs, std::vector<bool> deadAxis, Polynomial polynomial)
344{
345 PRECICE_ASSERT(!(RADIAL_BASIS_FUNCTION_T::isStrictlyPositiveDefinite() && polynomial == Polynomial::ON), "The integrated polynomial (polynomial=\"on\") is not supported for the selected radial-basis function. Please select another radial-basis function or change the polynomial configuration.");
346 // Convert dead axis vector into an active axis array so that we can handle the reduction more easily
347 std::array<bool, 3> activeAxis({{false, false, false}});
348 std::transform(deadAxis.begin(), deadAxis.end(), activeAxis.begin(), [](const auto ax) { return !ax; });
349
350 // First, assemble the interpolation matrix and check the invertability
351 bool decompositionSuccessful = false;
352 if constexpr (RADIAL_BASIS_FUNCTION_T::isStrictlyPositiveDefinite()) {
353 _decMatrixC = buildMatrixCLU(basisFunction, inputMesh, inputIDs, activeAxis, polynomial).llt();
354 decompositionSuccessful = _decMatrixC.info() == Eigen::ComputationInfo::Success;
355 } else {
356 _decMatrixC = buildMatrixCLU(basisFunction, inputMesh, inputIDs, activeAxis, polynomial).colPivHouseholderQr();
357 decompositionSuccessful = _decMatrixC.isInvertible();
358 }
359
360 PRECICE_CHECK(decompositionSuccessful,
361 "The interpolation matrix of the RBF mapping from mesh \"{}\" to mesh \"{}\" is not invertable. "
362 "This means that the mapping problem is not well-posed. "
363 "Please check if your coupling meshes are correct (e.g. no vertices are duplicated) or reconfigure "
364 "your basis-function (e.g. reduce the support-radius).",
365 inputMesh.getName(), outputMesh.getName());
366
367 // For polynomial on, the algorithm might fail in determining the size of the system
368 if (polynomial != Polynomial::ON && computeCrossValidation) {
369 // TODO: Disable synchronization
370 precice::profiling::Event e("map.rbf.computeLOOCV");
372 }
373 // Second, assemble evaluation matrix
374 _matrixA = buildMatrixA(basisFunction, inputMesh, inputIDs, outputMesh, outputIDs, activeAxis, polynomial);
375
376 // In case we deal with separated polynomials, we need dedicated matrices for the polynomial contribution
377 if (polynomial == Polynomial::SEPARATE) {
378
379 // 4 = 1 + dimensions(3) = maximum number of polynomial parameters
380 _localActiveAxis = activeAxis;
381 unsigned int polyParams = getNumberOfPolynomials();
382
383 do {
384 // First, build matrix Q and check for the condition number
385 _matrixQ.resize(inputIDs.size(), polyParams);
386 fillPolynomialEntries(_matrixQ, inputMesh, inputIDs, 0, _localActiveAxis);
387
388 // Compute the condition number
389 Eigen::JacobiSVD<Eigen::MatrixXd> svd(_matrixQ);
390 PRECICE_ASSERT(svd.singularValues().size() > 0);
391 PRECICE_DEBUG("Singular values in polynomial solver: {}", svd.singularValues());
392 const double conditionNumber = svd.singularValues()(0) / std::max(svd.singularValues()(svd.singularValues().size() - 1), math::NUMERICAL_ZERO_DIFFERENCE);
393 PRECICE_DEBUG("Condition number: {}", conditionNumber);
394
395 // Disable one axis
396 if (conditionNumber > 1e5) {
397 reduceActiveAxis(inputMesh, inputIDs, _localActiveAxis);
398 polyParams = getNumberOfPolynomials();
399 } else {
400 break;
401 }
402 } while (true);
403
404 // allocate and fill matrix V for the outputMesh
405 _matrixV.resize(outputIDs.size(), polyParams);
406 fillPolynomialEntries(_matrixV, outputMesh, outputIDs, 0, _localActiveAxis);
407
408 // 3. compute decomposition
409 _qrMatrixQ = _matrixQ.colPivHouseholderQr();
410 }
411}
412
413template <typename RADIAL_BASIS_FUNCTION_T>
414Eigen::VectorXd RadialBasisFctSolver<RADIAL_BASIS_FUNCTION_T>::solveConservative(const Eigen::VectorXd &inputData, Polynomial polynomial) const
415{
416 PRECICE_ASSERT((_matrixV.size() > 0 && polynomial == Polynomial::SEPARATE) || _matrixV.size() == 0, _matrixV.size());
417 // TODO: Avoid temporary allocations
418 // Au is equal to the eta in our PETSc implementation
419 PRECICE_ASSERT(inputData.size() == _matrixA.rows());
420 Eigen::VectorXd Au = _matrixA.transpose() * inputData;
421 PRECICE_ASSERT(Au.size() == _matrixA.cols());
422
423 // mu in the PETSc implementation
424 Eigen::VectorXd out = _decMatrixC.solve(Au);
425
426 if (polynomial == Polynomial::SEPARATE) {
427 Eigen::VectorXd epsilon = _matrixV.transpose() * inputData;
428 PRECICE_ASSERT(epsilon.size() == _matrixV.cols());
429
430 // epsilon = Q^T * mu - epsilon (tau in the PETSc impl)
431 epsilon -= _matrixQ.transpose() * out;
432 PRECICE_ASSERT(epsilon.size() == _matrixQ.cols());
433
434 // out = out - solveTranspose tau (sigma in the PETSc impl)
435 out -= static_cast<Eigen::VectorXd>(_qrMatrixQ.transpose().solve(-epsilon));
436 }
437 return out;
438}
439
440// @todo: change the signature to Eigen::MatrixXd and process all components at once, the solve function of eigen can handle that
441template <typename RADIAL_BASIS_FUNCTION_T>
442Eigen::VectorXd RadialBasisFctSolver<RADIAL_BASIS_FUNCTION_T>::solveConsistent(Eigen::VectorXd &inputData, Polynomial polynomial) const
443{
444 PRECICE_ASSERT((_matrixQ.size() > 0 && polynomial == Polynomial::SEPARATE) || _matrixQ.size() == 0);
445 Eigen::VectorXd polynomialContribution;
446 // Solve polynomial QR and subtract it from the input data
447 if (polynomial == Polynomial::SEPARATE) {
448 polynomialContribution = _qrMatrixQ.solve(inputData);
449 inputData -= (_matrixQ * polynomialContribution);
450 }
451
452 // Integrated polynomial (and separated)
453 PRECICE_ASSERT(inputData.size() == _matrixA.cols());
454 Eigen::VectorXd p = _decMatrixC.solve(inputData);
455
456 if (polynomial != Polynomial::ON && computeCrossValidation) {
457 precice::profiling::Event e("map.rbf.evaluateLOOCV");
458 PRECICE_INFO("Cross validation error (LOOCV): {}", evaluateRippaLOOCVerror(p));
459 }
460 PRECICE_ASSERT(p.size() == _matrixA.cols());
461 Eigen::VectorXd out = _matrixA * p;
462
463 // Add the polynomial part again for separated polynomial
464 if (polynomial == Polynomial::SEPARATE) {
465 out += (_matrixV * polynomialContribution);
466 }
467 return out;
468}
469
470template <typename RADIAL_BASIS_FUNCTION_T>
471void RadialBasisFctSolver<RADIAL_BASIS_FUNCTION_T>::computeCacheData(Eigen::MatrixXd &inputData, Polynomial polynomial, Eigen::MatrixXd &polyOut, Eigen::MatrixXd &coeffsOut) const
472{
473 PRECICE_ASSERT((_matrixQ.size() > 0 && polynomial == Polynomial::SEPARATE) || _matrixQ.size() == 0);
474 // Solve polynomial QR and subtract it from the input data
475 if (polynomial == Polynomial::SEPARATE) {
476 polyOut = _qrMatrixQ.solve(inputData);
477 inputData -= (_matrixQ * polyOut);
478 }
479
480 // Integrated polynomial (and separated)
481 coeffsOut = _decMatrixC.solve(inputData);
482}
483
484template <typename RADIAL_BASIS_FUNCTION_T>
485template <typename IndexContainer>
486Eigen::VectorXd RadialBasisFctSolver<RADIAL_BASIS_FUNCTION_T>::interpolateAt(const mesh::Vertex &v, const Eigen::MatrixXd &poly, const Eigen::MatrixXd &coeffs,
487 const RADIAL_BASIS_FUNCTION_T &basisFunction, const IndexContainer &inputIDs, const mesh::Mesh &inMesh) const
488{
490 // We ignore the dead axis here for the evaluation matrix (matrixA), as the vertex coordinates are zero for a potential 2d case and there is no option in PUM to set them from the user
491
492 // Two cases we have to distinguish here:
493 // 1. there is no polynomial given, then the result is out = _matrixA * p;
494 Eigen::VectorXd result(coeffs.cols());
495 result.setZero();
496
497 // Compute RBF values for matrix A
498 const auto &out = v.rawCoords();
499 for (const auto &j : inputIDs | boost::adaptors::indexed()) {
500 const auto &in = inMesh.vertex(j.value()).rawCoords();
501 double squaredDifference = computeSquaredDifference(out, in);
502 double eval = basisFunction.evaluate(std::sqrt(squaredDifference));
503
504 result += eval * coeffs.row(j.index()).transpose();
505 }
506
507 // 2. we have a separate polynomial, then we have to add it again here;
508 // out += (_matrixV * polynomialContribution);
509 if (poly.size() > 0) {
510 // constant polynomial
511 result += 1 * poly.row(0).transpose();
512 // the linear contributions
513 int k = 1;
514 for (std::size_t d = 0; d < _localActiveAxis.size(); ++d) {
515 if (_localActiveAxis[d]) {
516 result += out[d] * poly.row(k).transpose();
517 ++k;
518 }
519 }
520 }
521 return result;
522}
523
524template <typename RADIAL_BASIS_FUNCTION_T>
525template <typename IndexContainer>
526void RadialBasisFctSolver<RADIAL_BASIS_FUNCTION_T>::addWriteDataToCache(const mesh::Vertex &v, const Eigen::VectorXd &load, Eigen::MatrixXd &epsilon, Eigen::MatrixXd &Au,
527 const RADIAL_BASIS_FUNCTION_T &basisFunction, const IndexContainer &inputIDs, const mesh::Mesh &inMesh) const
528{
530 // We ignore the dead axis here for the evaluation matrix (matrixA), as the vertex coordinates are zero for a potential 2d case and there is no option in PUM to set them from the user
531
532 // 1. The matrix contribution
533 // Compute RBF values for matrix A
534 PRECICE_ASSERT(Au.rows() == static_cast<Eigen::Index>(inputIDs.size()));
535 PRECICE_ASSERT(Au.cols() == load.size());
536
537 const auto &out = v.rawCoords();
538 for (const auto &j : inputIDs | boost::adaptors::indexed()) {
539 const auto &in = inMesh.vertex(j.value()).rawCoords();
540 double squaredDifference = computeSquaredDifference(out, in);
541 double eval = basisFunction.evaluate(std::sqrt(squaredDifference));
542
543 Au.row(j.index()) += eval * load.transpose();
544 }
545
546 // 2. we have a separate polynomial, then we have to add it again here;
547 // Eigen::VectorXd epsilon = _matrixV.transpose() * inputData;
548 if (epsilon.size() > 0) {
549 PRECICE_ASSERT(epsilon.rows() == getNumberOfPolynomials());
550 PRECICE_ASSERT(epsilon.cols() == load.size());
551 // constant polynomial
552 epsilon.row(0) += 1 * load.transpose();
553 // the linear contributions
554 int k = 1;
555 for (std::size_t d = 0; d < _localActiveAxis.size(); ++d) {
556 if (_localActiveAxis[d]) {
557 epsilon.row(k) += out[d] * load.transpose();
558 ++k;
559 }
560 }
561 }
562}
563
564template <typename RADIAL_BASIS_FUNCTION_T>
565void RadialBasisFctSolver<RADIAL_BASIS_FUNCTION_T>::evaluateConservativeCache(Eigen::MatrixXd &epsilon, const Eigen::MatrixXd &Au, Eigen::MatrixXd &out) const
566{
567 // mu in the PETSc implementation
568 out = _decMatrixC.solve(Au);
569
570 if (epsilon.size() > 0) {
571 epsilon -= _matrixQ.transpose() * out;
572 // out = out - solveTranspose tau (sigma in the PETSc impl)
573 out -= static_cast<Eigen::MatrixXd>(_qrMatrixQ.transpose().solve(-epsilon));
574 }
575}
576
577template <typename RADIAL_BASIS_FUNCTION_T>
583
584template <typename RADIAL_BASIS_FUNCTION_T>
586{
587 return _decMatrixC.cols();
588}
589
590template <typename RADIAL_BASIS_FUNCTION_T>
592{
593 return _matrixA.rows();
594}
595
596template <typename RADIAL_BASIS_FUNCTION_T>
601
602} // namespace precice::mapping
#define PRECICE_DEBUG(...)
Definition LogMacros.hpp:61
#define PRECICE_TRACE(...)
Definition LogMacros.hpp:92
#define PRECICE_INFO(...)
Definition LogMacros.hpp:14
#define PRECICE_CHECK(check,...)
Definition LogMacros.hpp:32
T accumulate(T... args)
#define PRECICE_ASSERT(...)
Definition assertion.hpp:85
T begin(T... args)
This class provides a lightweight logger.
Definition Logger.hpp:17
void computeCacheData(Eigen::MatrixXd &inputData, Polynomial polynomial, Eigen::MatrixXd &polyOut, Eigen::MatrixXd &coeffsOut) const
double evaluateRippaLOOCVerror(const Eigen::VectorXd &lambda) const
Eigen::MatrixXd _matrixA
Evaluation matrix (output x input)
Eigen::VectorXd solveConsistent(Eigen::VectorXd &inputData, Polynomial polynomial) const
Maps the given input data.
std::conditional_t< RADIAL_BASIS_FUNCTION_T::isStrictlyPositiveDefinite(), Eigen::LLT< Eigen::MatrixXd >, Eigen::ColPivHouseholderQR< Eigen::MatrixXd > > DecompositionType
Eigen::VectorXd solveConservative(const Eigen::VectorXd &inputData, Polynomial polynomial) const
Maps the given input data.
Eigen::VectorXd interpolateAt(const mesh::Vertex &v, const Eigen::MatrixXd &poly, const Eigen::MatrixXd &coeffs, const RADIAL_BASIS_FUNCTION_T &function, const IndexContainer &inputIDs, const mesh::Mesh &inMesh) const
Eigen::ColPivHouseholderQR< Eigen::MatrixXd > _qrMatrixQ
Decomposition of the polynomial (for separate polynomial)
void addWriteDataToCache(const mesh::Vertex &v, const Eigen::VectorXd &load, Eigen::MatrixXd &epsilon, Eigen::MatrixXd &Au, const RADIAL_BASIS_FUNCTION_T &basisFunction, const IndexContainer &inputIDs, const mesh::Mesh &inMesh) const
Eigen::VectorXd _inverseDiagonal
Diagonal entris of the inverse matrix C, requires for the Rippa scheme.
RadialBasisFctSolver()=default
Default constructor.
Eigen::MatrixXd _matrixV
Polynomial matrix of the output mesh (for separate polynomial)
DecompositionType _decMatrixC
Decomposition of the interpolation matrix.
void evaluateConservativeCache(Eigen::MatrixXd &epsilon, const Eigen::MatrixXd &Au, Eigen::MatrixXd &result) const
Eigen::MatrixXd _matrixQ
Polynomial matrix of the input mesh (for separate polynomial)
Container and creator for meshes.
Definition Mesh.hpp:38
int getDimensions() const
Definition Mesh.cpp:100
const std::string & getName() const
Returns the name of the mesh, as set in the config file.
Definition Mesh.cpp:220
Vertex & vertex(VertexID id)
Mutable access to a vertex by VertexID.
Definition Mesh.cpp:43
Vertex of a mesh.
Definition Vertex.hpp:16
const RawCoords & rawCoords() const
Direct access to the coordinates.
Definition Vertex.hpp:121
T count(T... args)
T end(T... args)
T make_pair(T... args)
T max(T... args)
T minmax_element(T... args)
contains data mapping from points to meshes.
double computeSquaredDifference(const std::array< double, 3 > &u, std::array< double, 3 > v, const std::array< bool, 3 > &activeAxis={{true, true, true}})
Deletes all dead directions from fullVector and returns a vector of reduced dimensionality.
Eigen::VectorXd computeInverseDiagonal(Eigen::LLT< Eigen::MatrixXd > decMatrixC)
void fillPolynomialEntries(Eigen::MatrixXd &matrix, const mesh::Mesh &mesh, const IndexContainer &IDs, Eigen::Index startIndex, std::array< bool, 3 > activeAxis)
constexpr void reduceActiveAxis(const mesh::Mesh &mesh, const IndexContainer &IDs, std::array< bool, 3 > &axis)
given the active axis, computes sets the axis with the lowest spatial expansion to dead
Eigen::MatrixXd buildMatrixCLU(RADIAL_BASIS_FUNCTION_T basisFunction, const mesh::Mesh &inputMesh, const IndexContainer &inputIDs, std::array< bool, 3 > activeAxis, Polynomial polynomial)
Eigen::MatrixXd buildMatrixA(RADIAL_BASIS_FUNCTION_T basisFunction, const mesh::Mesh &inputMesh, const IndexContainer &inputIDs, const mesh::Mesh &outputMesh, const IndexContainer outputIDs, std::array< bool, 3 > activeAxis, Polynomial polynomial)
Polynomial
How to handle the polynomial?
constexpr double NUMERICAL_ZERO_DIFFERENCE
provides Mesh, Data and primitives.
Eigen::MatrixXd invertLowerTriangularBlockwise(const Eigen::MatrixXd &L)
Implements an iterative block scheme to determine the inverse of a lower triangular matrix which is m...
T size(T... args)
T sort(T... args)
T sqrt(T... args)
T transform(T... args)