Skip to content

Commit 8168d04

Browse files
committed
Test mesh quadrature transfer
1 parent eb7833f commit 8168d04

File tree

2 files changed

+98
-43
lines changed

2 files changed

+98
-43
lines changed

source/pbat/math/MomentFitting.cpp

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,39 @@ TEST_CASE("[math] MomentFitting")
4747
math::test::TestFixedQuadrature<3, 3>(precision);
4848
math::test::TestFixedQuadrature<3, 4>(precision);
4949
}
50+
SUBCASE("Moment fitting on mesh reproduces existing quadrature rule")
51+
{
52+
// Arrange
53+
auto constexpr kOrder = 2;
54+
auto constexpr kDims = 3;
55+
auto constexpr kMaxIterations = 20;
56+
math::SymmetricSimplexPolynomialQuadratureRule<kDims, kOrder> Q2{};
57+
auto X2Q = common::ToEigen(Q2.points).reshaped(kDims + 1, Q2.kPoints);
58+
auto w2Q = common::ToEigen(Q2.weights);
59+
60+
IndexVectorX S1(8), S2(8);
61+
S2 << 0, 0, 0, 1, 1, 1, 1, 0;
62+
S1 = S2;
63+
MatrixX X1(kDims + 1, 8), X2(kDims + 1, 8);
64+
VectorX w2(8);
65+
X2 << X2Q.col(0), X2Q.col(1), X2Q.col(2), X2Q.col(0), X2Q.col(1), X2Q.col(2), X2Q.col(3),
66+
X2Q.col(3);
67+
w2 << w2Q(0), w2Q(1), w2Q(2), w2Q(0), w2Q(1), w2Q(2), w2Q(3), w2Q(3);
68+
X1 = X2;
69+
70+
// Act
71+
bool constexpr bEvaluateError{true};
72+
auto [w1, error] = math::TransferQuadrature<kOrder>(
73+
S1,
74+
X1.bottomRows(kDims),
75+
S2,
76+
X2.bottomRows(kDims),
77+
w2,
78+
bEvaluateError,
79+
kMaxIterations);
80+
81+
// Assert
82+
CHECK((w1.array() >= Scalar(0)).all());
83+
CHECK_LT(error.maxCoeff(), 1e-10);
84+
}
5085
}

source/pbat/math/MomentFitting.h

Lines changed: 63 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include <limits>
1414
#include <tbb/parallel_for.h>
1515
#include <unsupported/Eigen/NNLS>
16+
#include <utility>
1617

1718
namespace pbat {
1819
namespace math {
@@ -169,9 +170,11 @@ Vector<TDerivedXg1::ColsAtCompileTime> TransferQuadrature(
169170
* @param Xi2 |#dims|x|#old quad.pts.| array of quadrature point positions defined in reference
170171
* simplex space
171172
* @param wi2 |#old quad.pts.|x1 array of quadrature weights
173+
* @param bEvaluateError Whether to compute the integration error on the new quadrature rule
172174
* @param maxIterations Maximum number of non-negative least-squares active set solver
173175
* @param precision Convergence threshold
174-
* @return
176+
* @return (w, e) where w are the quadrature weights associated with points Xi1, and e is the
177+
* integration error in each simplex (zeros if not bEvaluateError).
175178
*/
176179
template <
177180
auto Order,
@@ -180,82 +183,99 @@ template <
180183
class TDerivedS2,
181184
class TDerivedXi2,
182185
class TDerivedWg2>
183-
VectorX TransferQuadrature(
186+
std::pair<VectorX, VectorX> TransferQuadrature(
184187
Eigen::DenseBase<TDerivedS1> const& S1,
185188
Eigen::MatrixBase<TDerivedXi1> const& Xi1,
186189
Eigen::DenseBase<TDerivedS2> const& S2,
187190
Eigen::MatrixBase<TDerivedXi2> const& Xi2,
188191
Eigen::MatrixBase<TDerivedWg2> const& wi2,
192+
bool bEvaluateError = false,
189193
Index maxIterations = 10,
190194
Scalar precision = std::numeric_limits<Scalar>::epsilon())
191195
{
192196
// Compute adjacency graph from simplices s to their quadrature points Xi
197+
using common::ArgSort;
198+
using common::Counts;
199+
using common::CumSum;
200+
using common::ToEigen;
193201
auto nsimplices =
194202
std::max(*std::max_element(S1.begin(), S1.end()), *std::max_element(S2.begin(), S2.end())) +
195203
1;
196-
std::vector<Index> S1P =
197-
common::CumSum(common::Counts<Index>(S1.begin(), S1.end(), nsimplices));
198-
std::vector<Index> S2P =
199-
common::CumSum(common::Counts<Index>(S2.begin(), S2.end(), nsimplices));
200-
std::vector<Index> S1N =
201-
common::ArgSort<Index>(S1.size(), [](auto si, auto sj) { return S1(si) < S1(sj); });
202-
std::vector<Index> S2N =
203-
common::ArgSort<Index>(S2.size(), [](auto si, auto sj) { return S2(si) < S2(sj); });
204+
std::vector<Index> S1P = CumSum(Counts<Index>(S1.begin(), S1.end(), nsimplices));
205+
std::vector<Index> S2P = CumSum(Counts<Index>(S2.begin(), S2.end(), nsimplices));
206+
IndexVectorX S1N =
207+
ToEigen(ArgSort<Index>(S1.size(), [&](auto si, auto sj) { return S1(si) < S1(sj); }));
208+
IndexVectorX S2N =
209+
ToEigen(ArgSort<Index>(S2.size(), [&](auto si, auto sj) { return S2(si) < S2(sj); }));
204210
// Find weights wg1 that fit the given quadrature rule Xi2, wi2 on simplices S2
205-
auto fSolveWeights = [maxIterations,
206-
precision](MatrixX const& Xg1, MatrixX const& Xg2, VectorX const& wg2) {
211+
auto fSolveWeights = [maxIterations, precision, bEvaluateError](
212+
MatrixX const& Xg1,
213+
MatrixX const& Xg2,
214+
VectorX const& wg2) {
207215
if (Xg1.rows() == 1)
208216
{
209-
return TransferQuadrature(
210-
OrthonormalPolynomialBasis<1, Order>{},
211-
Xg1,
212-
Xg2,
213-
wg2,
214-
maxIterations,
215-
precision);
217+
OrthonormalPolynomialBasis<1, Order> P{};
218+
auto w = TransferQuadrature(P, Xg1, Xg2, wg2, maxIterations, precision);
219+
Scalar error(0);
220+
if (bEvaluateError)
221+
{
222+
auto b1 = math::Integrate(P, Xg1, w);
223+
auto b2 = math::Integrate(P, Xg2, wg2);
224+
error = (b1 - b2).squaredNorm();
225+
}
226+
return std::make_pair(w, error);
216227
}
217228
if (Xg1.rows() == 2)
218229
{
219-
return TransferQuadrature(
220-
OrthonormalPolynomialBasis<2, Order>{},
221-
Xg1,
222-
Xg2,
223-
wg2,
224-
maxIterations,
225-
precision);
230+
OrthonormalPolynomialBasis<2, Order> P{};
231+
auto w = TransferQuadrature(P, Xg1, Xg2, wg2, maxIterations, precision);
232+
Scalar error(0);
233+
if (bEvaluateError)
234+
{
235+
auto b1 = math::Integrate(P, Xg1, w);
236+
auto b2 = math::Integrate(P, Xg2, wg2);
237+
error = (b1 - b2).squaredNorm();
238+
}
239+
return std::make_pair(w, error);
226240
}
227241
if (Xg1.rows() == 3)
228242
{
229-
return TransferQuadrature(
230-
OrthonormalPolynomialBasis<3, Order>{},
231-
Xg1,
232-
Xg2,
233-
wg2,
234-
maxIterations,
235-
precision);
243+
OrthonormalPolynomialBasis<3, Order> P{};
244+
auto w = TransferQuadrature(P, Xg1, Xg2, wg2, maxIterations, precision);
245+
Scalar error(0);
246+
if (bEvaluateError)
247+
{
248+
auto b1 = math::Integrate(P, Xg1, w);
249+
auto b2 = math::Integrate(P, Xg2, wg2);
250+
error = (b1 - b2).squaredNorm();
251+
}
252+
return std::make_pair(w, error);
236253
}
237254
throw std::invalid_argument(
238255
"Expected quadrature points in reference simplex space of dimensions (i.e. rows) 1,2 "
239256
"or 3.");
240257
};
241258

242-
VectorX wg1 = VectorX::Zero(Xi1.cols());
259+
VectorX error = VectorX::Zero(nsimplices);
260+
VectorX wi1 = VectorX::Zero(Xi1.cols());
243261
tbb::parallel_for(Index(0), nsimplices, [&](Index s) {
244262
auto S1begin = S1P[s];
245263
auto S1end = S1P[s + 1];
246264
if (S1end > S1begin)
247265
{
248-
auto s1inds = S1N(Eigen::seq(S1begin, S1end - 1));
249-
MatrixX Xg1 = Xi1(Eigen::placeholders::all, s1inds);
250-
auto S2begin = S2P[s];
251-
auto S2end = S2P[s + 1];
252-
auto s2inds = S2N(Eigen::seq(S2begin, S2end - 1));
253-
MatrixX Xg2 = Xi2(Eigen::placeholders::all, s2inds);
254-
VectorX wg2 = wi2(s2inds);
255-
wg1(s1inds) = fSolveWeights(Xg1, Xg2, wg2);
266+
auto s1inds = S1N(Eigen::seq(S1begin, S1end - 1));
267+
MatrixX Xg1 = Xi1(Eigen::placeholders::all, s1inds);
268+
auto S2begin = S2P[s];
269+
auto S2end = S2P[s + 1];
270+
auto s2inds = S2N(Eigen::seq(S2begin, S2end - 1));
271+
MatrixX Xg2 = Xi2(Eigen::placeholders::all, s2inds);
272+
VectorX wg2 = wi2(s2inds);
273+
auto [wg1, err] = fSolveWeights(Xg1, Xg2, wg2);
274+
wi1(s1inds) = wg1;
275+
error(s) = err;
256276
}
257277
});
258-
return wg1;
278+
return {wi1, error};
259279
}
260280

261281
} // namespace math

0 commit comments

Comments
 (0)