Snapshot optimized solver and audit diagnostics before numerical experiments
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <memory>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
@@ -260,3 +264,295 @@ TEST_CASE(
|
||||
CheckMatrixExactly(retained->GetValues(0), firstExpected);
|
||||
CheckMatrixExactly(retained->GetValues(retained->GetPointCount() - 1), lastExpected);
|
||||
}
|
||||
|
||||
TEST_CASE("Tensor reference evaluation matches native H1 hex values and gradients",
|
||||
tags::unit &tags::quadrature) {
|
||||
for (const int order : {1, 2, 4}) {
|
||||
for (const int basis : {mfem::BasisType::GaussLobatto, mfem::BasisType::ClosedUniform}) {
|
||||
mfem::H1_FECollection collection(order, 3, basis);
|
||||
const auto &element = *collection.FiniteElementForGeometry(mfem::Geometry::CUBE);
|
||||
const auto rule = CopyRule(mfem::IntRules.Get(mfem::Geometry::CUBE, 2 * order + 3));
|
||||
const TableCache cache;
|
||||
const auto table = cache.GetScalarTable(element, rule);
|
||||
REQUIRE(table->HasTensorEvaluation3D());
|
||||
mfem::DenseMatrix dofs(element.GetDof(), 3), actual;
|
||||
mfem::Vector workspace;
|
||||
for (int c = 0; c < 3; ++c) for (int i = 0; i < element.GetDof(); ++i)
|
||||
dofs(i, c) = std::sin(0.37 * (i + 1) + c) + 0.21 * std::cos(0.13 * i - c);
|
||||
table->EvaluateTensor3D(dofs, actual, workspace);
|
||||
REQUIRE(actual.Height() == rule.GetNPoints());
|
||||
REQUIRE(actual.Width() == 12);
|
||||
for (int q = 0; q < rule.GetNPoints(); ++q) for (int c = 0; c < 3; ++c) {
|
||||
for (int derivative = 0; derivative < 4; ++derivative) {
|
||||
double expected = 0.0;
|
||||
for (int i = 0; i < element.GetDof(); ++i)
|
||||
expected += dofs(i, c) * (derivative == 0 ? table->GetValues()(q, i) :
|
||||
table->GetGradients(q)(i, derivative - 1));
|
||||
CHECK(std::abs(actual(q, 4 * c + derivative) - expected) <
|
||||
2.0e-12 * std::max(1.0, std::abs(expected)));
|
||||
}
|
||||
}
|
||||
// Reused caller scratch must not retain a previous direction.
|
||||
dofs = 0.0;
|
||||
table->EvaluateTensor3D(dofs, actual, workspace);
|
||||
for (int c = 0; c < actual.Width(); ++c) for (int q = 0; q < actual.Height(); ++q)
|
||||
CHECK(actual(q, c) == 0.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Tensor reference evaluation rejects non-Cartesian rules and owns its data",
|
||||
tags::unit &tags::quadrature) {
|
||||
std::shared_ptr<const ScalarTable> retained;
|
||||
mfem::DenseMatrix input, expected;
|
||||
{
|
||||
mfem::H1_FECollection collection(3, 3);
|
||||
const auto &element = *collection.FiniteElementForGeometry(mfem::Geometry::CUBE);
|
||||
auto rule = CopyRule(mfem::IntRules.Get(mfem::Geometry::CUBE, 7));
|
||||
const TableCache cache;
|
||||
retained = cache.GetScalarTable(element, rule);
|
||||
REQUIRE(retained->HasTensorEvaluation3D());
|
||||
input.SetSize(element.GetDof(), 1);
|
||||
input = 1.0;
|
||||
mfem::Vector scratch;
|
||||
retained->EvaluateTensor3D(input, expected, scratch);
|
||||
rule.IntPoint(1).y += 0.001;
|
||||
CHECK_FALSE(cache.GetScalarTable(element, rule)->HasTensorEvaluation3D());
|
||||
const auto &tet = *collection.FiniteElementForGeometry(mfem::Geometry::TETRAHEDRON);
|
||||
CHECK_FALSE(cache.GetScalarTable(tet, mfem::IntRules.Get(mfem::Geometry::TETRAHEDRON, 7))
|
||||
->HasTensorEvaluation3D());
|
||||
}
|
||||
mfem::DenseMatrix actual;
|
||||
mfem::Vector scratch;
|
||||
retained->EvaluateTensor3D(input, actual, scratch);
|
||||
CheckMatrixExactly(actual, expected);
|
||||
}
|
||||
|
||||
TEST_CASE("Tensor Reference Transposes Match Dense Assembly And Adjoint Products", tags::unit &tags::quadrature) {
|
||||
for (int order : {1, 2, 4}) {
|
||||
mfem::H1_FECollection h1(order, 3);
|
||||
const auto &fe = *h1.FiniteElementForGeometry(mfem::Geometry::CUBE);
|
||||
const auto &rule = mfem::IntRules.Get(mfem::Geometry::CUBE, 2*order+3);
|
||||
TableCache cache;
|
||||
auto table = cache.GetScalarTable(fe, rule);
|
||||
REQUIRE(table->HasTensorEvaluation3D());
|
||||
mfem::DenseMatrix dual(rule.GetNPoints(), 3), actual;
|
||||
for (int c = 0; c < 3; ++c) for (int q = 0; q < dual.Height(); ++q)
|
||||
dual(q,c) = std::sin(0.17*(q+1)*(c+1));
|
||||
mfem::Vector workspace, expected(fe.GetDof());
|
||||
for (int repeat = 0; repeat < 2; ++repeat) {
|
||||
if (repeat) dual = 0.0;
|
||||
table->ApplyTransposeTensor3D(dual, actual, workspace);
|
||||
for (int c = 0; c < 3; ++c) {
|
||||
table->GetValues().MultTranspose(dual.GetColumn(c), expected.GetData());
|
||||
for (int i = 0; i < fe.GetDof(); ++i)
|
||||
CHECK(std::abs(actual(i,c)-expected(i)) <= 2.0e-12 * std::max(1.0, expected.Normlinf()));
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int order : {0, 1, 3}) for (int basis : {mfem::BasisType::GaussLegendre, mfem::BasisType::IntegratedGLL}) {
|
||||
CAPTURE(order, basis);
|
||||
mfem::RT_FECollection rt(order, 3, mfem::BasisType::GaussLobatto, basis);
|
||||
const auto &fe = *rt.FiniteElementForGeometry(mfem::Geometry::CUBE);
|
||||
auto rule = CopyRule(mfem::IntRules.Get(mfem::Geometry::CUBE, 2*order+5));
|
||||
TableCache cache;
|
||||
auto table = cache.GetVectorTable(fe, rule);
|
||||
REQUIRE(table->HasTensorEvaluation3D());
|
||||
mfem::Vector dofs(fe.GetDof()), assembled, expected(fe.GetDof()), point(3), workspace;
|
||||
mfem::DenseMatrix values, dual(rule.GetNPoints(), 3);
|
||||
for (int i = 0; i < dofs.Size(); ++i) dofs(i) = std::sin(0.53*(i+1));
|
||||
for (int c = 0; c < 3; ++c) for (int q = 0; q < dual.Height(); ++q) dual(q,c) = std::cos(0.31*(q+1)+c);
|
||||
for (int repeat = 0; repeat < 2; ++repeat) {
|
||||
if (repeat) { dofs = 0.0; dual = 0.0; }
|
||||
table->EvaluateTensor3D(dofs, values, workspace);
|
||||
expected = 0.0;
|
||||
double primalProduct = 0.0;
|
||||
for (int q = 0; q < rule.GetNPoints(); ++q) {
|
||||
table->GetValues(q).MultTranspose(dofs, point);
|
||||
for (int c = 0; c < 3; ++c) {
|
||||
CHECK(std::abs(values(q,c)-point(c)) <= 2.0e-11 * std::max(1.0, point.Normlinf()));
|
||||
primalProduct += values(q,c)*dual(q,c);
|
||||
point(c) = dual(q,c);
|
||||
}
|
||||
table->GetValues(q).AddMult(point, expected);
|
||||
}
|
||||
table->ApplyTransposeTensor3D(dual, assembled, workspace);
|
||||
mfem::Vector difference(assembled); difference -= expected;
|
||||
CHECK(difference.Norml2() <= 2.0e-11 * std::max(1.0, expected.Norml2()));
|
||||
CHECK(std::abs(primalProduct - dofs*assembled) <= 2.0e-11 * std::max(1.0, std::abs(primalProduct)));
|
||||
}
|
||||
rule.IntPoint(rule.GetNPoints()-1).x += 0.017;
|
||||
CHECK_FALSE(cache.GetVectorTable(fe, rule)->HasTensorEvaluation3D());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("RT Tensor Plans Own Their Factors After Cache And FE Destruction", tags::unit &tags::quadrature) {
|
||||
std::shared_ptr<const VectorTable> table;
|
||||
mfem::Vector dofs, expected;
|
||||
mfem::DenseMatrix values, dual;
|
||||
{
|
||||
mfem::RT_FECollection rt(2, 3);
|
||||
TableCache cache;
|
||||
auto rule = CopyRule(mfem::IntRules.Get(mfem::Geometry::CUBE, 9));
|
||||
const auto &fe = *rt.FiniteElementForGeometry(mfem::Geometry::CUBE);
|
||||
table = cache.GetVectorTable(fe, rule);
|
||||
REQUIRE(table->HasTensorEvaluation3D());
|
||||
dofs.SetSize(fe.GetDof()); dofs = 0.3;
|
||||
dual.SetSize(rule.GetNPoints(), 3); dual = 0.17;
|
||||
mfem::Vector workspace;
|
||||
table->ApplyTransposeTensor3D(dual, expected, workspace);
|
||||
}
|
||||
mfem::Vector workspace, actual;
|
||||
table->EvaluateTensor3D(dofs, values, workspace);
|
||||
table->ApplyTransposeTensor3D(dual, actual, workspace);
|
||||
actual -= expected;
|
||||
CHECK(actual.Norml2() == 0.0);
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("L2 Tensor Values And Transposes Match Dense Reference", tags::unit &tags::quadrature) {
|
||||
for (int order : {0, 1, 3}) for (int basis : {mfem::BasisType::GaussLegendre, mfem::BasisType::OpenUniform}) {
|
||||
mfem::L2_FECollection collection(order, 3, basis);
|
||||
const auto &element = *collection.FiniteElementForGeometry(mfem::Geometry::CUBE);
|
||||
const auto &rule = mfem::IntRules.Get(mfem::Geometry::CUBE, 2*order+3);
|
||||
TableCache cache;
|
||||
const auto table = cache.GetScalarTable(element, rule);
|
||||
REQUIRE(table->HasTensorEvaluation3D());
|
||||
mfem::Vector dofs(element.GetDof()), dual(rule.GetNPoints()), values, expected(rule.GetNPoints()),
|
||||
transpose, expectedTranspose(element.GetDof()), workspace;
|
||||
for (int i = 0; i < dofs.Size(); ++i) dofs(i) = std::sin(0.37*(i+1));
|
||||
for (int q = 0; q < dual.Size(); ++q) dual(q) = std::cos(0.19*(q+1));
|
||||
table->EvaluateValuesTensor3D(dofs, values, workspace);
|
||||
table->GetValues().Mult(dofs, expected);
|
||||
mfem::Vector difference(values); difference -= expected;
|
||||
CHECK(difference.Norml2() <= 2.0e-12*std::max(1.0, expected.Norml2()));
|
||||
table->ApplyTransposeTensor3D(dual, transpose, workspace);
|
||||
table->GetValues().MultTranspose(dual, expectedTranspose);
|
||||
difference = transpose; difference -= expectedTranspose;
|
||||
CHECK(difference.Norml2() <= 2.0e-12*std::max(1.0, expectedTranspose.Norml2()));
|
||||
CHECK(std::abs((values*dual) - (dofs*transpose)) <= 2.0e-12*std::max(1.0, std::abs(values*dual)));
|
||||
dofs = 0.0; dual = 0.0;
|
||||
table->EvaluateValuesTensor3D(dofs, values, workspace);
|
||||
table->ApplyTransposeTensor3D(dual, transpose, workspace);
|
||||
CHECK(values.Norml2() == 0.0);
|
||||
CHECK(transpose.Norml2() == 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Geometry Inspector Reuses And Invalidates Fixed Geometry", tags::unit &tags::geometry) {
|
||||
for (const auto ordering : {mfem::Ordering::byNODES, mfem::Ordering::byVDIM}) {
|
||||
auto serial = mfem::Mesh::MakeCartesian3D(2, 1, 1, mfem::Element::HEXAHEDRON);
|
||||
serial.SetCurvature(2);
|
||||
mfem::ParMesh mesh(MPI_COMM_WORLD, serial);
|
||||
mfem::H1_FECollection collection(2, 3);
|
||||
mfem::ParFiniteElementSpace space(&mesh, &collection, 3, ordering);
|
||||
mfem::ParGridFunction field(&space);
|
||||
mfem::VectorFunctionCoefficient displacement(3, [](const mfem::Vector &x, mfem::Vector &u) {
|
||||
u.SetSize(3);
|
||||
u(0) = 0.08*x(0) + 0.02*x(1)*x(2);
|
||||
u(1) = -0.03*x(1) + 0.01*x(0)*x(2);
|
||||
u(2) = 0.04*x(2);
|
||||
});
|
||||
field.ProjectCoefficient(displacement);
|
||||
mfem::Vector state; field.GetTrueDofs(state);
|
||||
mean_field::fem::PreparedGeometryInspector inspector;
|
||||
auto compare = [&] {
|
||||
const double fast = inspector.Inspect(space, state);
|
||||
const double reference = inspector.Inspect(space, state, true);
|
||||
CHECK(std::isfinite(fast));
|
||||
CHECK(std::abs(fast-reference) <= 2.0e-12*std::max(1.0, std::abs(reference)));
|
||||
return fast;
|
||||
};
|
||||
compare();
|
||||
CHECK(inspector.GetTensorElementCount() == mesh.GetNE());
|
||||
const auto builds = inspector.GetGeometryBuildCount();
|
||||
state *= 0.5;
|
||||
const double before = compare();
|
||||
CHECK(inspector.GetGeometryBuildCount() == builds);
|
||||
|
||||
// Same FE/mesh topology, changed curved computational coordinates.
|
||||
mfem::VectorFunctionCoefficient stretch(3, [](const mfem::Vector &x, mfem::Vector &y) {
|
||||
y = x; y(0) = 1.2*x(0); y(1) += 0.04*x(0)*x(2);
|
||||
});
|
||||
mesh.Transform(stretch);
|
||||
const double after = compare();
|
||||
CHECK(std::abs(after-before) > 1.0e-6);
|
||||
CHECK(inspector.GetGeometryBuildCount() == builds + mesh.GetNE());
|
||||
inspector.ClearCache();
|
||||
CHECK(std::abs(compare()-after) < 2.0e-14);
|
||||
CHECK(inspector.GetGeometryBuildCount() == builds + 2*mesh.GetNE());
|
||||
|
||||
mfem::VectorFunctionCoefficient folded(3, [](const mfem::Vector &x, mfem::Vector &u) {
|
||||
u.SetSize(3); u = 0.0; u(0) = -2.0*x(0);
|
||||
});
|
||||
field.ProjectCoefficient(folded); field.GetTrueDofs(state);
|
||||
CHECK(compare() < 0.0);
|
||||
if (state.Size()) state(0) = std::numeric_limits<double>::quiet_NaN();
|
||||
CHECK_FALSE(std::isfinite(inspector.Inspect(space, state)));
|
||||
CHECK_FALSE(std::isfinite(inspector.Inspect(space, state, true)));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Geometry Inspector Preserves Tetrahedron Fallback", tags::unit &tags::geometry) {
|
||||
auto serial = mfem::Mesh::MakeCartesian3D(1, 1, 1, mfem::Element::TETRAHEDRON);
|
||||
mfem::ParMesh mesh(MPI_COMM_WORLD, serial);
|
||||
mfem::H1_FECollection collection(1, 3);
|
||||
mfem::ParFiniteElementSpace space(&mesh, &collection, 3);
|
||||
mfem::Vector state(space.GetTrueVSize()); state = 0.0;
|
||||
mean_field::fem::PreparedGeometryInspector inspector;
|
||||
CHECK(inspector.Inspect(space, state) == 1.0);
|
||||
CHECK(inspector.Inspect(space, state, true) == 1.0);
|
||||
CHECK(inspector.GetTensorElementCount() == 0);
|
||||
CHECK(inspector.GetGeometryBuildCount() == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("Tensor Gradient Transposes Match Dense Assembly And Adjoint Products", tags::unit &tags::quadrature) {
|
||||
const auto check = [](const mfem::FiniteElement &element, const mfem::IntegrationRule &rule) {
|
||||
TableCache cache;
|
||||
const auto table = cache.GetScalarTable(element, rule);
|
||||
REQUIRE(table->HasTensorEvaluation3D());
|
||||
constexpr int components = 2;
|
||||
mfem::DenseMatrix dofs(element.GetDof(), components), dual(rule.GetNPoints(), 3*components),
|
||||
values, actual, expected(element.GetDof(), components);
|
||||
mfem::Vector workspace;
|
||||
for (int c = 0; c < components; ++c) for (int i = 0; i < dofs.Height(); ++i)
|
||||
dofs(i,c) = std::sin(0.37*(i+1)+c);
|
||||
for (int c = 0; c < dual.Width(); ++c) for (int q = 0; q < dual.Height(); ++q)
|
||||
dual(q,c) = std::cos(0.23*(q+1)+0.31*c);
|
||||
|
||||
for (int repeat = 0; repeat < 2; ++repeat) {
|
||||
if (repeat) dual = 0.0;
|
||||
table->EvaluateTensor3D(dofs, values, workspace);
|
||||
table->ApplyGradientTransposeTensor3D(dual, actual, workspace);
|
||||
expected = 0.0;
|
||||
double forwardProduct = 0.0, transposeProduct = 0.0, scale = 1.0;
|
||||
for (int c = 0; c < components; ++c) for (int q = 0; q < rule.GetNPoints(); ++q)
|
||||
for (int a = 0; a < 3; ++a) {
|
||||
forwardProduct += values(q,4*c+a+1)*dual(q,3*c+a);
|
||||
for (int i = 0; i < element.GetDof(); ++i)
|
||||
expected(i,c) += table->GetGradients(q)(i,a)*dual(q,3*c+a);
|
||||
}
|
||||
for (int c = 0; c < components; ++c) for (int i = 0; i < dofs.Height(); ++i)
|
||||
scale = std::max(scale, std::abs(expected(i,c)));
|
||||
for (int c = 0; c < components; ++c) for (int i = 0; i < dofs.Height(); ++i) {
|
||||
CHECK(std::abs(actual(i,c)-expected(i,c)) <= 2.0e-11*scale);
|
||||
transposeProduct += dofs(i,c)*actual(i,c);
|
||||
if (repeat) CHECK(actual(i,c) == 0.0);
|
||||
}
|
||||
CHECK(std::abs(forwardProduct-transposeProduct) <=
|
||||
2.0e-11*std::max(1.0, std::abs(forwardProduct)));
|
||||
}
|
||||
};
|
||||
for (int order : {1, 2, 4}) {
|
||||
CAPTURE(order);
|
||||
mfem::H1_FECollection collection(order, 3);
|
||||
check(*collection.FiniteElementForGeometry(mfem::Geometry::CUBE),
|
||||
mfem::IntRules.Get(mfem::Geometry::CUBE, 2*order+3));
|
||||
}
|
||||
for (int order : {0, 1, 3}) for (int basis : {mfem::BasisType::GaussLegendre, mfem::BasisType::OpenUniform}) {
|
||||
CAPTURE(order, basis);
|
||||
mfem::L2_FECollection collection(order, 3, basis);
|
||||
check(*collection.FiniteElementForGeometry(mfem::Geometry::CUBE),
|
||||
mfem::IntRules.Get(mfem::Geometry::CUBE, 2*order+3));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/matchers/catch_matchers_floating_point.hpp>
|
||||
@@ -102,6 +103,43 @@ namespace {
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("Kelvin Prepared Jacobian Factors Match Full Variation", tags::unit &tags::mapping &tags::kelvin) {
|
||||
mapping::compactification::KelvinCompactification kelvin({.r_star_ref = 1.3, .r_inf_ref = 3.1});
|
||||
const auto position = make_vector(1.1, 0.2, -0.1);
|
||||
const auto coordinateGradient = make_vector(0.2, 0.03, -0.01);
|
||||
auto jacobian = make_identity();
|
||||
jacobian(0, 1) = 0.07;
|
||||
const auto direction = make_vector(-0.02, 0.07, 0.04);
|
||||
auto directionJacobian = make_identity();
|
||||
directionJacobian *= 0.03;
|
||||
directionJacobian(1, 2) = -0.05;
|
||||
for (const double coordinate : {0.0, 0.4, 0.93, 0.999}) {
|
||||
const mapping::compactification::ExteriorMapInput input{
|
||||
position, position, jacobian, coordinate, coordinateGradient};
|
||||
mapping::compactification::ExteriorMapResult base;
|
||||
REQUIRE(kelvin.Evaluate(input, base) == mapping::MappingStatus::valid);
|
||||
mapping::compactification::ExteriorMapVariation variation;
|
||||
REQUIRE(kelvin.EvaluateVariation(input, base, {direction, directionJacobian}, variation) == mapping::MappingStatus::valid);
|
||||
double scale = 0.0;
|
||||
mfem::Vector scaleGradient;
|
||||
REQUIRE(kelvin.EvaluateJacobianVariationFactors(coordinate, coordinateGradient, scale, scaleGradient) == mapping::MappingStatus::valid);
|
||||
for (int row = 0; row < 3; ++row) {
|
||||
for (int column = 0; column < 3; ++column) {
|
||||
const double expected = variation.mapping_jacobian_variation(row, column);
|
||||
CHECK_THAT(scale * directionJacobian(row, column) + direction(row) * scaleGradient(column),
|
||||
WithinAbs(expected, 1.0e-12 * std::max(1.0, std::abs(expected))));
|
||||
}
|
||||
}
|
||||
}
|
||||
double scale = 0.0;
|
||||
mfem::Vector gradient;
|
||||
CHECK(kelvin.EvaluateJacobianVariationFactors(1.0, coordinateGradient, scale, gradient) == mapping::MappingStatus::at_compactified_infinity);
|
||||
CHECK(kelvin.EvaluateJacobianVariationFactors(-0.1, coordinateGradient, scale, gradient) == mapping::MappingStatus::outside_reference_domain);
|
||||
auto invalid = coordinateGradient;
|
||||
invalid(1) = std::numeric_limits<double>::quiet_NaN();
|
||||
CHECK(kelvin.EvaluateJacobianVariationFactors(0.4, invalid, scale, gradient) == mapping::MappingStatus::non_finite_input);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Kelvin Compactification Validates Its Configuration",
|
||||
tags::unit &tags::mapping &tags::kelvin
|
||||
@@ -719,4 +757,4 @@ TEST_CASE(
|
||||
|
||||
check_vector(first_a.physical_position, second_a.physical_position, tolerance);
|
||||
check_matrix(first_a.mapping_jacobian, second_a.mapping_jacobian, tolerance);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2870,3 +2870,80 @@ TEST_CASE(
|
||||
CHECK_THROWS_AS(evaluator.Refresh(), std::invalid_argument);
|
||||
CHECK_THROWS_AS(evaluator.EvaluatePoint(*transformation, integration_point, context), std::invalid_argument);
|
||||
}
|
||||
|
||||
TEST_CASE("Fixed Mesh Geometry Cache Reuses Factors And Invalidates On Mesh Updates", "[mapping][geometry-cache]") {
|
||||
SingleElementFixture fixture;
|
||||
mapping::DomainMapper mapper({.dimension = dimension, .vacuum_element_attribute = 3}, make_kelvin_compactification());
|
||||
mapping::DomainMapper::Workspace workspace(dimension);
|
||||
const auto &rule = mfem::IntRules.Get(mfem::Geometry::CUBE, 5);
|
||||
const auto &point = rule.IntPoint(0);
|
||||
auto zero = fixture.MakeZeroElementDofs();
|
||||
mapping::ElementDisplacementData displacement(fixture.GetElement(), zero);
|
||||
ElementMappingDataOwner data(displacement);
|
||||
auto compare = [&](const mfem::IntegrationPoint &ip) {
|
||||
auto &transformation = *fixture.mesh.GetElementTransformation(0);
|
||||
mapping::VolumeMappingContext fast, reference;
|
||||
mapper.SetGeometryCacheEnabled(true);
|
||||
REQUIRE(mapper.EvaluateVolume(data.Get(), transformation, ip, workspace, fast) == mapping::MappingStatus::valid);
|
||||
mapper.SetGeometryCacheEnabled(false);
|
||||
REQUIRE(mapper.EvaluateVolume(data.Get(), transformation, ip, workspace, reference) == mapping::MappingStatus::valid);
|
||||
check_vector(fast.mapping.physical_position, reference.mapping.physical_position);
|
||||
check_matrix(fast.mapping.mapping_jacobian, reference.mapping.mapping_jacobian);
|
||||
check_matrix(fast.quadrature.J_inv, reference.quadrature.J_inv);
|
||||
CHECK_THAT(fast.quadrature.weight, WithinAbs(reference.quadrature.weight, 1.0e-12));
|
||||
mapper.SetGeometryCacheEnabled(true);
|
||||
return fast.quadrature.weight;
|
||||
};
|
||||
const double originalWeight = compare(point);
|
||||
CHECK(mapper.GetGeometryCacheStatistics().misses == 1);
|
||||
compare(point);
|
||||
CHECK(mapper.GetGeometryCacheStatistics().hits == 1);
|
||||
auto changedPoint = point;
|
||||
changedPoint.x += 0.013;
|
||||
compare(changedPoint);
|
||||
CHECK(mapper.GetGeometryCacheStatistics().points == 2);
|
||||
changedPoint.weight *= 0.5;
|
||||
compare(changedPoint);
|
||||
CHECK(mapper.GetGeometryCacheStatistics().points == 2); // weights are supplied by the caller
|
||||
fixture.mesh.Transform([](const mfem::Vector &x, mfem::Vector &y) {
|
||||
y = x; y(0) *= 1.2; y(1) += 0.17;
|
||||
});
|
||||
const double newWeight = compare(point);
|
||||
CHECK(newWeight > originalWeight);
|
||||
CHECK(mapper.GetGeometryCacheStatistics().points == 1);
|
||||
CHECK(mapper.GetGeometryCacheStatistics().misses == 3);
|
||||
mapper.ClearGeometryCache();
|
||||
CHECK(mapper.GetGeometryCacheStatistics().points == 0);
|
||||
compare(point);
|
||||
CHECK(mapper.GetGeometryCacheStatistics().misses == 1);
|
||||
}
|
||||
|
||||
TEST_CASE("Geometry Cache Does Not Freeze Displacement Or Compactification State", "[mapping][geometry-cache]") {
|
||||
SingleElementFixture fixture;
|
||||
mapping::DomainMapper mapper({.dimension = dimension, .vacuum_element_attribute = 3}, make_kelvin_compactification());
|
||||
mapping::DomainMapper::Workspace workspace(dimension);
|
||||
const auto &point = mfem::IntRules.Get(mfem::Geometry::CUBE, 3).IntPoint(0);
|
||||
for (double scale : {0.0, 0.02, -0.01}) {
|
||||
auto &transformation = *fixture.mesh.GetElementTransformation(0);
|
||||
auto dofs = fixture.MakeZeroElementDofs(); dofs = scale;
|
||||
mapping::ElementDisplacementData displacement(fixture.GetElement(), dofs);
|
||||
auto compactificationDofs = make_constant_compactification_dofs(fixture.GetElement(), 0.1 + scale);
|
||||
mapping::ElementCompactificationData compactification(fixture.GetElement(), compactificationDofs);
|
||||
mapping::ElementMappingData data{displacement, compactification};
|
||||
for (int attribute : {1, 3}) {
|
||||
transformation.Attribute = attribute;
|
||||
mapping::VolumeMappingContext fast, reference;
|
||||
mapper.SetGeometryCacheEnabled(true);
|
||||
const auto fastStatus = mapper.EvaluateVolume(data, transformation, point, workspace, fast);
|
||||
mapper.SetGeometryCacheEnabled(false);
|
||||
const auto referenceStatus = mapper.EvaluateVolume(data, transformation, point, workspace, reference);
|
||||
REQUIRE(fastStatus == referenceStatus);
|
||||
if (fastStatus == mapping::MappingStatus::valid) {
|
||||
check_vector(fast.mapping.physical_position, reference.mapping.physical_position);
|
||||
check_matrix(fast.mapping.mapping_jacobian, reference.mapping.mapping_jacobian);
|
||||
check_matrix(fast.quadrature.J_inv, reference.quadrature.J_inv);
|
||||
}
|
||||
}
|
||||
}
|
||||
CHECK(mapper.GetGeometryCacheStatistics().points == 1);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <catch2/catch_approx.hpp>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <mfem.hpp>
|
||||
#include <mpi.h>
|
||||
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
@@ -997,3 +998,83 @@ TEST_CASE(
|
||||
CHECK(physicalInverse.IsCurrent());
|
||||
CHECK(scaledInverse.IsCurrent());
|
||||
}
|
||||
|
||||
TEST_CASE("Direct Reference Mass Diagonals Match Full True DOF Assembly", "[normalization][diagonal][assembly]") {
|
||||
using namespace mean_field;
|
||||
auto args = test_utils::setup_args();
|
||||
auto f = fem::setup_fem(args.mesh_file, args, 0);
|
||||
REQUIRE(f.okay());
|
||||
auto compare = [](const mfem::Vector &a, const mfem::Vector &b) {
|
||||
REQUIRE(a.Size() == b.Size());
|
||||
mfem::Vector difference(a); difference -= b;
|
||||
CHECK(difference.Norml2() <= 2.0e-11 * std::max(1.0, b.Norml2()));
|
||||
};
|
||||
compare(normalization::AssembleReferenceHDivMassDiagonal(*f.gravityFluxFes),
|
||||
normalization::AssembleReferenceHDivMassDiagonal(*f.gravityFluxFes, true));
|
||||
for (auto *space : {f.gravityPotentialFes.get(), f.densityFes.get(), f.enthalpyFes.get()}) {
|
||||
compare(normalization::AssembleReferenceScalarMassDiagonal(*space),
|
||||
normalization::AssembleReferenceScalarMassDiagonal(*space, nullptr, true));
|
||||
mfem::Array<int> marker(f.mesh->attributes.Max());
|
||||
marker = 0; marker[f.mesh->GetAttribute(0)-1] = 1;
|
||||
compare(normalization::AssembleReferenceScalarMassDiagonal(*space, &marker),
|
||||
normalization::AssembleReferenceScalarMassDiagonal(*space, &marker, true));
|
||||
}
|
||||
auto surfaceMap = field::make_stellar_surface_scalar_dof_map(*f.surfaceDeformationFes);
|
||||
// The stellar surface is a tagged internal interface, which an exterior-only
|
||||
// boundary face restriction would omit even though volume diagonals pass.
|
||||
using Schema = utils::domain::CoreEnvelopeVacuumDomainSchema;
|
||||
constexpr int surfaceAttribute = Schema::boundary_attribute<utils::domain::StellarSurface>();
|
||||
int localFaceCounts[2] = {0, 0}, globalFaceCounts[2] = {0, 0};
|
||||
for (int b = 0; b < f.mesh->GetNBE(); ++b) {
|
||||
if (f.mesh->GetBdrAttribute(b) != surfaceAttribute) continue;
|
||||
++localFaceCounts[0];
|
||||
const int face = f.mesh->GetBdrElementFaceIndex(b);
|
||||
if (f.mesh->GetFaceInformation(face).IsOfFaceType(mfem::FaceType::Interior))
|
||||
++localFaceCounts[1];
|
||||
}
|
||||
REQUIRE(MPI_Allreduce(localFaceCounts, globalFaceCounts, 2, MPI_INT, MPI_SUM,
|
||||
f.surfaceDeformationFes->GetComm()) == MPI_SUCCESS);
|
||||
REQUIRE(globalFaceCounts[0] > 0);
|
||||
REQUIRE(globalFaceCounts[1] == globalFaceCounts[0]);
|
||||
|
||||
const auto surface = normalization::AssembleReferenceSurfaceMassDiagonal(f, surfaceMap);
|
||||
const auto referenceSurface = normalization::AssembleReferenceSurfaceMassDiagonal(f, surfaceMap, true);
|
||||
REQUIRE(surface.Size() == referenceSurface.Size());
|
||||
for (int i = 0; i < surface.Size(); ++i) {
|
||||
CAPTURE(i, surface(i), referenceSurface(i));
|
||||
CHECK(std::isfinite(surface(i)));
|
||||
CHECK(surface(i) > 0.0);
|
||||
CHECK(std::isfinite(referenceSurface(i)));
|
||||
CHECK(referenceSurface(i) > 0.0);
|
||||
}
|
||||
mfem::Vector difference(surface);
|
||||
difference -= referenceSurface;
|
||||
const double errorNorm = difference.Norml2(), referenceNorm = referenceSurface.Norml2();
|
||||
const double localSquares[2] = {errorNorm * errorNorm, referenceNorm * referenceNorm};
|
||||
double globalSquares[2] = {0.0, 0.0};
|
||||
REQUIRE(MPI_Allreduce(localSquares, globalSquares, 2, MPI_DOUBLE, MPI_SUM,
|
||||
f.surfaceDeformationFes->GetComm()) == MPI_SUCCESS);
|
||||
REQUIRE(globalSquares[1] > 0.0);
|
||||
CHECK(std::sqrt(globalSquares[0] / globalSquares[1]) <= 2.0e-11);
|
||||
}
|
||||
|
||||
TEST_CASE("Nonconforming Reference Diagonals Retain Exact Assembled Cross Terms", "[normalization][diagonal][nonconforming]") {
|
||||
using namespace mean_field;
|
||||
auto serial = mfem::Mesh::MakeCartesian3D(2, 1, 1, mfem::Element::HEXAHEDRON);
|
||||
serial.EnsureNCMesh();
|
||||
mfem::Array<int> refine(1); refine[0] = 0;
|
||||
serial.GeneralRefinement(refine);
|
||||
REQUIRE(serial.Nonconforming());
|
||||
mfem::ParMesh mesh(MPI_COMM_WORLD, serial);
|
||||
mfem::H1_FECollection h1(2, 3);
|
||||
mfem::RT_FECollection rt(1, 3);
|
||||
mfem::ParFiniteElementSpace scalar(&mesh, &h1), flux(&mesh, &rt);
|
||||
auto actual = normalization::AssembleReferenceScalarMassDiagonal(scalar);
|
||||
auto expected = normalization::AssembleReferenceScalarMassDiagonal(scalar, nullptr, true);
|
||||
actual -= expected;
|
||||
CHECK(actual.Norml2() == 0.0);
|
||||
actual = normalization::AssembleReferenceHDivMassDiagonal(flux);
|
||||
expected = normalization::AssembleReferenceHDivMassDiagonal(flux, true);
|
||||
actual -= expected;
|
||||
CHECK(actual.Norml2() == 0.0);
|
||||
}
|
||||
|
||||
@@ -601,6 +601,15 @@ TEST_CASE(
|
||||
CHECK(preparedOperator.IsPrepared());
|
||||
CHECK(preparedOperator.GetResidualPreparationCount() == 2);
|
||||
CHECK(preparedOperator.GetResidualApplicationCount() == 1);
|
||||
mfem::Vector updatedResidual, referenceResidual;
|
||||
preparedOperator.BuildResidual(updatedResidual);
|
||||
CHECK(preparedOperator.PrepareReference().DidAnyWork());
|
||||
preparedOperator.BuildResidual(referenceResidual);
|
||||
CHECK(gravity_displacement_force_test_utils::relative_difference(
|
||||
updatedResidual, referenceResidual, f.mesh->GetComm()) < 2.0e-12);
|
||||
CHECK_FALSE(preparedOperator.PrepareReference().DidAnyWork());
|
||||
CHECK(preparedOperator.Prepare().DidAnyWork());
|
||||
CHECK_FALSE(preparedOperator.Prepare().DidAnyWork());
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
@@ -665,6 +674,19 @@ TEST_CASE(
|
||||
densityDirectionReduced, displacementDirectionReduced, gravityGradientDirectionReduced, completeAction
|
||||
);
|
||||
|
||||
mfem::Vector retainedAction;
|
||||
preparedOperator.ApplyCompleteJacobianActionReference(
|
||||
densityDirectionReduced, displacementDirectionReduced, gravityGradientDirectionReduced, retainedAction);
|
||||
CHECK(preparedOperator.GetFastElementCount() > 0);
|
||||
CHECK(preparedOperator.GetTensorElementCount() == preparedOperator.GetFastElementCount());
|
||||
CHECK(gravity_displacement_force_test_utils::relative_difference(
|
||||
completeAction, retainedAction, f.mesh->GetComm()) < 2.0e-12);
|
||||
|
||||
preparedOperator.ApplyCompleteJacobianActionCachedReference(
|
||||
densityDirectionReduced, displacementDirectionReduced, gravityGradientDirectionReduced, retainedAction);
|
||||
CHECK(gravity_displacement_force_test_utils::relative_difference(
|
||||
completeAction, retainedAction, f.mesh->GetComm()) < 2.0e-12);
|
||||
|
||||
mfem::Vector summedColumns(densityAction);
|
||||
summedColumns += gravityAction;
|
||||
summedColumns += displacementAction;
|
||||
|
||||
@@ -312,6 +312,10 @@ TEST_CASE(
|
||||
operation.ApplyCompleteJacobianAction(
|
||||
reducedDensityDirection, reducedDisplacementDirection, angularVelocityDirection, completeAction
|
||||
);
|
||||
mfem::Vector preparedReference;
|
||||
operation.ApplyCompleteJacobianActionReference(
|
||||
reducedDensityDirection, reducedDisplacementDirection, angularVelocityDirection, preparedReference);
|
||||
CHECK(angular_momentum_test_utils::relativeError(completeAction(0), preparedReference(0)) < 2.0e-11);
|
||||
CHECK(
|
||||
angular_momentum_test_utils::relativeError(
|
||||
completeAction(0), densityAction(0) + geometryAction(0) + angularVelocityAction(0)
|
||||
@@ -409,10 +413,26 @@ TEST_CASE(
|
||||
operation.Prepare(0.52, dependencies);
|
||||
const auto preparationCount = operation.GetPreparationCount();
|
||||
const double moment = operation.GetMomentOfInertia();
|
||||
CHECK(operation.GetJacobianRowPreparationCount() == 0);
|
||||
const auto rhoDirection = gravityContext.GetDensityMap().gather(
|
||||
angular_momentum_test_utils::projectDensityDirection(finiteElements, 0.67));
|
||||
const auto uDirection = gravityContext.GetDisplacementMap().gather(
|
||||
angular_momentum_test_utils::projectDisplacementDirection(finiteElements, -0.53));
|
||||
auto checkRows = [&](std::uint64_t expectedBuilds) {
|
||||
REQUIRE(operation.PrepareJacobianRows());
|
||||
CHECK(operation.GetJacobianRowPreparationCount() == expectedBuilds);
|
||||
mfem::Vector actual, reference;
|
||||
operation.ApplyCompleteJacobianAction(rhoDirection, uDirection, -0.19, actual);
|
||||
operation.ApplyCompleteJacobianActionReference(rhoDirection, uDirection, -0.19, reference);
|
||||
CHECK(angular_momentum_test_utils::relativeError(actual(0), reference(0)) < 2.0e-11);
|
||||
CHECK(operation.GetJacobianRowPreparationCount() == expectedBuilds);
|
||||
};
|
||||
checkRows(1);
|
||||
|
||||
const auto repeated = operation.Prepare(0.52, dependencies);
|
||||
CHECK_FALSE(repeated.DidAnyWork());
|
||||
CHECK(operation.GetPreparationCount() == preparationCount);
|
||||
checkRows(1);
|
||||
|
||||
++gravityPotentialRevision;
|
||||
angular_momentum_test_utils::prepareGravityContext(
|
||||
@@ -420,6 +440,7 @@ TEST_CASE(
|
||||
);
|
||||
const auto unrelatedPotential = operation.Prepare(0.52, dependencies);
|
||||
CHECK_FALSE(unrelatedPotential.DidAnyWork());
|
||||
checkRows(1);
|
||||
|
||||
const double residualBeforeRotation = angular_momentum_test_utils::residual(operation);
|
||||
++dependencies.rotation.revision;
|
||||
@@ -429,6 +450,7 @@ TEST_CASE(
|
||||
CHECK_FALSE(rotationOnly.refreshedDensity);
|
||||
CHECK_FALSE(rotationOnly.refreshedGeometry);
|
||||
CHECK(operation.GetMomentOfInertia() == moment);
|
||||
checkRows(1);
|
||||
CHECK(
|
||||
angular_momentum_test_utils::residual(operation) - residualBeforeRotation ==
|
||||
Catch::Approx((0.81 - 0.52) * moment).epsilon(3.0e-15)
|
||||
@@ -443,6 +465,8 @@ TEST_CASE(
|
||||
CHECK(densityOnly.refreshedDensity);
|
||||
CHECK_FALSE(densityOnly.refreshedGeometry);
|
||||
CHECK_FALSE(densityOnly.updatedAngularVelocity);
|
||||
CHECK(operation.GetJacobianRowPreparationCount() == 1);
|
||||
checkRows(2);
|
||||
|
||||
displacement = angular_momentum_test_utils::projectDisplacementDirection(finiteElements, 0.87);
|
||||
++dependencies.displacement.revision;
|
||||
@@ -453,6 +477,8 @@ TEST_CASE(
|
||||
CHECK(geometryOnly.refreshedGeometry);
|
||||
CHECK_FALSE(geometryOnly.refreshedDensity);
|
||||
CHECK_FALSE(geometryOnly.updatedAngularVelocity);
|
||||
CHECK(operation.GetJacobianRowPreparationCount() == 2);
|
||||
checkRows(3);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
@@ -488,6 +514,8 @@ TEST_CASE(
|
||||
const auto initial = operation.TryPrepare(angularVelocity, dependencies);
|
||||
REQUIRE(initial.has_value());
|
||||
const std::uint64_t initialPreparationCount = operation.GetPreparationCount();
|
||||
REQUIRE(operation.PrepareJacobianRows());
|
||||
CHECK(operation.GetJacobianRowPreparationCount() == 1);
|
||||
|
||||
const std::uint64_t successfulPreparations = initialPreparationCount;
|
||||
|
||||
@@ -513,6 +541,8 @@ TEST_CASE(
|
||||
const auto recovered = operation.TryPrepare(angularVelocity, dependencies);
|
||||
REQUIRE(recovered.has_value());
|
||||
CHECK(operation.IsPrepared());
|
||||
REQUIRE(operation.PrepareJacobianRows());
|
||||
CHECK(operation.GetJacobianRowPreparationCount() == 2);
|
||||
|
||||
++dependencies.rotation.revision;
|
||||
const auto nonFiniteAngularVelocity = operation.TryPrepare(std::numeric_limits<double>::quiet_NaN(), dependencies);
|
||||
@@ -527,4 +557,6 @@ TEST_CASE(
|
||||
++dependencies.rotation.revision;
|
||||
REQUIRE(operation.TryPrepare(angularVelocity, dependencies).has_value());
|
||||
CHECK(operation.IsPrepared());
|
||||
REQUIRE(operation.PrepareJacobianRows());
|
||||
CHECK(operation.GetJacobianRowPreparationCount() == 3);
|
||||
}
|
||||
|
||||
@@ -526,6 +526,18 @@ namespace prepared_barotropic_closure_test_utils {
|
||||
|
||||
const MPI_Comm communicator = f.mesh->GetComm();
|
||||
|
||||
mfem::Vector retained, enthalpyOnly, displacementOnly;
|
||||
preparedOperator.MultReference(densityVariation, enthalpyVariation, displacementVariation, retained);
|
||||
preparedOperator.ApplyEnthalpyJacobianAction(enthalpyVariation, enthalpyOnly);
|
||||
preparedOperator.ApplyDisplacementJacobianAction(displacementVariation, displacementOnly);
|
||||
CHECK(relative_error(preparedCompleteAction, retained, communicator) < 2.0e-12);
|
||||
CHECK(relative_error(enthalpyOnly, referenceEnthalpyAction, communicator) < 2.0e-12);
|
||||
CHECK(relative_error(displacementOnly, referenceDisplacementAction, communicator) < 2.0e-12);
|
||||
preparedOperator.ApplyEnthalpyJacobianAction(zeroEnthalpy, enthalpyOnly);
|
||||
preparedOperator.ApplyDisplacementJacobianAction(zeroDisplacement, displacementOnly);
|
||||
CHECK(enthalpyOnly.Norml2() == 0.0);
|
||||
CHECK(displacementOnly.Norml2() == 0.0);
|
||||
|
||||
CHECK(relative_error(preparedResidual, referenceResidual, communicator) < 2.0e-12);
|
||||
CHECK(relative_error(preparedDensityAction, referenceDensityAction, communicator) < 2.0e-12);
|
||||
CHECK(relative_error(preparedEnthalpyAction, referenceEnthalpyAction, communicator) < 2.0e-12);
|
||||
@@ -831,6 +843,16 @@ namespace prepared_barotropic_closure_test_utils {
|
||||
mfem::Vector preparedAction;
|
||||
preparedOperator.BuildResidual(preparedResidual);
|
||||
preparedOperator.Mult(densityVariation, enthalpyVariation, displacementVariation, preparedAction);
|
||||
CHECK(preparedOperator.GetTensorElementCount() > 0);
|
||||
mfem::Vector cached, tensorColumn, cachedColumn;
|
||||
preparedOperator.MultCachedReference(densityVariation, enthalpyVariation, displacementVariation, cached);
|
||||
CHECK(relative_error(preparedAction, cached, communicator) < 2.0e-11);
|
||||
preparedOperator.ApplyEnthalpyJacobianAction(enthalpyVariation, tensorColumn);
|
||||
preparedOperator.ApplyEnthalpyJacobianAction(enthalpyVariation, cachedColumn, false);
|
||||
CHECK(relative_error(tensorColumn, cachedColumn, communicator) < 2.0e-11);
|
||||
preparedOperator.ApplyDisplacementJacobianAction(displacementVariation, tensorColumn);
|
||||
preparedOperator.ApplyDisplacementJacobianAction(displacementVariation, cachedColumn, false);
|
||||
CHECK(relative_error(tensorColumn, cachedColumn, communicator) < 2.0e-11);
|
||||
|
||||
mfem::Vector fullReferenceResidual;
|
||||
mean_field::operators::kernels::apply_barotropic_closure(
|
||||
|
||||
@@ -69,6 +69,7 @@ TEST_CASE(
|
||||
REQUIRE(f.okay());
|
||||
|
||||
operators::PreparedMappedGravitySourceOperator operation(f, *f.domainMapperStateless);
|
||||
operators::PreparedMappedGravitySourceOperator oldWeights(f, *f.domainMapperStateless);
|
||||
const mfem::Vector densityTrue = prepared_test::make_deterministic_vector(f.densityFes->GetTrueVSize(), 0.41);
|
||||
const mfem::Vector density = operation.GetDensityMap().gather(densityTrue);
|
||||
const mfem::Vector displacementTrue = prepared_test::make_displacement(f, 0.4);
|
||||
@@ -81,6 +82,21 @@ TEST_CASE(
|
||||
mfem::Vector baselineVariation;
|
||||
operation.Mult(density, baselineAction);
|
||||
operation.MultDisplacementVariationTrue(densityTrue, directionTrue, baselineVariation);
|
||||
REQUIRE(operation.GetTensorVariationElementCount() > 0);
|
||||
mfem::Vector pointwiseVariation, zeroDirection(directionTrue), zeroVariation;
|
||||
operation.MultDisplacementVariationTrueReference(densityTrue, directionTrue, pointwiseVariation);
|
||||
CHECK(prepared_test::relative_error(baselineVariation, pointwiseVariation, communicator) < 2.0e-12);
|
||||
zeroDirection = 0.0;
|
||||
operation.MultDisplacementVariationTrue(densityTrue, zeroDirection, zeroVariation);
|
||||
CHECK(zeroVariation.Norml2() == 0.0);
|
||||
operation.MultDisplacementVariationTrue(densityTrue, directionTrue, pointwiseVariation);
|
||||
CHECK(prepared_test::relative_error(baselineVariation, pointwiseVariation, communicator) < 2.0e-14);
|
||||
oldWeights.PrepareReference(displacement);
|
||||
mfem::Vector oldAction, oldVariation;
|
||||
oldWeights.Mult(density, oldAction);
|
||||
oldWeights.MultDisplacementVariationTrue(densityTrue, directionTrue, oldVariation);
|
||||
CHECK(prepared_test::relative_error(baselineAction, oldAction, communicator) < 2.0e-14);
|
||||
CHECK(prepared_test::relative_error(baselineVariation, oldVariation, communicator) < 2.0e-14);
|
||||
|
||||
const mfem::Vector primalDisplacementTrue = prepared_test::make_displacement(f, 1.0);
|
||||
operation.PreparePrimal(operation.GetDisplacementMap().gather(primalDisplacementTrue));
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/matchers/catch_matchers_floating_point.hpp>
|
||||
#include <cmath>
|
||||
#include <memory>
|
||||
#include <mfem.hpp>
|
||||
#include <stdexcept>
|
||||
|
||||
@@ -12,6 +13,27 @@ using Catch::Matchers::WithinAbs;
|
||||
namespace prepared_test = gravity_prepared_test_utils;
|
||||
|
||||
namespace {
|
||||
// Deliberately not a KelvinCompactification dynamic type: the prepared
|
||||
// operator must keep arbitrary exterior maps on the general derivative.
|
||||
class DelegatingExteriorMap final : public mapping::compactification::ExteriorDomainMap {
|
||||
public:
|
||||
explicit DelegatingExteriorMap(mapping::compactification::options::KelvinCompactificationOptions options)
|
||||
: kelvin(options) {}
|
||||
mapping::MappingStatus Evaluate(const mapping::compactification::ExteriorMapInput &input,
|
||||
mapping::compactification::ExteriorMapResult &result) const override {
|
||||
return kelvin.Evaluate(input, result);
|
||||
}
|
||||
mapping::MappingStatus EvaluateVariation(const mapping::compactification::ExteriorMapInput &input,
|
||||
const mapping::compactification::ExteriorMapResult &result,
|
||||
const mapping::compactification::ExteriorMapDirection &direction,
|
||||
mapping::compactification::ExteriorMapVariation &variation) const override {
|
||||
return kelvin.EvaluateVariation(input, result, direction, variation);
|
||||
}
|
||||
std::string_view GetName() const noexcept override { return "DelegatingExteriorMap"; }
|
||||
private:
|
||||
mapping::compactification::KelvinCompactification kelvin;
|
||||
};
|
||||
|
||||
[[nodiscard]] mfem::Vector make_folding_displacement(const mean_field::fem::FEM &f) {
|
||||
mfem::ParGridFunction field(f.displacementFes.get());
|
||||
mfem::VectorFunctionCoefficient coefficient(
|
||||
@@ -65,9 +87,15 @@ TEST_CASE(
|
||||
const mfem::Vector displacementTrue = prepared_test::make_displacement(f, scale);
|
||||
const mfem::Vector displacement = preparedOperator.GetDisplacementMap().gather(displacementTrue);
|
||||
preparedOperator.Prepare(displacement);
|
||||
CHECK(preparedOperator.GetFastVariationElementCount() == f.mesh->GetNE());
|
||||
CHECK(preparedOperator.GetTensorVariationElementCount() == f.mesh->GetNE());
|
||||
preparedOperator.MultDisplacementVariationTrue(first, direction, firstAction);
|
||||
|
||||
mfem::Vector referenceAction;
|
||||
preparedOperator.MultDisplacementVariationTrueCachedReference(first, direction, referenceAction);
|
||||
CHECK(prepared_test::relative_error(firstAction, referenceAction, communicator) < 2.0e-11);
|
||||
preparedOperator.MultDisplacementVariationTrueReference(first, direction, referenceAction);
|
||||
CHECK(prepared_test::relative_error(firstAction, referenceAction, communicator) < 2.0e-11);
|
||||
operators::kernels::apply_mapped_hdiv_mass_variation(
|
||||
f, *f.domainMapperStateless, first, displacementTrue, direction, referenceAction
|
||||
);
|
||||
@@ -85,6 +113,39 @@ TEST_CASE(
|
||||
CHECK(preparedOperator.GetPreparationCount() == 2);
|
||||
}
|
||||
|
||||
TEST_CASE("Prepared Hdiv Tangent Retains General Exterior Map Fallback",
|
||||
tags::gravity_prepared_jacobian_accuracy) {
|
||||
auto args = test_utils::setup_args();
|
||||
auto f = fem::setup_fem(args.mesh_file, args, 0);
|
||||
REQUIRE(f.okay());
|
||||
mapping::DomainMapper mapper(args.domain_mapper_options, std::make_unique<DelegatingExteriorMap>(args.kelvin_options));
|
||||
operators::PreparedMappedHDivMassOperator prepared(f, mapper);
|
||||
const auto base = prepared_test::make_displacement(f, 0.7);
|
||||
const auto direction = prepared_test::make_displacement(f, 0.43);
|
||||
const auto flux = prepared_test::make_deterministic_vector(f.gravityFluxFes->GetTrueVSize(), 0.31);
|
||||
prepared.Prepare(prepared.GetDisplacementMap().gather(base));
|
||||
int stellarElements = 0;
|
||||
for (int element = 0; element < f.mesh->GetNE(); ++element)
|
||||
stellarElements += !mapper.IsCompactifiedElement(*f.mesh->GetElementTransformation(element));
|
||||
CHECK(prepared.GetFastVariationElementCount() == stellarElements);
|
||||
mfem::Vector actual, expected;
|
||||
prepared.MultDisplacementVariationTrue(flux, direction, actual);
|
||||
operators::kernels::apply_mapped_hdiv_mass_variation(f, mapper, flux, base, direction, expected);
|
||||
CHECK(prepared_test::relative_error(actual, expected, f.mesh->GetComm()) < 2.0e-11);
|
||||
prepared.PreparePrimal(prepared.GetDisplacementMap().gather(base));
|
||||
CHECK_FALSE(prepared.HasVariationData());
|
||||
CHECK(prepared.GetFastVariationElementCount() == 0);
|
||||
CHECK(prepared.GetFastMassElementCount() == f.mesh->GetNE());
|
||||
mfem::Vector primal, primalReference;
|
||||
const auto reducedFlux = prepared.GetFluxMap().gather(flux);
|
||||
prepared.Mult(reducedFlux, primal);
|
||||
prepared.MultReference(reducedFlux, primalReference);
|
||||
CHECK(prepared_test::relative_error(primal, primalReference, f.mesh->GetComm()) < 2.0e-11);
|
||||
prepared.Prepare(prepared.GetDisplacementMap().gather(base));
|
||||
prepared.MultDisplacementVariationTrue(flux, direction, actual);
|
||||
CHECK(prepared_test::relative_error(actual, expected, f.mesh->GetComm()) < 2.0e-11);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Prepared Mapped Hdiv Mass Reports Invalid Candidate Geometry Without Unwinding",
|
||||
tags::gravity_prepared_unit &tags::geometry
|
||||
@@ -144,6 +205,13 @@ TEST_CASE(
|
||||
mfem::Vector prepared_action;
|
||||
|
||||
prepared_operator.Mult(gravity_gradient, prepared_action);
|
||||
CHECK(prepared_operator.GetFastMassElementCount() == f.mesh->GetNE());
|
||||
mfem::Vector mfemAction;
|
||||
prepared_operator.MultReference(gravity_gradient, mfemAction);
|
||||
CHECK(prepared_test::relative_error(prepared_action, mfemAction, communicator) < 2.0e-11);
|
||||
// Reference calls must restore the optimized mode and leave PA data intact.
|
||||
prepared_operator.Mult(gravity_gradient, mfemAction);
|
||||
CHECK(prepared_test::relative_error(prepared_action, mfemAction, communicator) < 2.0e-14);
|
||||
mfem::Vector reference_action_true;
|
||||
operators::kernels::apply_mapped_hdiv_mass(
|
||||
f, *f.domainMapperStateless, gravity_gradient_true, displacement_true, reference_action_true
|
||||
|
||||
@@ -361,3 +361,80 @@ TEST_CASE(
|
||||
CHECK(recovered->preparedResidual);
|
||||
CHECK(preparedOperator.IsPrepared());
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Hydrostatic Tensor Preparation Matches Pointwise Preparation And Rebuilds On Mode Changes",
|
||||
tags::barotrope_hydrostatic_prepared_residual &tags::unit
|
||||
) {
|
||||
const auto args = test_utils::setup_args();
|
||||
auto f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
REQUIRE(f.okay());
|
||||
using namespace prepared_hydrostatic_test_utils;
|
||||
mean_field::operators::PreparedHydrostaticEquilibriumOperator fast(f, *f.domainMapperStateless);
|
||||
mean_field::operators::PreparedHydrostaticEquilibriumOperator reference(f, *f.domainMapperStateless);
|
||||
auto h = make_enthalpy(f), phi = make_gravity_potential(f);
|
||||
const auto dh = make_enthalpy(f, 0.53), dp = make_gravity_potential(f, 0.71);
|
||||
const auto du = field_dof_test_utils::make_supported_displacement(f, 0.31);
|
||||
const auto rotation = make_rotation();
|
||||
auto dependencies = make_dependencies();
|
||||
|
||||
for (double scale : {0.0, 0.42, 0.73}) {
|
||||
CAPTURE(scale);
|
||||
const auto displacement = field_dof_test_utils::make_supported_displacement(f, scale);
|
||||
h *= 1.01; phi *= 0.99;
|
||||
++dependencies.displacement.revision;
|
||||
++dependencies.enthalpy.revision;
|
||||
++dependencies.gravityPotential.revision;
|
||||
const auto state = make_state(h, phi, displacement, 0.39);
|
||||
REQUIRE(fast.Prepare(state, dependencies, rotation).DidAnyWork());
|
||||
REQUIRE(reference.PrepareReference(state, dependencies, rotation).DidAnyWork());
|
||||
CHECK(fast.GetTensorDisplacementElementCount() > 0);
|
||||
const auto compare = [&](auto &&apply) {
|
||||
mfem::Vector a, b;
|
||||
apply(fast, a); apply(reference, b);
|
||||
CHECK(gravity_prepared_test_utils::relative_error(a, b, f.mesh->GetComm()) < 2.0e-11);
|
||||
};
|
||||
compare([](auto &op, auto &out) { op.BuildResidual(out); });
|
||||
compare([&](auto &op, auto &out) { op.ApplyEnthalpyJacobianAction(dh, out); });
|
||||
compare([&](auto &op, auto &out) { op.ApplyGravityPotentialJacobianAction(dp, out); });
|
||||
compare([](auto &op, auto &out) { op.ApplyBernoulliConstantJacobianAction(0.13, out); });
|
||||
compare([&](auto &op, auto &out) { op.ApplyDisplacementJacobianAction(du, out); });
|
||||
compare([&](auto &op, auto &out) { op.ApplyCompleteJacobianAction(dh, dp, 0.13, du, out); });
|
||||
compare([](auto &op, auto &out) { op.AssembleEnthalpyJacobianDiagonal(out); });
|
||||
CHECK_FALSE(fast.Prepare(state, dependencies, rotation).DidAnyWork());
|
||||
CHECK_FALSE(reference.PrepareReference(state, dependencies, rotation).DidAnyWork());
|
||||
|
||||
CHECK(fast.PrepareReference(state, dependencies, rotation).DidAnyWork());
|
||||
compare([](auto &op, auto &out) { op.BuildResidual(out); });
|
||||
CHECK_FALSE(fast.PrepareReference(state, dependencies, rotation).DidAnyWork());
|
||||
CHECK(fast.Prepare(state, dependencies, rotation).DidAnyWork());
|
||||
compare([&](auto &op, auto &out) { op.ApplyCompleteJacobianAction(dh, dp, 0.13, du, out); });
|
||||
CHECK_FALSE(fast.Prepare(state, dependencies, rotation).DidAnyWork());
|
||||
}
|
||||
|
||||
mfem::ParGridFunction foldedField(f.displacementFes.get());
|
||||
mfem::VectorFunctionCoefficient fold(3, [](const mfem::Vector &x, mfem::Vector &u) {
|
||||
u = x; u *= -2.0;
|
||||
});
|
||||
foldedField.ProjectCoefficient(fold);
|
||||
mfem::Vector foldedTrue; foldedField.GetTrueDofs(foldedTrue);
|
||||
const auto folded = fast.GetDisplacementMap().gather(foldedTrue);
|
||||
++dependencies.displacement.revision;
|
||||
const auto rejectedState = make_state(h, phi, folded, 0.39);
|
||||
const auto rejected = fast.TryPrepare(rejectedState, dependencies, rotation);
|
||||
const auto referenceRejected = reference.TryPrepareReference(rejectedState, dependencies, rotation);
|
||||
REQUIRE_FALSE(rejected.has_value());
|
||||
REQUIRE_FALSE(referenceRejected.has_value());
|
||||
CHECK(rejected.error().reason == referenceRejected.error().reason);
|
||||
CHECK(rejected.error().mappingStatus == mean_field::mapping::MappingStatus::non_positive_determinant);
|
||||
CHECK_FALSE(fast.IsPrepared());
|
||||
CHECK_FALSE(reference.IsPrepared());
|
||||
|
||||
const auto recovered = make_state(h, phi, du, 0.39);
|
||||
++dependencies.displacement.revision;
|
||||
REQUIRE(fast.TryPrepare(recovered, dependencies, rotation).has_value());
|
||||
REQUIRE(reference.TryPrepareReference(recovered, dependencies, rotation).has_value());
|
||||
mfem::Vector a, b;
|
||||
fast.BuildResidual(a); reference.BuildResidual(b);
|
||||
CHECK(gravity_prepared_test_utils::relative_error(a, b, f.mesh->GetComm()) < 2.0e-11);
|
||||
}
|
||||
|
||||
@@ -198,6 +198,16 @@ TEST_CASE(
|
||||
CHECK(statistics.applications == 3);
|
||||
CHECK(centeredDifferenceError < 2.0e-7);
|
||||
CHECK(linearityError < 5.0e-12);
|
||||
int stellarElements = 0;
|
||||
preparedOperator.VisitMappedGeometryRules([&](int, const mfem::IntegrationRule &) { ++stellarElements; });
|
||||
CHECK(preparedOperator.GetFastDisplacementElementCount() == stellarElements);
|
||||
mfem::Vector preparedReference;
|
||||
preparedOperator.ApplyDisplacementJacobianActionReference(firstVariation, preparedReference);
|
||||
CHECK(prepared_hydrostatic_displacement_test_utils::relative_error(
|
||||
firstAction, preparedReference, f.mesh->GetComm()) < 2.0e-11);
|
||||
preparedOperator.ApplyDisplacementJacobianActionReference(secondVariation, preparedReference);
|
||||
CHECK(prepared_hydrostatic_displacement_test_utils::relative_error(
|
||||
secondAction, preparedReference, f.mesh->GetComm()) < 2.0e-11);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
@@ -344,4 +354,13 @@ TEST_CASE(
|
||||
CHECK(displacementStatistics.applications == 4);
|
||||
CHECK(geometryReferenceError < 2.0e-7);
|
||||
CHECK(geometryEffect > 1.0e-8);
|
||||
mfem::Vector preparedReference;
|
||||
preparedOperator.ApplyDisplacementJacobianActionReference(displacementVariation, preparedReference);
|
||||
CHECK(prepared_hydrostatic_displacement_test_utils::relative_error(
|
||||
geometryUpdatedAction, preparedReference, f.mesh->GetComm()) < 2.0e-11);
|
||||
preparedOperator.ApplyDisplacementJacobianActionCachedReference(displacementVariation, preparedReference);
|
||||
CHECK(prepared_hydrostatic_displacement_test_utils::relative_error(
|
||||
geometryUpdatedAction, preparedReference, f.mesh->GetComm()) < 2.0e-11);
|
||||
CHECK(preparedOperator.GetTensorDisplacementElementCount() > 0);
|
||||
CHECK(preparedOperator.GetDisplacementJacobianStatistics().applications == 4);
|
||||
}
|
||||
|
||||
@@ -439,10 +439,26 @@ TEST_CASE(
|
||||
|
||||
massOperator.Prepare({.targetMass = 1.0}, dependencies);
|
||||
const std::uint64_t preparationCount = massOperator.GetPreparationCount();
|
||||
CHECK(massOperator.GetJacobianRowPreparationCount() == 0);
|
||||
const auto rhoDirection = gravityContext.GetDensityMap().gather(
|
||||
mass_normalization_test_utils::make_density_direction(f, 0.67));
|
||||
const auto uDirection = gravityContext.GetDisplacementMap().gather(
|
||||
mass_normalization_test_utils::make_displacement_direction(f, -0.53));
|
||||
auto checkRows = [&](std::uint64_t expectedBuilds) {
|
||||
REQUIRE(massOperator.PrepareJacobianRows());
|
||||
CHECK(massOperator.GetJacobianRowPreparationCount() == expectedBuilds);
|
||||
mfem::Vector actual, reference;
|
||||
massOperator.ApplyCompleteJacobianAction(rhoDirection, uDirection, actual);
|
||||
massOperator.ApplyCompleteJacobianActionReference(rhoDirection, uDirection, reference);
|
||||
CHECK(mass_normalization_test_utils::relative_error(actual(0), reference(0)) < 2.0e-11);
|
||||
CHECK(massOperator.GetJacobianRowPreparationCount() == expectedBuilds);
|
||||
};
|
||||
checkRows(1);
|
||||
|
||||
const auto repeated = massOperator.Prepare({.targetMass = 1.0}, dependencies);
|
||||
CHECK_FALSE(repeated.DidAnyWork());
|
||||
CHECK(massOperator.GetPreparationCount() == preparationCount);
|
||||
checkRows(1);
|
||||
|
||||
++gravityPotentialRevision;
|
||||
mass_normalization_test_utils::prepare_gravity_context(
|
||||
@@ -451,6 +467,7 @@ TEST_CASE(
|
||||
|
||||
const auto potentialOnly = massOperator.Prepare({.targetMass = 1.0}, dependencies);
|
||||
CHECK_FALSE(potentialOnly.DidAnyWork());
|
||||
checkRows(1);
|
||||
|
||||
const double residualBeforeTargetChange = mass_normalization_test_utils::residual_value(massOperator);
|
||||
const double massBeforeTargetChange = massOperator.GetCurrentMass();
|
||||
@@ -463,6 +480,7 @@ TEST_CASE(
|
||||
CHECK_FALSE(targetOnly.refreshedGeometry);
|
||||
CHECK_FALSE(targetOnly.refreshedDensity);
|
||||
CHECK(massOperator.GetCurrentMass() == massBeforeTargetChange);
|
||||
checkRows(1);
|
||||
CHECK(
|
||||
mass_normalization_test_utils::relative_error(
|
||||
mass_normalization_test_utils::residual_value(massOperator) - residualBeforeTargetChange, -0.4
|
||||
@@ -481,6 +499,8 @@ TEST_CASE(
|
||||
CHECK(densityOnly.assembledResidual);
|
||||
CHECK_FALSE(densityOnly.refreshedGeometry);
|
||||
CHECK(massOperator.GetCurrentMass() != massBeforeDensityChange);
|
||||
CHECK(massOperator.GetJacobianRowPreparationCount() == 1);
|
||||
checkRows(2);
|
||||
|
||||
displacement = mass_normalization_test_utils::make_displacement_direction(f, 0.87);
|
||||
++dependencies.displacement.revision;
|
||||
@@ -492,6 +512,8 @@ TEST_CASE(
|
||||
CHECK(geometryOnly.refreshedGeometry);
|
||||
CHECK(geometryOnly.assembledResidual);
|
||||
CHECK_FALSE(geometryOnly.refreshedDensity);
|
||||
CHECK(massOperator.GetJacobianRowPreparationCount() == 2);
|
||||
checkRows(3);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
@@ -587,6 +609,11 @@ TEST_CASE(
|
||||
massOperator.ApplyDisplacementJacobianAction(reducedDisplacementDirection, displacementAction);
|
||||
massOperator.ApplyCompleteJacobianAction(reducedDensityDirection, reducedDisplacementDirection, completeAction);
|
||||
|
||||
mfem::Vector preparedReference;
|
||||
massOperator.ApplyCompleteJacobianActionReference(
|
||||
reducedDensityDirection, reducedDisplacementDirection, preparedReference);
|
||||
CHECK(mass_normalization_test_utils::relative_error(completeAction(0), preparedReference(0)) < 2.0e-11);
|
||||
|
||||
CHECK(
|
||||
mass_normalization_test_utils::relative_error(completeAction(0), densityAction(0) + displacementAction(0)) <
|
||||
2.0e-15
|
||||
|
||||
@@ -439,6 +439,15 @@ TEST_CASE(
|
||||
mfem::Vector kernelDisplacementActionTrue;
|
||||
|
||||
preparedOperator.ApplyDisplacementJacobianAction(displacementDirection, preparedDisplacementAction);
|
||||
REQUIRE(preparedOperator.GetTensorElementCount() > 0);
|
||||
mfem::Vector cachedDisplacementAction;
|
||||
preparedOperator.ApplyDisplacementJacobianActionCachedReference(displacementDirection, cachedDisplacementAction);
|
||||
CHECK(prepared_pressure_force_test_utils::relative_difference(
|
||||
preparedDisplacementAction, cachedDisplacementAction, f.mesh->GetComm()) < 2.0e-12);
|
||||
mfem::Vector retainedDisplacementAction;
|
||||
preparedOperator.ApplyDisplacementJacobianActionReference(displacementDirection, retainedDisplacementAction);
|
||||
CHECK(prepared_pressure_force_test_utils::relative_difference(
|
||||
preparedDisplacementAction, retainedDisplacementAction, f.mesh->GetComm()) < 2.0e-12);
|
||||
|
||||
mean_field::operators::kernels::apply_pressure_force_displacement_action(
|
||||
f, *f.domainMapperStateless, equationOfState, enthalpyTrue, displacementDirectionTrue, displacementTrue,
|
||||
@@ -465,6 +474,30 @@ TEST_CASE(
|
||||
prepared_pressure_force_test_utils::relative_difference(fusedAction, expectedFusedAction, f.mesh->GetComm()) <
|
||||
2.0e-12
|
||||
);
|
||||
|
||||
mfem::Vector originalResidual, referenceResidual, referenceEnthalpy;
|
||||
preparedOperator.BuildResidual(originalResidual);
|
||||
const auto state = mean_field::operators::context::pressure_force::PressureForceStateView{
|
||||
.enthalpy = enthalpy, .displacement = displacement};
|
||||
const auto dependencies = prepared_pressure_force_test_utils::make_dependencies();
|
||||
CHECK(preparedOperator.PrepareReference(state, dependencies).DidAnyWork());
|
||||
preparedOperator.BuildResidual(referenceResidual);
|
||||
preparedOperator.ApplyEnthalpyJacobianAction(enthalpyDirection, referenceEnthalpy);
|
||||
CHECK(prepared_pressure_force_test_utils::relative_difference(
|
||||
originalResidual, referenceResidual, f.mesh->GetComm()) < 2.0e-12);
|
||||
CHECK(prepared_pressure_force_test_utils::relative_difference(
|
||||
preparedEnthalpyAction, referenceEnthalpy, f.mesh->GetComm()) < 2.0e-12);
|
||||
CHECK_FALSE(preparedOperator.PrepareReference(state, dependencies).DidAnyWork());
|
||||
CHECK(preparedOperator.PrepareCachedReference(state, dependencies).DidAnyWork());
|
||||
preparedOperator.BuildResidual(referenceResidual);
|
||||
preparedOperator.ApplyEnthalpyJacobianAction(enthalpyDirection, referenceEnthalpy);
|
||||
CHECK(prepared_pressure_force_test_utils::relative_difference(
|
||||
originalResidual, referenceResidual, f.mesh->GetComm()) < 2.0e-12);
|
||||
CHECK(prepared_pressure_force_test_utils::relative_difference(
|
||||
preparedEnthalpyAction, referenceEnthalpy, f.mesh->GetComm()) < 2.0e-12);
|
||||
CHECK_FALSE(preparedOperator.PrepareCachedReference(state, dependencies).DidAnyWork());
|
||||
CHECK(preparedOperator.Prepare(state, dependencies).DidAnyWork());
|
||||
CHECK_FALSE(preparedOperator.Prepare(state, dependencies).DidAnyWork());
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
|
||||
@@ -551,6 +551,19 @@ TEST_CASE(
|
||||
CHECK(rotationReport.preparedResidual);
|
||||
CHECK(preparedOperator.GetResidualPreparationCount() == 3);
|
||||
CHECK(preparedOperator.GetResidualApplicationCount() == 1);
|
||||
mfem::Vector updatedResidual, referenceResidual;
|
||||
preparedOperator.BuildResidual(updatedResidual);
|
||||
CHECK(preparedOperator.PrepareReference(
|
||||
{.density = density, .displacement = displacement}, dependencies, rotation).DidAnyWork());
|
||||
preparedOperator.BuildResidual(referenceResidual);
|
||||
CHECK(rotational_displacement_force_test_utils::relative_difference(
|
||||
updatedResidual, referenceResidual, f.mesh->GetComm()) < 2.0e-12);
|
||||
CHECK_FALSE(preparedOperator.PrepareReference(
|
||||
{.density = density, .displacement = displacement}, dependencies, rotation).DidAnyWork());
|
||||
CHECK(preparedOperator.Prepare(
|
||||
{.density = density, .displacement = displacement}, dependencies, rotation).DidAnyWork());
|
||||
CHECK_FALSE(preparedOperator.Prepare(
|
||||
{.density = density, .displacement = displacement}, dependencies, rotation).DidAnyWork());
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
@@ -598,6 +611,16 @@ TEST_CASE(
|
||||
|
||||
preparedOperator.ApplyCompleteJacobianAction(densityDirection, displacementDirection, completeAction);
|
||||
|
||||
mfem::Vector retainedAction;
|
||||
preparedOperator.ApplyCompleteJacobianActionReference(densityDirection, displacementDirection, retainedAction);
|
||||
CHECK(preparedOperator.GetFastElementCount() > 0);
|
||||
CHECK(preparedOperator.GetTensorElementCount() == preparedOperator.GetFastElementCount());
|
||||
CHECK(rotational_displacement_force_test_utils::relative_difference(
|
||||
completeAction, retainedAction, f.mesh->GetComm()) < 2.0e-12);
|
||||
preparedOperator.ApplyCompleteJacobianActionCachedReference(densityDirection, displacementDirection, retainedAction);
|
||||
CHECK(rotational_displacement_force_test_utils::relative_difference(
|
||||
completeAction, retainedAction, f.mesh->GetComm()) < 2.0e-12);
|
||||
|
||||
mfem::Vector summedColumns(densityAction);
|
||||
summedColumns += displacementAction;
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
|
||||
#include <catch2/catch_approx.hpp>
|
||||
@@ -313,6 +314,8 @@ TEST_CASE(
|
||||
preconditioning::GravityApproximateLDU{}
|
||||
);
|
||||
auto chebyshevPrepared = preconditioning::prepare(geometry.finiteElements, geometry.context, chebyshevBlock);
|
||||
CHECK(prepared.GetDivergenceAssemblyCount() == 1);
|
||||
CHECK(chebyshevPrepared.GetDivergenceAssemblyCount() == 1);
|
||||
|
||||
const auto unchanged = prepared.Refresh(geometry.finiteElements, geometry.context);
|
||||
const auto unchangedChebyshev = chebyshevPrepared.Refresh(geometry.finiteElements, geometry.context);
|
||||
@@ -338,6 +341,8 @@ TEST_CASE(
|
||||
const std::uint64_t sourcePreparations = geometry.context.GetSourceOperator().GetPreparationCount();
|
||||
const auto changed = prepared.Refresh(geometry.finiteElements, geometry.context);
|
||||
const auto changedChebyshev = chebyshevPrepared.Refresh(geometry.finiteElements, geometry.context);
|
||||
CHECK(prepared.GetDivergenceAssemblyCount() == 1);
|
||||
CHECK(chebyshevPrepared.GetDivergenceAssemblyCount() == 1);
|
||||
|
||||
CHECK(changed.geometryChanged);
|
||||
CHECK_FALSE(changed.discretizationChanged);
|
||||
@@ -377,6 +382,7 @@ TEST_CASE(
|
||||
CHECK(reconstructed.rebuiltPotentialSchur);
|
||||
CHECK(chebyshevPrepared.IsCurrent());
|
||||
CHECK(chebyshevPrepared.GetMassInverse().GetStatistics().setups == 3);
|
||||
CHECK(chebyshevPrepared.GetDivergenceAssemblyCount() == 2);
|
||||
|
||||
mfem::Vector firstReconstructedAction(chebyshevPrepared.Height());
|
||||
mfem::Vector secondReconstructedAction(chebyshevPrepared.Height());
|
||||
@@ -391,3 +397,52 @@ TEST_CASE(
|
||||
CHECK(std::isfinite(firstReconstructedAction(index)));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Fixed Gravity Divergence Cache Preserves Changing Mass Weights And Invalidates",
|
||||
tags::preconditioning_gravity_integration
|
||||
) {
|
||||
const auto args = test_utils::setup_args();
|
||||
auto f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
|
||||
REQUIRE(f.okay());
|
||||
preconditioning::PreparedGravityDivergenceCache cache;
|
||||
mfem::Vector diagonal(f.gravityFluxFes->GetTrueVSize()); diagonal = 1.0;
|
||||
const auto x = gravity_prepared_test_utils::make_deterministic_vector(f.gravityPotentialFes->GetTrueVSize(), 0.37);
|
||||
auto initial = preconditioning::assembleGravityPotentialSchurSurrogate(f, diagonal, &cache);
|
||||
mfem::Vector initialAction(initial->Height()); initial->Mult(x, initialAction);
|
||||
REQUIRE(initialAction.Norml2() > 0.0);
|
||||
REQUIRE(cache.GetBuildCount() == 1);
|
||||
for (double scale : {2.0, 0.5, 1.0}) {
|
||||
CAPTURE(scale);
|
||||
diagonal = scale;
|
||||
auto fast = preconditioning::assembleGravityPotentialSchurSurrogate(f, diagonal, &cache);
|
||||
auto reference = preconditioning::assembleGravityPotentialSchurSurrogate(f, diagonal);
|
||||
mfem::Vector a(fast->Height()), b(reference->Height()), expected(initialAction);
|
||||
fast->Mult(x, a); reference->Mult(x, b); expected /= scale;
|
||||
CHECK(gravity_prepared_test_utils::relative_error(a, b, f.mesh->GetComm()) < 2.0e-12);
|
||||
CHECK(gravity_prepared_test_utils::relative_error(a, expected, f.mesh->GetComm()) < 2.0e-12);
|
||||
CHECK(cache.GetBuildCount() == 1);
|
||||
initial->Mult(x, a);
|
||||
CHECK(gravity_prepared_test_utils::relative_error(a, initialAction, f.mesh->GetComm()) < 2.0e-14);
|
||||
}
|
||||
for (double bad : {0.0, -1.0, std::numeric_limits<double>::quiet_NaN()}) {
|
||||
diagonal = bad;
|
||||
CHECK_THROWS_AS(
|
||||
preconditioning::assembleGravityPotentialSchurSurrogate(f, diagonal, &cache), std::invalid_argument);
|
||||
CHECK(cache.GetBuildCount() == 1);
|
||||
}
|
||||
diagonal = 1.0;
|
||||
const auto checkRebuild = [&] {
|
||||
auto fast = preconditioning::assembleGravityPotentialSchurSurrogate(f, diagonal, &cache);
|
||||
auto reference = preconditioning::assembleGravityPotentialSchurSurrogate(f, diagonal);
|
||||
mfem::Vector a(fast->Height()), b(reference->Height());
|
||||
fast->Mult(x, a); reference->Mult(x, b);
|
||||
CHECK(gravity_prepared_test_utils::relative_error(a, b, f.mesh->GetComm()) < 2.0e-12);
|
||||
};
|
||||
cache.Clear(); checkRebuild();
|
||||
CHECK(cache.GetBuildCount() == 2);
|
||||
f.mesh->NodesUpdated(); checkRebuild();
|
||||
CHECK(cache.GetBuildCount() == 3);
|
||||
checkRebuild();
|
||||
CHECK(cache.GetBuildCount() == 3);
|
||||
}
|
||||
|
||||
@@ -1552,3 +1552,48 @@ TEST_CASE(
|
||||
CHECK(report.lastAcceptedCheckpointView().valid());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Skipping Geometry Preflight Retains Trial Acceptance And Fold Rejection",
|
||||
"[solver][newton][geometry][preflight][backtracking][wiring]") {
|
||||
using namespace mean_field;
|
||||
using namespace stellar_solver_architecture_test;
|
||||
for (bool folding : {false, true}) {
|
||||
CAPTURE(folding);
|
||||
auto finiteElements = makeFiniteElements();
|
||||
auto discretization = equilibrium::makeStellarDiscretization(
|
||||
std::move(finiteElements),
|
||||
normalization::PhysicalRieszDiagonal{dimensions::LengthValue{utils::RADIUS}, utils::G});
|
||||
auto context = solver::makeContext(
|
||||
makeModel(), std::move(discretization), preconditioning::makePreconditioner(),
|
||||
ScriptedBackend{nullptr, folding ? -10.0 : 0.0, false, true});
|
||||
auto metricState = std::make_shared<MetricSequenceState>();
|
||||
metricState->evaluations = {{.residualNorm = 2.0, .merit = 2.0}, {.residualNorm = 1.0, .merit = 0.5}};
|
||||
bool observed = false;
|
||||
auto observer = solver::nonlinear::makeObserver(
|
||||
[](const solver::nonlinear::BeforeIteration &) {},
|
||||
[&](const solver::nonlinear::AfterIteration &event) {
|
||||
observed = true;
|
||||
CHECK_FALSE(event.geometryPreflight.has_value());
|
||||
CHECK(event.geometryPreflightSeconds == 0.0);
|
||||
CHECK(event.lineSearchTrials == 1);
|
||||
CHECK(event.stepAccepted == !folding);
|
||||
});
|
||||
auto newton = solver::nonlinear::Newton(
|
||||
solver::nonlinear::NewtonOptions{
|
||||
.relativeTolerance = 0.0, .absoluteTolerance = 0.0, .maximumIterations = 1,
|
||||
.linearSolve = {.relativeTolerance = 0.0, .absoluteTolerance = std::numeric_limits<double>::max(),
|
||||
.maximumIterations = 1},
|
||||
.backtracking = {.maximumTrials = 1, .geometryPreflight = false}},
|
||||
SequencedMetric{metricState});
|
||||
auto solverInstance = solver::make(context, std::move(newton), std::move(observer));
|
||||
const auto report = solverInstance.evaluate();
|
||||
CHECK(observed);
|
||||
CHECK_FALSE(report.diagnostics().lastGeometryPreflight.has_value());
|
||||
CHECK(report.diagnostics().totalGeometryPreflightSeconds == 0.0);
|
||||
CHECK(report.diagnostics().geometryLimitedIterations == 0);
|
||||
CHECK(report.diagnostics().totalLineSearchTrials == 1);
|
||||
CHECK(report.diagnostics().acceptedNonlinearIterations == (folding ? 0 : 1));
|
||||
CHECK(report.diagnostics().inadmissibleLineSearchTrials == (folding ? 1 : 0));
|
||||
CHECK(report.lastAcceptedCheckpointView().valid());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user