perf(allocations): reduced overall allocations by 95%, increaseed jacobian applicatin by 2x
This commit uses global pre allocated work space to dramatically reduce memory usage and allocation time
This commit is contained in:
257
tests/deformation/safe_newton_step.cpp
Normal file
257
tests/deformation/safe_newton_step.cpp
Normal file
@@ -0,0 +1,257 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/matchers/catch_matchers_floating_point.hpp>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <mfem.hpp>
|
||||
#include <mpi.h>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
import mean_field;
|
||||
|
||||
namespace {
|
||||
using Catch::Matchers::WithinAbs;
|
||||
|
||||
constexpr int dimension = 3;
|
||||
|
||||
[[nodiscard]] mfem::Mesh make_serial_mesh(const int attribute) {
|
||||
mfem::Mesh mesh = mfem::Mesh::MakeCartesian3D(2, 1, 1, mfem::Element::HEXAHEDRON, 2.0, 1.0, 1.0);
|
||||
for (int element = 0; element < mesh.GetNE(); ++element) {
|
||||
mesh.GetElement(element)->SetAttribute(attribute);
|
||||
}
|
||||
return mesh;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::unique_ptr<const mean_field::mapping::compactification::ExteriorDomainMap>
|
||||
make_kelvin_compactification() {
|
||||
return std::make_unique<mean_field::mapping::compactification::KelvinCompactification>(
|
||||
mean_field::mapping::compactification::options::KelvinCompactificationOptions{
|
||||
.r_star_ref = 1.0, .r_inf_ref = 4.0
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
struct GeometryFixture final {
|
||||
mfem::Mesh serialMesh;
|
||||
mfem::ParMesh mesh;
|
||||
mfem::H1_FECollection displacementCollection;
|
||||
mfem::ParFiniteElementSpace displacementSpace;
|
||||
mfem::H1_FECollection compactificationCollection;
|
||||
mfem::ParFiniteElementSpace compactificationSpace;
|
||||
mfem::ParGridFunction compactificationCoordinate;
|
||||
mean_field::mapping::DomainMapper mapper;
|
||||
|
||||
explicit GeometryFixture(const bool compactified = false)
|
||||
: serialMesh(make_serial_mesh(compactified ? 2 : 1)),
|
||||
mesh(
|
||||
MPI_COMM_WORLD,
|
||||
serialMesh
|
||||
),
|
||||
displacementCollection(
|
||||
1,
|
||||
dimension
|
||||
),
|
||||
displacementSpace(
|
||||
&mesh,
|
||||
&displacementCollection,
|
||||
dimension,
|
||||
mfem::Ordering::byNODES
|
||||
),
|
||||
compactificationCollection(
|
||||
1,
|
||||
dimension
|
||||
),
|
||||
compactificationSpace(
|
||||
&mesh,
|
||||
&compactificationCollection
|
||||
),
|
||||
compactificationCoordinate(&compactificationSpace),
|
||||
mapper(
|
||||
{.dimension = dimension,
|
||||
.vacuum_element_attribute = 2},
|
||||
make_kelvin_compactification()
|
||||
) {
|
||||
compactificationCoordinate = 0.0;
|
||||
}
|
||||
|
||||
[[nodiscard]] mfem::Vector zero_true_vector() const {
|
||||
mfem::Vector result(displacementSpace.GetTrueVSize());
|
||||
result = 0.0;
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename Function> [[nodiscard]] mfem::Vector project_direction(Function &&function) {
|
||||
mfem::VectorFunctionCoefficient coefficient(dimension, std::forward<Function>(function));
|
||||
mfem::ParGridFunction field(&displacementSpace);
|
||||
field.ProjectCoefficient(coefficient);
|
||||
mfem::Vector result;
|
||||
field.GetTrueDofs(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::vector<mean_field::deformation::NewtonStepGeometryRule> geometry_rules() {
|
||||
std::vector<mean_field::deformation::NewtonStepGeometryRule> result;
|
||||
result.reserve(static_cast<std::size_t>(mesh.GetNE()));
|
||||
for (int element = 0; element < mesh.GetNE(); ++element) {
|
||||
mfem::ElementTransformation *transformation = mesh.GetElementTransformation(element);
|
||||
result.push_back(
|
||||
{.element = element, .integrationRule = &mfem::IntRules.Get(transformation->GetGeometryType(), 2)}
|
||||
);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
void compress_x(
|
||||
const mfem::Vector &position,
|
||||
mfem::Vector &value
|
||||
) {
|
||||
value.SetSize(dimension);
|
||||
value = 0.0;
|
||||
value(0) = -2.0 * position(0);
|
||||
}
|
||||
|
||||
void compress_x_and_y(
|
||||
const mfem::Vector &position,
|
||||
mfem::Vector &value
|
||||
) {
|
||||
value.SetSize(dimension);
|
||||
value = 0.0;
|
||||
value(0) = -2.0 * position(0);
|
||||
value(1) = -2.0 * position(1);
|
||||
}
|
||||
|
||||
void expand_x(
|
||||
const mfem::Vector &position,
|
||||
mfem::Vector &value
|
||||
) {
|
||||
value.SetSize(dimension);
|
||||
value = 0.0;
|
||||
value(0) = position(0);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE(
|
||||
"Safe Newton Step Finds The First Mapping Boundary",
|
||||
"[deformation][newton][geometry][mpi]"
|
||||
) {
|
||||
GeometryFixture fixture;
|
||||
const mfem::Vector accepted = fixture.zero_true_vector();
|
||||
const mfem::Vector direction = fixture.project_direction(compress_x);
|
||||
const auto rules = fixture.geometry_rules();
|
||||
|
||||
const auto estimate = mean_field::deformation::estimate_largest_safe_newton_step_size(
|
||||
fixture.mapper, fixture.displacementSpace, fixture.compactificationCoordinate, accepted, direction, rules,
|
||||
{.maximumStepSize = 1.0, .determinantFloor = 0.0, .fractionToBoundarySafety = 0.8}
|
||||
);
|
||||
|
||||
CHECK(estimate.limitedByGeometry);
|
||||
CHECK_THAT(estimate.boundaryStepSize, WithinAbs(0.5, 2.0e-13));
|
||||
CHECK_THAT(estimate.stepSize, WithinAbs(0.4, 2.0e-13));
|
||||
CHECK_THAT(estimate.minimumDeterminantAtAcceptedState, WithinAbs(1.0, 2.0e-13));
|
||||
CHECK_THAT(estimate.minimumDeterminantAtMaximumStepSize, WithinAbs(-1.0, 2.0e-13));
|
||||
CHECK_THAT(estimate.limitingPointDeterminantAtStepSize, WithinAbs(0.2, 2.0e-13));
|
||||
CHECK(estimate.sampledQuadraturePointCount > 0);
|
||||
CHECK(estimate.limitingRank == 0);
|
||||
CHECK(estimate.limitingElement >= 0);
|
||||
CHECK(estimate.limitingRule >= 0);
|
||||
CHECK(estimate.limitingQuadraturePoint >= 0);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Safe Newton Step Detects A Tangent Singularity Before An Admissible Endpoint",
|
||||
"[deformation][newton][geometry][mpi]"
|
||||
) {
|
||||
GeometryFixture fixture(true);
|
||||
const mfem::Vector accepted = fixture.zero_true_vector();
|
||||
const mfem::Vector direction = fixture.project_direction(compress_x_and_y);
|
||||
const auto rules = fixture.geometry_rules();
|
||||
|
||||
const auto estimate = mean_field::deformation::estimate_largest_safe_newton_step_size(
|
||||
fixture.mapper, fixture.displacementSpace, fixture.compactificationCoordinate, accepted, direction, rules
|
||||
);
|
||||
|
||||
// det(J(alpha)) = (1 - 2 alpha)^2. Both endpoints are positive;
|
||||
// checking only alpha=1 would miss the singularity at alpha=1/2.
|
||||
CHECK(estimate.limitedByGeometry);
|
||||
CHECK_THAT(estimate.minimumDeterminantAtMaximumStepSize, WithinAbs(1.0, 3.0e-13));
|
||||
CHECK_THAT(estimate.boundaryStepSize, WithinAbs(0.5, 3.0e-13));
|
||||
CHECK_THAT(estimate.stepSize, WithinAbs(0.45, 3.0e-13));
|
||||
CHECK_THAT(estimate.limitingPointDeterminantAtStepSize, WithinAbs(0.01, 3.0e-13));
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Safe Newton Step Honors A Positive Determinant Floor",
|
||||
"[deformation][newton][geometry][mpi]"
|
||||
) {
|
||||
GeometryFixture fixture;
|
||||
const mfem::Vector accepted = fixture.zero_true_vector();
|
||||
const mfem::Vector direction = fixture.project_direction(compress_x);
|
||||
const auto rules = fixture.geometry_rules();
|
||||
|
||||
const auto estimate = mean_field::deformation::estimate_largest_safe_newton_step_size(
|
||||
fixture.mapper, fixture.displacementSpace, fixture.compactificationCoordinate, accepted, direction, rules,
|
||||
{.maximumStepSize = 1.0, .determinantFloor = 0.25, .fractionToBoundarySafety = 0.8}
|
||||
);
|
||||
|
||||
CHECK(estimate.limitedByGeometry);
|
||||
CHECK_THAT(estimate.boundaryStepSize, WithinAbs(0.375, 2.0e-13));
|
||||
CHECK_THAT(estimate.stepSize, WithinAbs(0.3, 2.0e-13));
|
||||
CHECK(estimate.limitingPointDeterminantAtStepSize > 0.25);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Safe Newton Step Leaves An Unconstrained Step Unchanged",
|
||||
"[deformation][newton][geometry][mpi]"
|
||||
) {
|
||||
GeometryFixture fixture;
|
||||
const mfem::Vector accepted = fixture.zero_true_vector();
|
||||
const mfem::Vector direction = fixture.project_direction(expand_x);
|
||||
const auto rules = fixture.geometry_rules();
|
||||
|
||||
const auto estimate = mean_field::deformation::estimate_largest_safe_newton_step_size(
|
||||
fixture.mapper, fixture.displacementSpace, fixture.compactificationCoordinate, accepted, direction, rules
|
||||
);
|
||||
|
||||
CHECK_FALSE(estimate.limitedByGeometry);
|
||||
CHECK_THAT(estimate.boundaryStepSize, WithinAbs(1.0, 2.0e-13));
|
||||
CHECK_THAT(estimate.stepSize, WithinAbs(1.0, 2.0e-13));
|
||||
CHECK_THAT(estimate.minimumDeterminantAtMaximumStepSize, WithinAbs(2.0, 2.0e-13));
|
||||
CHECK_THAT(estimate.limitingPointDeterminantAtStepSize, WithinAbs(2.0, 2.0e-13));
|
||||
CHECK(estimate.limitingRank == -1);
|
||||
CHECK(estimate.limitingElement == -1);
|
||||
CHECK(estimate.limitingRule == -1);
|
||||
CHECK(estimate.limitingQuadraturePoint == -1);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Safe Newton Step Rejects Invalid Inputs Collectively",
|
||||
"[deformation][newton][geometry][mpi]"
|
||||
) {
|
||||
GeometryFixture fixture;
|
||||
const mfem::Vector zero = fixture.zero_true_vector();
|
||||
const auto rules = fixture.geometry_rules();
|
||||
|
||||
CHECK_THROWS_AS(
|
||||
mean_field::deformation::estimate_largest_safe_newton_step_size(
|
||||
fixture.mapper, fixture.displacementSpace, fixture.compactificationCoordinate, zero, zero, rules,
|
||||
{.maximumStepSize = 0.0}
|
||||
),
|
||||
std::invalid_argument
|
||||
);
|
||||
CHECK_THROWS_AS(
|
||||
mean_field::deformation::estimate_largest_safe_newton_step_size(
|
||||
fixture.mapper, fixture.displacementSpace, fixture.compactificationCoordinate, zero, zero, {}
|
||||
),
|
||||
std::invalid_argument
|
||||
);
|
||||
|
||||
const mfem::Vector invalidAccepted = fixture.project_direction(compress_x);
|
||||
CHECK_THROWS_AS(
|
||||
mean_field::deformation::estimate_largest_safe_newton_step_size(
|
||||
fixture.mapper, fixture.displacementSpace, fixture.compactificationCoordinate, invalidAccepted, zero, rules
|
||||
),
|
||||
std::domain_error
|
||||
);
|
||||
}
|
||||
262
tests/fem/reference_tables.cpp
Normal file
262
tests/fem/reference_tables.cpp
Normal file
@@ -0,0 +1,262 @@
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <mfem.hpp>
|
||||
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
|
||||
namespace {
|
||||
using ScalarTable = mean_field::fem::ScalarReferenceTable;
|
||||
using VectorTable = mean_field::fem::VectorReferenceTable;
|
||||
using TableCache = mean_field::fem::ReferenceTableCache;
|
||||
|
||||
static_assert(std::is_same_v<
|
||||
decltype(std::declval<const TableCache &>().GetScalarTable(
|
||||
std::declval<const mfem::FiniteElement &>(),
|
||||
std::declval<const mfem::IntegrationRule &>()
|
||||
)),
|
||||
std::shared_ptr<const ScalarTable>>);
|
||||
static_assert(std::is_same_v<
|
||||
decltype(std::declval<const ScalarTable &>().GetValues()),
|
||||
const mfem::DenseMatrix &>);
|
||||
static_assert(std::is_same_v<
|
||||
decltype(std::declval<const ScalarTable &>().GetGradients(0)),
|
||||
const mfem::DenseMatrix &>);
|
||||
static_assert(std::is_same_v<
|
||||
decltype(std::declval<const TableCache &>().GetVectorTable(
|
||||
std::declval<const mfem::FiniteElement &>(),
|
||||
std::declval<const mfem::IntegrationRule &>()
|
||||
)),
|
||||
std::shared_ptr<const VectorTable>>);
|
||||
static_assert(std::is_same_v<
|
||||
decltype(std::declval<const VectorTable &>().GetValues(0)),
|
||||
const mfem::DenseMatrix &>);
|
||||
|
||||
void CheckMatrixExactly(
|
||||
const mfem::DenseMatrix &actual,
|
||||
const mfem::DenseMatrix &expected
|
||||
) {
|
||||
REQUIRE(actual.Height() == expected.Height());
|
||||
REQUIRE(actual.Width() == expected.Width());
|
||||
for (int column = 0; column < actual.Width(); ++column) {
|
||||
for (int row = 0; row < actual.Height(); ++row) {
|
||||
CHECK(actual(row, column) == expected(row, column));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CheckScalarTable(
|
||||
const ScalarTable &table,
|
||||
const mfem::FiniteElement &element,
|
||||
const mfem::IntegrationRule &rule
|
||||
) {
|
||||
REQUIRE(table.GetPointCount() == rule.GetNPoints());
|
||||
REQUIRE(table.GetDofCount() == element.GetDof());
|
||||
REQUIRE(table.GetDimension() == element.GetDim());
|
||||
REQUIRE(table.GetValues().Height() == rule.GetNPoints());
|
||||
REQUIRE(table.GetValues().Width() == element.GetDof());
|
||||
|
||||
mfem::Vector shape(element.GetDof());
|
||||
mfem::DenseMatrix gradient(element.GetDof(), element.GetDim());
|
||||
for (int point = 0; point < rule.GetNPoints(); ++point) {
|
||||
element.CalcShape(rule.IntPoint(point), shape);
|
||||
element.CalcDShape(rule.IntPoint(point), gradient);
|
||||
for (int dof = 0; dof < element.GetDof(); ++dof) {
|
||||
CHECK(table.GetValues()(point, dof) == shape(dof));
|
||||
}
|
||||
CheckMatrixExactly(table.GetGradients(point), gradient);
|
||||
}
|
||||
}
|
||||
|
||||
void CheckVectorTable(
|
||||
const VectorTable &table,
|
||||
const mfem::FiniteElement &element,
|
||||
const mfem::IntegrationRule &rule
|
||||
) {
|
||||
REQUIRE(table.GetPointCount() == rule.GetNPoints());
|
||||
REQUIRE(table.GetDofCount() == element.GetDof());
|
||||
REQUIRE(table.GetDimension() == element.GetRangeDim());
|
||||
mfem::DenseMatrix shape(element.GetDof(), element.GetRangeDim());
|
||||
for (int point = 0; point < rule.GetNPoints(); ++point) {
|
||||
element.CalcVShape(rule.IntPoint(point), shape);
|
||||
CheckMatrixExactly(table.GetValues(point), shape);
|
||||
}
|
||||
}
|
||||
|
||||
mfem::IntegrationRule CopyRule(const mfem::IntegrationRule &source) {
|
||||
mfem::IntegrationRule copy(source.GetNPoints());
|
||||
copy.SetOrder(source.GetOrder());
|
||||
for (int point = 0; point < source.GetNPoints(); ++point) {
|
||||
copy.IntPoint(point) = source.IntPoint(point);
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE(
|
||||
"Reference Table Cache Matches Scalar MFEM Values And Gradients",
|
||||
tags::unit &tags::quadrature
|
||||
) {
|
||||
for (const int dimension : std::array{2, 3}) {
|
||||
const auto geometry = dimension == 2 ? mfem::Geometry::SQUARE : mfem::Geometry::CUBE;
|
||||
for (const int order : std::array{1, 3}) {
|
||||
CAPTURE(dimension, order);
|
||||
mfem::H1_FECollection h1(order, dimension);
|
||||
mfem::L2_FECollection l2(order - 1, dimension);
|
||||
TableCache cache;
|
||||
const mfem::IntegrationRule &rule = mfem::IntRules.Get(geometry, 2 * order + 1);
|
||||
for (const mfem::FiniteElement *element :
|
||||
std::array{h1.FiniteElementForGeometry(geometry), l2.FiniteElementForGeometry(geometry)}) {
|
||||
REQUIRE(element != nullptr);
|
||||
const auto table = cache.GetScalarTable(*element, rule);
|
||||
REQUIRE(table != nullptr);
|
||||
CheckScalarTable(*table, *element, rule);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Reference Table Cache Matches RT Reference Values And Shares Equal Rules",
|
||||
tags::unit &tags::quadrature
|
||||
) {
|
||||
for (const int dimension : std::array{2, 3}) {
|
||||
const auto geometry = dimension == 2 ? mfem::Geometry::SQUARE : mfem::Geometry::CUBE;
|
||||
for (const int order : std::array{0, 2}) {
|
||||
CAPTURE(dimension, order);
|
||||
mfem::RT_FECollection standard(order, dimension);
|
||||
mfem::RT_FECollection integrated(
|
||||
order, dimension, mfem::BasisType::GaussLobatto, mfem::BasisType::IntegratedGLL
|
||||
);
|
||||
const mfem::FiniteElement &standardElement = *standard.FiniteElementForGeometry(geometry);
|
||||
const mfem::FiniteElement &integratedElement = *integrated.FiniteElementForGeometry(geometry);
|
||||
const mfem::IntegrationRule &rule = mfem::IntRules.Get(geometry, 2 * order + 3);
|
||||
const mfem::IntegrationRule copiedRule = CopyRule(rule);
|
||||
const TableCache cache;
|
||||
const auto standardTable = cache.GetVectorTable(standardElement, rule);
|
||||
const auto integratedTable = cache.GetVectorTable(integratedElement, rule);
|
||||
REQUIRE(standardTable != nullptr);
|
||||
REQUIRE(integratedTable != nullptr);
|
||||
CHECK(cache.GetVectorTable(standardElement, copiedRule).get() == standardTable.get());
|
||||
CHECK(cache.GetVectorTable(integratedElement, copiedRule).get() == integratedTable.get());
|
||||
CHECK(standardTable.get() != integratedTable.get());
|
||||
CheckVectorTable(*standardTable, standardElement, rule);
|
||||
CheckVectorTable(*integratedTable, integratedElement, rule);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Reference Table Cache Shares Equal Rules And Distinguishes Rule Contents",
|
||||
tags::unit &tags::quadrature
|
||||
) {
|
||||
mfem::H1_FECollection collection(3, 3);
|
||||
const mfem::FiniteElement &element = *collection.FiniteElementForGeometry(mfem::Geometry::CUBE);
|
||||
const mfem::IntegrationRule &rule = mfem::IntRules.Get(mfem::Geometry::CUBE, 7);
|
||||
mfem::IntegrationRule copiedRule = CopyRule(rule);
|
||||
mfem::IntegrationRule movedPointRule = CopyRule(rule);
|
||||
mfem::IntegrationRule changedWeightRule = CopyRule(rule);
|
||||
movedPointRule.IntPoint(0).x += 0.03125;
|
||||
changedWeightRule.IntPoint(0).weight *= 1.25;
|
||||
|
||||
const TableCache cache;
|
||||
const auto original = cache.GetScalarTable(element, rule);
|
||||
const mfem::DenseMatrix originalValues(original->GetValues());
|
||||
const auto copy = cache.GetScalarTable(element, copiedRule);
|
||||
const auto movedPoint = cache.GetScalarTable(element, movedPointRule);
|
||||
const auto changedWeight = cache.GetScalarTable(element, changedWeightRule);
|
||||
|
||||
CHECK(copy.get() == original.get());
|
||||
CHECK(movedPointRule.GetOrder() == rule.GetOrder());
|
||||
CHECK(changedWeightRule.GetOrder() == rule.GetOrder());
|
||||
CHECK(movedPoint.get() != original.get());
|
||||
CHECK(changedWeight.get() != original.get());
|
||||
CHECK(changedWeight.get() != movedPoint.get());
|
||||
CheckScalarTable(*movedPoint, element, movedPointRule);
|
||||
CheckScalarTable(*changedWeight, element, changedWeightRule);
|
||||
CheckMatrixExactly(original->GetValues(), originalValues);
|
||||
|
||||
// Rule identity is its contents, not its address, including after mutation.
|
||||
copiedRule.IntPoint(0).x = movedPointRule.IntPoint(0).x;
|
||||
CHECK(cache.GetScalarTable(element, copiedRule).get() == movedPoint.get());
|
||||
CHECK(cache.GetScalarTable(element, rule).get() == original.get());
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Reference Table Cache Distinguishes Scalar Basis Variants",
|
||||
tags::unit &tags::quadrature
|
||||
) {
|
||||
constexpr int dimension = 3;
|
||||
constexpr int order = 3;
|
||||
mfem::H1_FECollection nodalH1(order, dimension, mfem::BasisType::GaussLobatto);
|
||||
mfem::H1_FECollection positiveH1(order, dimension, mfem::BasisType::Positive);
|
||||
mfem::L2_FECollection openL2(order, dimension, mfem::BasisType::GaussLegendre);
|
||||
mfem::L2_FECollection closedL2(order, dimension, mfem::BasisType::GaussLobatto);
|
||||
const mfem::IntegrationRule &rule = mfem::IntRules.Get(mfem::Geometry::CUBE, 5);
|
||||
const TableCache cache;
|
||||
std::array<std::shared_ptr<const ScalarTable>, 4> tables;
|
||||
const std::array<const mfem::FiniteElement *, 4> elements{
|
||||
nodalH1.FiniteElementForGeometry(mfem::Geometry::CUBE),
|
||||
positiveH1.FiniteElementForGeometry(mfem::Geometry::CUBE),
|
||||
openL2.FiniteElementForGeometry(mfem::Geometry::CUBE), closedL2.FiniteElementForGeometry(mfem::Geometry::CUBE)
|
||||
};
|
||||
for (std::size_t index = 0; index < elements.size(); ++index) {
|
||||
REQUIRE(elements[index] != nullptr);
|
||||
REQUIRE(elements[index]->GetOrder() == order);
|
||||
REQUIRE(elements[index]->GetDof() == elements[0]->GetDof());
|
||||
tables[index] = cache.GetScalarTable(*elements[index], rule);
|
||||
CheckScalarTable(*tables[index], *elements[index], rule);
|
||||
for (std::size_t previous = 0; previous < index; ++previous) {
|
||||
CHECK(tables[index].get() != tables[previous].get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Reference Table Cache Published Scalar Storage Outlives Its Cache",
|
||||
tags::unit &tags::quadrature
|
||||
) {
|
||||
std::shared_ptr<const ScalarTable> retained;
|
||||
mfem::DenseMatrix expectedValues;
|
||||
mfem::DenseMatrix expectedGradient;
|
||||
{
|
||||
// FE objects remain immutable and alive throughout the cache lifetime.
|
||||
mfem::H1_FECollection collection(3, 2);
|
||||
const mfem::FiniteElement &element = *collection.FiniteElementForGeometry(mfem::Geometry::SQUARE);
|
||||
const mfem::IntegrationRule rule = CopyRule(mfem::IntRules.Get(mfem::Geometry::SQUARE, 7));
|
||||
const TableCache cache;
|
||||
retained = cache.GetScalarTable(element, rule);
|
||||
CheckScalarTable(*retained, element, rule);
|
||||
expectedValues = retained->GetValues();
|
||||
expectedGradient = retained->GetGradients(0);
|
||||
}
|
||||
REQUIRE(retained != nullptr);
|
||||
CheckMatrixExactly(retained->GetValues(), expectedValues);
|
||||
CheckMatrixExactly(retained->GetGradients(0), expectedGradient);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Reference Table Cache Published RT Storage Outlives Its Cache",
|
||||
tags::unit &tags::quadrature
|
||||
) {
|
||||
std::shared_ptr<const VectorTable> retained;
|
||||
mfem::DenseMatrix firstExpected;
|
||||
mfem::DenseMatrix lastExpected;
|
||||
{
|
||||
mfem::RT_FECollection collection(2, 3, mfem::BasisType::GaussLobatto, mfem::BasisType::IntegratedGLL);
|
||||
const mfem::FiniteElement &element = *collection.FiniteElementForGeometry(mfem::Geometry::CUBE);
|
||||
const mfem::IntegrationRule rule = CopyRule(mfem::IntRules.Get(mfem::Geometry::CUBE, 7));
|
||||
const TableCache cache;
|
||||
retained = cache.GetVectorTable(element, rule);
|
||||
CheckVectorTable(*retained, element, rule);
|
||||
firstExpected = retained->GetValues(0);
|
||||
lastExpected = retained->GetValues(rule.GetNPoints() - 1);
|
||||
}
|
||||
REQUIRE(retained != nullptr);
|
||||
CheckMatrixExactly(retained->GetValues(0), firstExpected);
|
||||
CheckMatrixExactly(retained->GetValues(retained->GetPointCount() - 1), lastExpected);
|
||||
}
|
||||
150
tests/mapping/prepared_cache.cpp
Normal file
150
tests/mapping/prepared_cache.cpp
Normal file
@@ -0,0 +1,150 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <mfem.hpp>
|
||||
#include <stdexcept>
|
||||
|
||||
import mean_field;
|
||||
|
||||
namespace {
|
||||
using mean_field::mapping::VolumeMappingContext;
|
||||
|
||||
void fill_vector(
|
||||
mfem::Vector &vector,
|
||||
const int dimension,
|
||||
const double offset
|
||||
) {
|
||||
vector.SetSize(dimension);
|
||||
for (int i = 0; i < dimension; ++i)
|
||||
vector(i) = offset + i;
|
||||
}
|
||||
|
||||
void fill_matrix(
|
||||
mfem::DenseMatrix &matrix,
|
||||
const int dimension,
|
||||
const double offset
|
||||
) {
|
||||
matrix.SetSize(dimension);
|
||||
for (int j = 0; j < dimension; ++j) {
|
||||
for (int i = 0; i < dimension; ++i)
|
||||
matrix(i, j) = offset + 10 * j + i;
|
||||
}
|
||||
}
|
||||
|
||||
VolumeMappingContext make_context(
|
||||
const int dimension,
|
||||
const double offset,
|
||||
const bool compactified
|
||||
) {
|
||||
VolumeMappingContext context;
|
||||
fill_vector(context.mapping.reference_position, dimension, offset + 1);
|
||||
fill_vector(context.mapping.displaced_position, dimension, offset + 2);
|
||||
fill_vector(context.mapping.physical_position, dimension, offset + 3);
|
||||
fill_matrix(context.mapping.displacement_jacobian, dimension, offset + 4);
|
||||
fill_matrix(context.mapping.mapping_jacobian, dimension, offset + 5);
|
||||
fill_matrix(context.mapping.inverse_mapping_jacobian, dimension, offset + 6);
|
||||
fill_matrix(context.quadrature.J_inv, dimension, offset + 7);
|
||||
context.mapping.mapping_determinant = offset + 8;
|
||||
context.mapping.compactified = compactified;
|
||||
context.quadrature.detJ = offset + 9;
|
||||
context.quadrature.weight = offset + 10;
|
||||
return context;
|
||||
}
|
||||
|
||||
void check_vector(
|
||||
const mfem::Vector &actual,
|
||||
const mfem::Vector &expected
|
||||
) {
|
||||
REQUIRE(actual.Size() == expected.Size());
|
||||
for (int i = 0; i < expected.Size(); ++i)
|
||||
CHECK(actual(i) == expected(i));
|
||||
}
|
||||
|
||||
void check_matrix(
|
||||
const mfem::DenseMatrix &actual,
|
||||
const mfem::DenseMatrix &expected
|
||||
) {
|
||||
REQUIRE(actual.Height() == expected.Height());
|
||||
REQUIRE(actual.Width() == expected.Width());
|
||||
for (int j = 0; j < expected.Width(); ++j) {
|
||||
for (int i = 0; i < expected.Height(); ++i)
|
||||
CHECK(actual(i, j) == expected(i, j));
|
||||
}
|
||||
}
|
||||
|
||||
void check_context(
|
||||
const VolumeMappingContext &actual,
|
||||
const VolumeMappingContext &expected
|
||||
) {
|
||||
check_vector(actual.mapping.reference_position, expected.mapping.reference_position);
|
||||
check_vector(actual.mapping.displaced_position, expected.mapping.displaced_position);
|
||||
check_vector(actual.mapping.physical_position, expected.mapping.physical_position);
|
||||
check_matrix(actual.mapping.displacement_jacobian, expected.mapping.displacement_jacobian);
|
||||
check_matrix(actual.mapping.mapping_jacobian, expected.mapping.mapping_jacobian);
|
||||
check_matrix(actual.mapping.inverse_mapping_jacobian, expected.mapping.inverse_mapping_jacobian);
|
||||
check_matrix(actual.quadrature.J_inv, expected.quadrature.J_inv);
|
||||
CHECK(actual.mapping.mapping_determinant == expected.mapping.mapping_determinant);
|
||||
CHECK(actual.mapping.compactified == expected.mapping.compactified);
|
||||
CHECK(actual.quadrature.detJ == expected.quadrature.detJ);
|
||||
CHECK(actual.quadrature.weight == expected.quadrature.weight);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE(
|
||||
"Flat Volume Mapping Cache Preserves Every Context Field",
|
||||
"[mapping][prepared-cache]"
|
||||
) {
|
||||
for (const int dimension : {1, 2, 3}) {
|
||||
CAPTURE(dimension);
|
||||
mean_field::mapping::VolumeMappingCache cache;
|
||||
cache.SetSize(2, dimension);
|
||||
CHECK(cache.GetPointCount() == 2);
|
||||
CHECK(cache.GetDimension() == dimension);
|
||||
const auto first = make_context(dimension, 0.125, false);
|
||||
const auto second = make_context(dimension, -30.25, true);
|
||||
cache.Store(0, first);
|
||||
cache.Store(1, second);
|
||||
|
||||
VolumeMappingContext workspace;
|
||||
cache.Load(1, workspace);
|
||||
check_context(workspace, second);
|
||||
const double *inverse_buffer = workspace.quadrature.J_inv.HostRead();
|
||||
const double *position_buffer = workspace.mapping.physical_position.HostRead();
|
||||
cache.Load(0, workspace);
|
||||
check_context(workspace, first);
|
||||
CHECK(workspace.quadrature.J_inv.HostRead() == inverse_buffer);
|
||||
CHECK(workspace.mapping.physical_position.HostRead() == position_buffer);
|
||||
|
||||
mfem::DenseMatrix inverse;
|
||||
cache.LoadInverseJacobian(1, inverse);
|
||||
check_matrix(inverse, second.quadrature.J_inv);
|
||||
const auto copy = cache;
|
||||
cache.Store(1, first);
|
||||
copy.Load(1, workspace);
|
||||
check_context(workspace, second);
|
||||
cache.Load(1, workspace);
|
||||
check_context(workspace, first);
|
||||
|
||||
cache.SetSize(1, 3);
|
||||
const auto resized = make_context(3, 13.5, true);
|
||||
cache.Store(0, resized);
|
||||
cache.Load(0, workspace);
|
||||
check_context(workspace, resized);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Flat Volume Mapping Cache Rejects Invalid Indices And Dimensions",
|
||||
"[mapping][prepared-cache]"
|
||||
) {
|
||||
mean_field::mapping::VolumeMappingCache cache;
|
||||
VolumeMappingContext workspace;
|
||||
CHECK_THROWS_AS(cache.SetSize(-1, 3), std::invalid_argument);
|
||||
CHECK_THROWS_AS(cache.SetSize(1, 0), std::invalid_argument);
|
||||
CHECK_THROWS_AS(cache.SetSize(1, 4), std::invalid_argument);
|
||||
cache.SetSize(1, 3);
|
||||
CHECK_THROWS_AS(cache.Load(-1, workspace), std::out_of_range);
|
||||
CHECK_THROWS_AS(cache.Load(1, workspace), std::out_of_range);
|
||||
CHECK_THROWS_AS(cache.Store(0, make_context(2, 0.0, false)), std::invalid_argument);
|
||||
cache.SetSize(0, 2);
|
||||
CHECK(cache.GetPointCount() == 0);
|
||||
CHECK_THROWS_AS(cache.Load(0, workspace), std::out_of_range);
|
||||
}
|
||||
@@ -60,6 +60,67 @@ TEST_CASE(
|
||||
CHECK(preparedOperator.GetPreparationCount() == 1);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Prepared Mapped Gravity Source Reuses Tables Across Preparation Modes And Rejection",
|
||||
tags::gravity_prepared_unit &tags::geometry
|
||||
) {
|
||||
auto args = test_utils::setup_args();
|
||||
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
|
||||
REQUIRE(f.okay());
|
||||
|
||||
operators::PreparedMappedGravitySourceOperator operation(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);
|
||||
const mfem::Vector displacement = operation.GetDisplacementMap().gather(displacementTrue);
|
||||
const mfem::Vector directionTrue = prepared_test::make_displacement(f, 0.7);
|
||||
const MPI_Comm communicator = f.mesh->GetComm();
|
||||
|
||||
operation.Prepare(displacement);
|
||||
mfem::Vector baselineAction;
|
||||
mfem::Vector baselineVariation;
|
||||
operation.Mult(density, baselineAction);
|
||||
operation.MultDisplacementVariationTrue(densityTrue, directionTrue, baselineVariation);
|
||||
|
||||
const mfem::Vector primalDisplacementTrue = prepared_test::make_displacement(f, 1.0);
|
||||
operation.PreparePrimal(operation.GetDisplacementMap().gather(primalDisplacementTrue));
|
||||
REQUIRE(operation.IsPrepared());
|
||||
CHECK_FALSE(operation.HasVariationData());
|
||||
mfem::Vector primalAction;
|
||||
mfem::Vector referenceActionTrue;
|
||||
operation.Mult(density, primalAction);
|
||||
operators::kernels::apply_mapped_source(
|
||||
f, *f.domainMapperStateless, densityTrue, primalDisplacementTrue, referenceActionTrue
|
||||
);
|
||||
CHECK_THAT(
|
||||
prepared_test::relative_error(
|
||||
primalAction, operation.GetPotentialMap().gather(referenceActionTrue), communicator
|
||||
),
|
||||
WithinAbs(0.0, 2.0e-11)
|
||||
);
|
||||
|
||||
operation.Prepare(displacement);
|
||||
REQUIRE(operation.HasVariationData());
|
||||
mfem::Vector repeatedAction;
|
||||
mfem::Vector repeatedVariation;
|
||||
operation.Mult(density, repeatedAction);
|
||||
operation.MultDisplacementVariationTrue(densityTrue, directionTrue, repeatedVariation);
|
||||
CHECK(prepared_test::relative_error(repeatedAction, baselineAction, communicator) < 2.0e-14);
|
||||
CHECK(prepared_test::relative_error(repeatedVariation, baselineVariation, communicator) < 2.0e-14);
|
||||
|
||||
const auto rejected = operation.TryPrepare(operation.GetDisplacementMap().gather(make_folding_displacement(f)));
|
||||
REQUIRE_FALSE(rejected.has_value());
|
||||
CHECK_FALSE(operation.IsPrepared());
|
||||
CHECK_FALSE(operation.HasVariationData());
|
||||
REQUIRE(operation.TryPrepare(displacement).has_value());
|
||||
REQUIRE(operation.HasVariationData());
|
||||
operation.Mult(density, repeatedAction);
|
||||
operation.MultDisplacementVariationTrue(densityTrue, directionTrue, repeatedVariation);
|
||||
CHECK(prepared_test::relative_error(repeatedAction, baselineAction, communicator) < 2.0e-14);
|
||||
CHECK(prepared_test::relative_error(repeatedVariation, baselineVariation, communicator) < 2.0e-14);
|
||||
CHECK(operation.GetPreparationCount() == 4);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Prepared Mapped Gravity Source Matches Stateless Kernel",
|
||||
tags::gravity_prepared
|
||||
|
||||
@@ -30,6 +30,61 @@ namespace {
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE(
|
||||
"Prepared Hdiv Geometry Variation Preserves Reference Piola Contractions",
|
||||
tags::gravity_prepared_jacobian_accuracy
|
||||
) {
|
||||
auto args = test_utils::setup_args();
|
||||
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
|
||||
REQUIRE(f.okay());
|
||||
|
||||
operators::PreparedMappedHDivMassOperator preparedOperator(f, *f.domainMapperStateless);
|
||||
const MPI_Comm communicator = f.gravityFluxFes->GetComm();
|
||||
const mfem::Vector first = prepared_test::make_deterministic_vector(f.gravityFluxFes->GetTrueVSize(), 0.31);
|
||||
const mfem::Vector second = prepared_test::make_deterministic_vector(f.gravityFluxFes->GetTrueVSize(), 0.79);
|
||||
const mfem::Vector direction = prepared_test::make_displacement(f, 0.43);
|
||||
mfem::Vector firstAction;
|
||||
|
||||
bool hasStellar = false;
|
||||
bool hasVacuum = false;
|
||||
for (int element = 0; element < f.mesh->GetNE(); ++element) {
|
||||
const bool vacuum = f.domainMapperStateless->IsCompactifiedElement(*f.mesh->GetElementTransformation(element));
|
||||
hasVacuum = hasVacuum || vacuum;
|
||||
hasStellar = hasStellar || !vacuum;
|
||||
}
|
||||
const int localDomains[2]{hasStellar ? 1 : 0, hasVacuum ? 1 : 0};
|
||||
int globalDomains[2]{};
|
||||
REQUIRE(MPI_Allreduce(localDomains, globalDomains, 2, MPI_INT, MPI_MAX, communicator) == MPI_SUCCESS);
|
||||
REQUIRE(globalDomains[0] != 0);
|
||||
REQUIRE(globalDomains[1] != 0);
|
||||
|
||||
// The stateless path still constructs each physically mapped RT basis;
|
||||
// compare both an undeformed and a changed prepared geometry, including
|
||||
// Kelvin exterior elements, against the compact forward/dual contractions.
|
||||
for (const double scale : {0.0, 0.7}) {
|
||||
const mfem::Vector displacementTrue = prepared_test::make_displacement(f, scale);
|
||||
const mfem::Vector displacement = preparedOperator.GetDisplacementMap().gather(displacementTrue);
|
||||
preparedOperator.Prepare(displacement);
|
||||
preparedOperator.MultDisplacementVariationTrue(first, direction, firstAction);
|
||||
|
||||
mfem::Vector referenceAction;
|
||||
operators::kernels::apply_mapped_hdiv_mass_variation(
|
||||
f, *f.domainMapperStateless, first, displacementTrue, direction, referenceAction
|
||||
);
|
||||
const double error = prepared_test::relative_error(firstAction, referenceAction, communicator);
|
||||
INFO("Deformation scale = " << scale);
|
||||
INFO("Prepared/stateless geometry-variation relative error = " << error);
|
||||
CHECK(error < 2.0e-11);
|
||||
}
|
||||
|
||||
mfem::Vector secondAction;
|
||||
preparedOperator.MultDisplacementVariationTrue(second, direction, secondAction);
|
||||
const double firstSecond = prepared_test::global_dot(first, secondAction, communicator);
|
||||
const double secondFirst = prepared_test::global_dot(second, firstAction, communicator);
|
||||
CHECK(prepared_test::relative_scalar_error(firstSecond, secondFirst) < 2.0e-11);
|
||||
CHECK(preparedOperator.GetPreparationCount() == 2);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Prepared Mapped Hdiv Mass Reports Invalid Candidate Geometry Without Unwinding",
|
||||
tags::gravity_prepared_unit &tags::geometry
|
||||
|
||||
@@ -76,17 +76,20 @@ namespace stellar_solver_architecture_test {
|
||||
std::shared_ptr<LifetimeProbe> probe;
|
||||
double correctionValue{0.0};
|
||||
bool resizeCorrection{false};
|
||||
bool surfaceCorrectionOnly{false};
|
||||
|
||||
ScriptedBackend() = default;
|
||||
|
||||
explicit ScriptedBackend(
|
||||
std::shared_ptr<LifetimeProbe> lifetimeProbe,
|
||||
const double scriptedCorrectionValue = 0.0,
|
||||
const bool resizeScriptedCorrection = false
|
||||
const double scriptedCorrectionValue = 0.0,
|
||||
const bool resizeScriptedCorrection = false,
|
||||
const bool scriptOnlySurfaceCorrection = false
|
||||
)
|
||||
: probe(std::move(lifetimeProbe)),
|
||||
correctionValue(scriptedCorrectionValue),
|
||||
resizeCorrection(resizeScriptedCorrection) {
|
||||
resizeCorrection(resizeScriptedCorrection),
|
||||
surfaceCorrectionOnly(scriptOnlySurfaceCorrection) {
|
||||
}
|
||||
};
|
||||
|
||||
@@ -98,14 +101,16 @@ namespace stellar_solver_architecture_test {
|
||||
const MPI_Comm communicator,
|
||||
std::shared_ptr<LifetimeProbe> probe,
|
||||
const double correctionValue,
|
||||
const bool resizeCorrection
|
||||
const bool resizeCorrection,
|
||||
const bool surfaceCorrectionOnly
|
||||
)
|
||||
: m_operation(std::addressof(operation)),
|
||||
m_preconditioner(std::addressof(preconditioner)),
|
||||
m_communicator(communicator),
|
||||
m_probe(std::move(probe)),
|
||||
m_correctionValue(correctionValue),
|
||||
m_resizeCorrection(resizeCorrection) {
|
||||
m_resizeCorrection(resizeCorrection),
|
||||
m_surfaceCorrectionOnly(surfaceCorrectionOnly) {
|
||||
if (m_probe != nullptr) {
|
||||
m_probe->problemIdentity = std::addressof(operation.GetProblem());
|
||||
}
|
||||
@@ -181,7 +186,17 @@ namespace stellar_solver_architecture_test {
|
||||
m_probe->incomingCorrectionNorms.push_back(GlobalNorm(correction));
|
||||
}
|
||||
|
||||
correction = m_correctionValue;
|
||||
if (m_surfaceCorrectionOnly) {
|
||||
mfem::Vector physicalCorrection(CorrectionSize());
|
||||
physicalCorrection = 0.0;
|
||||
auto physicalDirection = m_operation->GetProblem().GetManifest().stateView(physicalCorrection);
|
||||
mfem::Vector surfaceDirection =
|
||||
physicalDirection.block(mean_field::utils::blocks::surface_deformation_field.parameters_term);
|
||||
surfaceDirection = m_correctionValue;
|
||||
m_operation->NormalizeState(physicalCorrection, correction);
|
||||
} else {
|
||||
correction = m_correctionValue;
|
||||
}
|
||||
if (m_probe != nullptr) {
|
||||
m_probe->returnedCorrectionNorms.push_back(GlobalNorm(correction));
|
||||
}
|
||||
@@ -226,6 +241,7 @@ namespace stellar_solver_architecture_test {
|
||||
std::shared_ptr<LifetimeProbe> m_probe;
|
||||
double m_correctionValue;
|
||||
bool m_resizeCorrection;
|
||||
bool m_surfaceCorrectionOnly;
|
||||
};
|
||||
|
||||
template <
|
||||
@@ -243,7 +259,8 @@ namespace stellar_solver_architecture_test {
|
||||
communicator,
|
||||
std::move(configuration.probe),
|
||||
configuration.correctionValue,
|
||||
configuration.resizeCorrection
|
||||
configuration.resizeCorrection,
|
||||
configuration.surfaceCorrectionOnly
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1013,11 +1030,14 @@ TEST_CASE(
|
||||
event.normalizedState.begin(), event.normalizedState.end()
|
||||
);
|
||||
CHECK(event.iterationSeconds >= 0.0);
|
||||
CHECK(event.geometryPreflightSeconds >= 0.0);
|
||||
CHECK(event.lineSearchSeconds >= 0.0);
|
||||
CHECK(event.trialPreparationSeconds >= 0.0);
|
||||
CHECK(event.metricEvaluationSeconds >= 0.0);
|
||||
CHECK(event.preconditionerRefreshSeconds >= 0.0);
|
||||
CHECK(event.rollbackSeconds >= 0.0);
|
||||
REQUIRE(event.geometryPreflight.has_value());
|
||||
CHECK(event.geometryPreflight->sampledQuadraturePointCount > 0);
|
||||
}
|
||||
);
|
||||
auto newton = solver::nonlinear::Newton(
|
||||
@@ -1050,11 +1070,14 @@ TEST_CASE(
|
||||
CHECK(report.diagnostics().nonFiniteLineSearchTrials == 0);
|
||||
CHECK(report.diagnostics().insufficientDecreaseTrials == 2);
|
||||
CHECK(report.diagnostics().totalLinearSolveSeconds >= 0.0);
|
||||
CHECK(report.diagnostics().totalGeometryPreflightSeconds >= 0.0);
|
||||
CHECK(report.diagnostics().totalLineSearchSeconds >= 0.0);
|
||||
CHECK(report.diagnostics().totalTrialPreparationSeconds >= 0.0);
|
||||
CHECK(report.diagnostics().totalMetricEvaluationSeconds >= 0.0);
|
||||
CHECK(report.diagnostics().totalPreconditionerRefreshSeconds >= 0.0);
|
||||
CHECK(report.diagnostics().totalRollbackSeconds >= 0.0);
|
||||
REQUIRE(report.diagnostics().lastGeometryPreflight.has_value());
|
||||
CHECK(report.diagnostics().lastGeometryPreflight->sampledQuadraturePointCount > 0);
|
||||
CHECK(metricState->next == metricState->evaluations.size());
|
||||
REQUIRE(observerRecord->beforeCalls == 2);
|
||||
REQUIRE(observerRecord->afterCalls == 2);
|
||||
@@ -1105,6 +1128,82 @@ TEST_CASE(
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Newton Geometry Preflight Caps A Surface Step Before Trial Preparation",
|
||||
"[solver][newton][geometry][preflight][backtracking][wiring]"
|
||||
) {
|
||||
using namespace mean_field;
|
||||
using namespace stellar_solver_architecture_test;
|
||||
using Catch::Approx;
|
||||
|
||||
auto finiteElements = makeFiniteElements();
|
||||
REQUIRE(finiteElements.okay());
|
||||
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, -10.0, false, true}
|
||||
);
|
||||
|
||||
auto metricState = std::make_shared<MetricSequenceState>();
|
||||
metricState->evaluations = {{.residualNorm = 2.0, .merit = 2.0}, {.residualNorm = 1.0, .merit = 0.5}};
|
||||
std::optional<deformation::LargestSafeNewtonStepSizeEstimate> observedPreflight;
|
||||
auto observer = solver::nonlinear::makeObserver(
|
||||
[](const solver::nonlinear::BeforeIteration &) { },
|
||||
[&observedPreflight](const solver::nonlinear::AfterIteration &event) {
|
||||
observedPreflight = event.geometryPreflight;
|
||||
CHECK(event.geometryPreflightSeconds >= 0.0);
|
||||
CHECK(event.stepAccepted);
|
||||
CHECK(event.lineSearchTrials == 1);
|
||||
}
|
||||
);
|
||||
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 =
|
||||
{.initialStepLength = 1.0,
|
||||
.contractionFactor = 0.5,
|
||||
.fractionToBoundarySafety = 0.5,
|
||||
.sufficientDecrease = 1.0e-4,
|
||||
.minimumStepLength = 1.0e-8,
|
||||
.maximumTrials = 1}
|
||||
},
|
||||
SequencedMetric{metricState}
|
||||
);
|
||||
|
||||
auto equilibriumSolver = solver::make(context, std::move(newton), std::move(observer));
|
||||
const auto report = equilibriumSolver.evaluate();
|
||||
|
||||
REQUIRE_FALSE(report.converged());
|
||||
CHECK(report.failure().reason == solver::StellarEquilibriumFailureReason::iteration_limit);
|
||||
CHECK(report.diagnostics().attemptedNonlinearIterations == 1);
|
||||
CHECK(report.diagnostics().acceptedNonlinearIterations == 1);
|
||||
CHECK(report.diagnostics().totalLineSearchTrials == 1);
|
||||
CHECK(report.diagnostics().inadmissibleLineSearchTrials == 0);
|
||||
CHECK(report.diagnostics().geometryLimitedIterations == 1);
|
||||
REQUIRE(report.diagnostics().lastGeometryPreflight.has_value());
|
||||
const auto &preflight = *report.diagnostics().lastGeometryPreflight;
|
||||
CHECK(preflight.limitedByGeometry);
|
||||
CHECK(preflight.sampledQuadraturePointCount > 0);
|
||||
CHECK(preflight.minimumDeterminantAtAcceptedState > 0.0);
|
||||
CHECK(preflight.minimumDeterminantAtMaximumStepSize <= 0.0);
|
||||
CHECK(preflight.stepSize > 0.0);
|
||||
CHECK(preflight.stepSize < 1.0);
|
||||
CHECK(preflight.stepSize == Approx(0.5 * preflight.boundaryStepSize));
|
||||
CHECK(report.diagnostics().lastAcceptedStepLength == Approx(preflight.stepSize));
|
||||
REQUIRE(observedPreflight.has_value());
|
||||
CHECK(observedPreflight->stepSize == Approx(preflight.stepSize));
|
||||
CHECK(report.lastAcceptedCheckpointView().valid());
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"An Accepted Final Newton Step Reports The Iteration Limit To Its Observer",
|
||||
"[solver][newton][iteration-limit][observer][checkpoint]"
|
||||
|
||||
Reference in New Issue
Block a user