PhysicsBasedAnimationToolkit 0.0.10
Cross-platform C++20 library of algorithms and data structures commonly used in computer graphics research on physically-based simulation.
Loading...
Searching...
No Matches
MomentFitting.h
Go to the documentation of this file.
1
10
11#ifndef PBAT_MATH_MOMENTFITTING_H
12#define PBAT_MATH_MOMENTFITTING_H
13
14#include "Concepts.h"
15#include "pbat/Aliases.h"
16#include "pbat/common/ArgSort.h"
17#include "pbat/common/Eigen.h"
21
22#include <algorithm>
23#include <exception>
24#include <limits>
25#include <tbb/parallel_for.h>
26#include <tuple>
27#include <unsupported/Eigen/NNLS>
28#include <utility>
29
30namespace pbat {
31namespace math {
32
39template <int Dims, int Order>
41{
42 static auto constexpr kOrder = Order;
43 static auto constexpr kDims = Dims;
44
49};
50
60template <polynomial::CBasis TBasis, CFixedPointPolynomialQuadratureRule TQuad>
62{
63 static_assert(
64 TBasis::kDims == TQuad::kDims,
65 "Dimensions of the quadrature rule and the polynomial basis must match, i.e. a k-D "
66 "polynomial must be fit in a k-D integration domain.");
68 auto Xg = common::ToEigen(Q.points).reshaped(TQuad::kDims + 1, TQuad::kPoints);
69 // Eigen::Map<Matrix<TQuad::kDims + 1, TQuad::kPoints> const> Xg(Q.points.data());
70 for (auto g = 0u; g < TQuad::kPoints; ++g)
71 P.col(g) = Pb.eval(Xg.col(g).template segment<TQuad::kDims>(1));
72 return P;
73}
74
84template <polynomial::CBasis TBasis, CPolynomialQuadratureRule TQuad>
86{
87 static_assert(
88 TBasis::kDims == TQuad::kDims,
89 "Dimensions of the quadrature rule and the polynomial basis must match, i.e. a k-D "
90 "polynomial must be fit in a k-D integration domain.");
91 Matrix<TBasis::kSize, Eigen::Dynamic> P(TBasis::kSize, Q.weights.size());
92 auto Xg = common::ToEigen(Q.points).reshaped(TQuad::kDims + 1, Q.weights.size());
93 for (auto g = 0u; g < Xg.cols(); ++g)
94 P.col(g) = Pb.eval(Xg.col(g).template segment<TQuad::kDims>(1));
95 return P;
96}
97
107template <polynomial::CBasis TBasis, class TDerivedXg>
109ReferenceMomentFittingMatrix(TBasis const& Pb, Eigen::MatrixBase<TDerivedXg> const& Xg)
110{
112 static_assert(
113 TBasis::kDims == QuadratureType::kDims,
114 "Dimensions of the quadrature rule and the polynomial basis must match, i.e. a k-D "
115 "polynomial must be fit in a k-D integration domain.");
116 Matrix<TBasis::kSize, Eigen::Dynamic> P(TBasis::kSize, Xg.cols());
117 for (auto g = 0u; g < Xg.cols(); ++g)
118 P.col(g) = Pb.eval(Xg.col(g));
119 return P;
120}
121
133template <class TDerivedP, class TDerivedB>
135 Eigen::MatrixBase<TDerivedP> const& P,
136 Eigen::DenseBase<TDerivedB> const& b,
137 Index maxIterations = 10,
138 Scalar precision = std::numeric_limits<Scalar>::epsilon())
139{
140 using MatrixType = TDerivedP;
141 Eigen::NNLS<MatrixType> nnls{};
142 nnls.setMaxIterations(maxIterations);
143 nnls.setTolerance(precision);
144 nnls.compute(P.derived());
145 auto w = nnls.solve(b);
146 if (nnls.info() != Eigen::ComputationInfo::Success)
147 {
148 std::string what = "Moment fitting's non-negative least-squares failed with error: ";
149 if (nnls.info() == Eigen::ComputationInfo::InvalidInput)
150 what += "InvalidInput";
151 if (nnls.info() == Eigen::ComputationInfo::NoConvergence)
152 what += "NoConvergence";
153 if (nnls.info() == Eigen::ComputationInfo::NumericalIssue)
154 what += "NumericalIssue";
155 throw std::invalid_argument(what);
156 }
157 return w;
158}
159
171template <polynomial::CBasis Polynomial, class TDerivedXg, class TDerivedWg>
173 Polynomial const& P,
174 Eigen::MatrixBase<TDerivedXg> const& Xg,
175 Eigen::DenseBase<TDerivedWg> const& wg)
176{
178 b.setZero();
179 for (auto g = 0; g < wg.size(); ++g)
180 b += wg(g) * P.eval(Xg.col(g));
181 return b;
182}
183
203template <polynomial::CBasis Polynomial, class TDerivedXg1, class TDerivedXg2, class TDerivedWg2>
205 Polynomial const& P,
206 Eigen::MatrixBase<TDerivedXg1> const& Xg1,
207 Eigen::MatrixBase<TDerivedXg2> const& Xg2,
208 Eigen::DenseBase<TDerivedWg2> const& wg2,
209 Index maxIterations = 10,
210 Scalar precision = std::numeric_limits<Scalar>::epsilon())
211{
212 auto b = Integrate(P, Xg2, wg2);
213 auto M = ReferenceMomentFittingMatrix(P, Xg1);
214 auto w = MomentFittedWeights(M, b, maxIterations, precision);
215 return w;
216}
217
246template <
247 auto Order,
248 class TDerivedS1,
249 class TDerivedXi1,
250 class TDerivedS2,
251 class TDerivedXi2,
252 class TDerivedWg2>
253std::pair<VectorX, VectorX> TransferQuadrature(
254 Eigen::DenseBase<TDerivedS1> const& S1,
255 Eigen::MatrixBase<TDerivedXi1> const& Xi1,
256 Eigen::DenseBase<TDerivedS2> const& S2,
257 Eigen::MatrixBase<TDerivedXi2> const& Xi2,
258 Eigen::DenseBase<TDerivedWg2> const& wi2,
259 Index nSimplices = -1,
260 bool bEvaluateError = false,
261 Index maxIterations = 10,
262 Scalar precision = std::numeric_limits<Scalar>::epsilon())
263{
264 // Compute adjacency graph from simplices s to their quadrature points Xi
265 using common::ArgSort;
266 using common::Counts;
267 using common::CumSum;
268 using common::ToEigen;
269 if (nSimplices < 0)
270 nSimplices = std::max(
271 *std::max_element(S1.begin(), S1.end()),
272 *std::max_element(S2.begin(), S2.end())) +
273 1;
274 IndexVectorX S1P = CumSum(Counts(S1.begin(), S1.end(), nSimplices));
275 IndexVectorX S2P = CumSum(Counts(S2.begin(), S2.end(), nSimplices));
276 IndexVectorX S1N = ArgSort<Index>(S1.size(), [&](auto si, auto sj) { return S1(si) < S1(sj); });
277 IndexVectorX S2N = ArgSort<Index>(S2.size(), [&](auto si, auto sj) { return S2(si) < S2(sj); });
278 // Find weights wg1 that fit the given quadrature rule Xi2, wi2 on simplices S2
279 auto fSolveWeights = [maxIterations,
280 precision,
281 bEvaluateError](auto const& Xg1, auto const& Xg2, auto const& wg2) {
282 if (Xg1.rows() == 1)
283 {
285 auto w = TransferQuadrature(P, Xg1, Xg2, wg2, maxIterations, precision);
286 Scalar error(0);
287 if (bEvaluateError)
288 {
289 auto b1 = math::Integrate(P, Xg1, w);
290 auto b2 = math::Integrate(P, Xg2, wg2);
291 error = (b1 - b2).squaredNorm();
292 }
293 return std::make_pair(w, error);
294 }
295 if (Xg1.rows() == 2)
296 {
298 auto w = TransferQuadrature(P, Xg1, Xg2, wg2, maxIterations, precision);
299 Scalar error(0);
300 if (bEvaluateError)
301 {
302 auto b1 = math::Integrate(P, Xg1, w);
303 auto b2 = math::Integrate(P, Xg2, wg2);
304 error = (b1 - b2).squaredNorm();
305 }
306 return std::make_pair(w, error);
307 }
308 if (Xg1.rows() == 3)
309 {
311 auto w = TransferQuadrature(P, Xg1, Xg2, wg2, maxIterations, precision);
312 Scalar error(0);
313 if (bEvaluateError)
314 {
315 auto b1 = math::Integrate(P, Xg1, w);
316 auto b2 = math::Integrate(P, Xg2, wg2);
317 error = (b1 - b2).squaredNorm();
318 }
319 return std::make_pair(w, error);
320 }
321 throw std::invalid_argument(
322 "Expected quadrature points in reference simplex space of dimensions (i.e. rows) 1,2 "
323 "or 3.");
324 };
325 // Solve moment fitting on each simplex
326 VectorX error = VectorX::Zero(nSimplices);
327 VectorX wi1 = VectorX::Zero(Xi1.cols());
328 tbb::parallel_for(Index(0), nSimplices, [&](Index s) {
329 auto S1begin = S1P(s);
330 auto S1end = S1P(s + 1);
331 if (S1end > S1begin)
332 {
333 auto s1inds = S1N(Eigen::seq(S1begin, S1end - 1));
334 MatrixX Xg1 = Xi1(Eigen::placeholders::all, s1inds);
335 auto S2begin = S2P(s);
336 auto S2end = S2P(s + 1);
337 auto s2inds = S2N(Eigen::seq(S2begin, S2end - 1));
338 MatrixX Xg2 = Xi2(Eigen::placeholders::all, s2inds);
339 VectorX wg2 = wi2(s2inds);
340 auto [wg1, err] = fSolveWeights(Xg1, Xg2, wg2);
341 wi1(s1inds) = wg1;
342 error(s) = err;
343 }
344 });
345 return {wi1, error};
346}
347
375template <
376 int Order,
377 class TDerivedS1,
378 class TDerivedX1,
379 class TDerivedS2,
380 class TDerivedX2,
381 class TDerivedW2>
382std::tuple<MatrixX /*P*/, MatrixX /*B*/, IndexVectorX /*prefix into columns of P*/>
384 Eigen::DenseBase<TDerivedS1> const& S1,
385 Eigen::MatrixBase<TDerivedX1> const& X1,
386 Eigen::DenseBase<TDerivedS2> const& S2,
387 Eigen::MatrixBase<TDerivedX2> const& X2,
388 Eigen::DenseBase<TDerivedW2> const& w2,
389 Index nSimplices = Index(-1))
390{
391 // Compute adjacency graph from simplices s to their quadrature points Xi
392 using common::ArgSort;
393 using common::Counts;
394 using common::CumSum;
395 using common::ToEigen;
396 if (nSimplices < 0)
397 nSimplices = std::max(
398 *std::max_element(S1.begin(), S1.end()),
399 *std::max_element(S2.begin(), S2.end())) +
400 1;
401 IndexVectorX S1P = CumSum(Counts<Index>(S1.begin(), S1.end(), nSimplices));
402 IndexVectorX S2P = CumSum(Counts<Index>(S2.begin(), S2.end(), nSimplices));
403 IndexVectorX S1N = ArgSort<Index>(S1.size(), [&](auto si, auto sj) { return S1(si) < S1(sj); });
404 IndexVectorX S2N = ArgSort<Index>(S2.size(), [&](auto si, auto sj) { return S2(si) < S2(sj); });
405 // Assemble moment fitting matrices and their rhs
406 auto fPolyRows = [](MatrixX const& Xg) {
407 if (Xg.rows() == 1)
409 if (Xg.rows() == 2)
411 if (Xg.rows() == 3)
413 throw std::invalid_argument(
414 "Expected quadrature points in reference simplex space of dimensions (i.e. rows) 1,2 "
415 "or 3.");
416 };
417 auto fAssembleSystem =
418 [](auto const& Xg1, auto const& Xg2, auto const& wg2) -> std::pair<MatrixX, VectorX> {
419 if (Xg1.rows() == 1)
420 {
422 auto M = ReferenceMomentFittingMatrix(P, Xg1);
423 auto b = Integrate(P, Xg2, wg2);
424 return {M, b};
425 }
426 if (Xg1.rows() == 2)
427 {
429 auto M = ReferenceMomentFittingMatrix(P, Xg1);
430 auto b = Integrate(P, Xg2, wg2);
431 return {M, b};
432 }
433 if (Xg1.rows() == 3)
434 {
436 auto M = ReferenceMomentFittingMatrix(P, Xg1);
437 auto b = Integrate(P, Xg2, wg2);
438 return {M, b};
439 }
440 throw std::invalid_argument(
441 "Expected quadrature points in reference simplex space of dimensions (i.e. rows) 1,2 "
442 "or 3.");
443 };
444 auto nrows = fPolyRows(X1);
445
446 // Count actual number of systems and their dimensions
447 Index nSystems{0};
448 for (Index s = 0; s < nSimplices; ++s)
449 if (S1P(s + 1) > S1P(s))
450 ++nSystems;
451 IndexVectorX nQuads(nSystems);
452 nQuads.setZero();
453 for (Index s = 0, sy = 0; s < nSimplices; ++s)
454 {
455 if (S1P(s + 1) > S1P(s))
456 {
457 nQuads(sy++) = S1P(s + 1) - S1P(s);
458 }
459 }
460 IndexVectorX const prefix = CumSum(nQuads);
461
462 MatrixX P(nrows, prefix(Eigen::placeholders::last));
463 MatrixX B(nrows, nSystems);
464 B.setZero();
465 for (Index s = 0, sy = 0; s < nSimplices; ++s)
466 {
467 auto S1begin = S1P(s);
468 auto S1end = S1P(s + 1);
469 if (S1end > S1begin)
470 {
471 auto s1inds = S1N(Eigen::seq(S1begin, S1end - 1));
472 MatrixX Xg1 = X1(Eigen::placeholders::all, s1inds);
473 auto S2begin = S2P(s);
474 auto S2end = S2P(s + 1);
475 auto s2inds = S2N(Eigen::seq(S2begin, S2end - 1));
476 MatrixX Xg2 = X2(Eigen::placeholders::all, s2inds);
477 VectorX wg2 = w2(s2inds);
478 auto [Ps, bs] = fAssembleSystem(Xg1, Xg2, wg2);
479 P.block(0, prefix(sy), nrows, nQuads(sy)) = Ps;
480 B.col(sy) = bs;
481 ++sy;
482 }
483 }
484 return std::make_tuple(P, B, prefix);
485}
486
500template <class TDerivedM, class TDerivedP>
502 Eigen::MatrixBase<TDerivedM> const& M,
503 Eigen::DenseBase<TDerivedP> const& P)
504{
505 auto const nblocks = P.size() - 1;
506 auto const nblockrows = M.rows();
507 auto const nrows = nblockrows * nblocks;
508 auto const ncols = P(Eigen::placeholders::last);
509 CSRMatrix GM(nrows, ncols);
510 IndexVectorX reserves(nrows);
511 for (auto b = 0; b < nblocks; ++b)
512 {
513 auto begin = P(b);
514 auto end = P(b + 1);
515 auto const nblockcols = end - begin;
516 auto const offset = b * nblockrows;
517 for (auto i = 0; i < nblockrows; ++i)
518 reserves(offset + i) = nblockcols;
519 }
520 GM.reserve(reserves);
521 for (auto b = 0; b < nblocks; ++b)
522 {
523 auto begin = P(b);
524 auto end = P(b + 1);
525 auto const nblockcols = end - begin;
526 auto Mb = M.block(0, begin, nblockrows, nblockcols);
527 auto const roffset = b * nblockrows;
528 auto const coffset = begin;
529 for (auto i = 0; i < nblockrows; ++i)
530 {
531 for (auto j = 0; j < nblockcols; ++j)
532 {
533 GM.insert(roffset + i, coffset + j) = Mb(i, j);
534 }
535 }
536 }
537 return GM;
538}
539
540} // namespace math
541} // namespace pbat
542
543#endif // PBAT_MATH_MOMENTFITTING_H
Non-intrusive sorting.
Basis polynomials in dimensions and orders .
Eigen adaptors for ranges.
This file contains the concepts used in the math module.
Concepts for polynomial basis.
auto Counts(auto begin, auto end, TIndex ncounts) -> Eigen::Vector< TIndex, Eigen::Dynamic >
Counts the number of occurrences of each integer in a contiguous range.
Definition Indexing.h:57
auto CumSum(R &&sizes) -> Eigen::Vector< TIndex, Eigen::Dynamic >
Cumulative sum of a range of integers.
Definition Indexing.h:34
auto ArgSort(TIndex n, FLess less) -> Eigen::Vector< TIndex, Eigen::Dynamic >
Computes the indices that would sort an array.
Definition ArgSort.h:32
auto ToEigen(R &&r) -> Eigen::Map< Eigen::Vector< std::ranges::range_value_t< R >, Eigen::Dynamic > const >
Map a range of scalars to an eigen vector of such scalars.
Definition Eigen.h:28
Math related functionality.
Definition Concepts.h:19
Vector< Polynomial::kSize > Integrate(Polynomial const &P, Eigen::MatrixBase< TDerivedXg > const &Xg, Eigen::DenseBase< TDerivedWg > const &wg)
Computes given an existing quadrature rule .
Definition MomentFitting.h:172
CSRMatrix BlockDiagonalReferenceMomentFittingSystem(Eigen::MatrixBase< TDerivedM > const &M, Eigen::DenseBase< TDerivedP > const &P)
Block diagonalizes the given reference moment fitting systems (see ReferenceMomentFittingSystems()) i...
Definition MomentFitting.h:501
VectorX MomentFittedWeights(Eigen::MatrixBase< TDerivedP > const &P, Eigen::DenseBase< TDerivedB > const &b, Index maxIterations=10, Scalar precision=std::numeric_limits< Scalar >::epsilon())
Computes non-negative quadrature weights by moment fitting.
Definition MomentFitting.h:134
Vector< TDerivedXg1::ColsAtCompileTime > TransferQuadrature(Polynomial const &P, Eigen::MatrixBase< TDerivedXg1 > const &Xg1, Eigen::MatrixBase< TDerivedXg2 > const &Xg2, Eigen::DenseBase< TDerivedWg2 > const &wg2, Index maxIterations=10, Scalar precision=std::numeric_limits< Scalar >::epsilon())
Obtain weights by transferring an existing quadrature rule defined on a simplex onto a new quadrat...
Definition MomentFitting.h:204
std::tuple< MatrixX, MatrixX, IndexVectorX > ReferenceMomentFittingSystems(Eigen::DenseBase< TDerivedS1 > const &S1, Eigen::MatrixBase< TDerivedX1 > const &X1, Eigen::DenseBase< TDerivedS2 > const &S2, Eigen::MatrixBase< TDerivedX2 > const &X2, Eigen::DenseBase< TDerivedW2 > const &w2, Index nSimplices=Index(-1))
Computes reference moment fitting systems for all simplices , given an existing quadrature rule defi...
Definition MomentFitting.h:383
Matrix< TBasis::kSize, TQuad::kPoints > ReferenceMomentFittingMatrix(TBasis const &Pb, TQuad const &Q)
Compute the moment fitting matrix in the reference simplex space.
Definition MomentFitting.h:61
The main namespace of the library.
Definition Aliases.h:15
Eigen::Vector< Index, Eigen::Dynamic > IndexVectorX
Dynamic-size index vector type.
Definition Aliases.h:49
Eigen::Matrix< Scalar, Eigen::Dynamic, Eigen::Dynamic > MatrixX
Dynamic-size matrix type.
Definition Aliases.h:34
Eigen::Vector< Scalar, N > Vector
Fixed-size vector type.
Definition Aliases.h:24
Eigen::Vector< Scalar, Eigen::Dynamic > VectorX
Dynamic-size vector type.
Definition Aliases.h:33
std::ptrdiff_t Index
Index type.
Definition Aliases.h:17
double Scalar
Scalar type.
Definition Aliases.h:18
Eigen::SparseMatrix< Scalar, Eigen::RowMajor > CSRMatrix
Row-major sparse matrix type.
Definition Aliases.h:53
Eigen::Matrix< Scalar, Rows, Cols > Matrix
Fixed-size matrix type.
Definition Aliases.h:31
Quadrature rule with variable points and weights.
Definition MomentFitting.h:41
MatrixX points
Definition MomentFitting.h:45
static auto constexpr kDims
Spatial dimensions.
Definition MomentFitting.h:43
VectorX weights
Definition MomentFitting.h:47
static auto constexpr kOrder
Polynomial order.
Definition MomentFitting.h:42
Orthonormal polynomial basis in dimensions and order .
Definition Basis.h:126
static constexpr int kSize
Number of basis functions.
Definition Basis.h:131