feat(newton): first newton solver implementation

This commit is contained in:
2026-09-08 06:36:39 -04:00
parent 76818f2f82
commit b3c04d507a
98 changed files with 20397 additions and 11040 deletions

View File

@@ -3,6 +3,7 @@
#include <cmath>
#include <limits>
#include <memory>
#include <stdexcept>
#include <catch2/catch_test_macros.hpp>
#include <mfem.hpp>
@@ -51,6 +52,26 @@ namespace prepared_pressure_force_test_utils {
}
};
[[nodiscard]] mfem::Vector make_affine_displacement(
const mean_field::fem::FEM &f,
const double scale
) {
mfem::ParGridFunction field(f.displacementFes.get());
mfem::VectorFunctionCoefficient coefficient(
f.mesh->Dimension(), [scale](const mfem::Vector &position, mfem::Vector &value) {
value.SetSize(position.Size());
for (int dimension = 0; dimension < position.Size(); ++dimension) {
value(dimension) = scale * position(dimension);
}
}
);
field.ProjectCoefficient(coefficient);
mfem::Vector result;
field.GetTrueDofs(result);
return result;
}
[[nodiscard]]
mfem::Vector make_positive_enthalpy_true(
const mean_field::fem::FEM &f,
@@ -265,6 +286,97 @@ TEST_CASE(
);
}
TEST_CASE(
"Prepared Pressure Force Reports Expected EOS Rejections Without Unwinding",
tags::barotrope &tags::pressure &tags::prepared &tags::unit
) {
mean_field::utils::Args args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
REQUIRE(f.okay());
const prepared_pressure_force_test_utils::Maps maps(f);
const mean_field::eos::Polytrope equationOfState(3.0, 0.25);
mean_field::operators::PreparedPressureForceOperator preparedOperator(f, *f.domainMapperStateless, equationOfState);
mfem::Vector enthalpy(maps.enthalpy.reduced_size());
mfem::Vector displacement(maps.displacement.reduced_size());
enthalpy = -1.0;
displacement = 0.0;
auto dependencies = prepared_pressure_force_test_utils::make_dependencies();
const auto outsideDomain =
preparedOperator.TryPrepare({.enthalpy = enthalpy, .displacement = displacement}, dependencies);
REQUIRE_FALSE(outsideDomain.has_value());
CHECK(
outsideDomain.error().reason ==
mean_field::operators::PressureForcePreparationRejectionReason::equation_of_state
);
CHECK(outsideDomain.error().equationOfStateCode == mean_field::eos::EvaluationErrorCode::outside_domain);
CHECK_FALSE(preparedOperator.IsPrepared());
try {
(void)preparedOperator.Prepare({.enthalpy = enthalpy, .displacement = displacement}, dependencies);
FAIL("The compatibility Prepare overload accepted an out-of-domain EOS input.");
} catch (const mean_field::eos::EvaluationError &error) {
CHECK(error.code() == mean_field::eos::EvaluationErrorCode::outside_domain);
}
enthalpy = std::numeric_limits<double>::max();
++dependencies.enthalpy.revision;
const auto rejected =
preparedOperator.TryPrepare({.enthalpy = enthalpy, .displacement = displacement}, dependencies);
REQUIRE_FALSE(rejected.has_value());
CHECK(rejected.error().reason == mean_field::operators::PressureForcePreparationRejectionReason::equation_of_state);
CHECK(rejected.error().equationOfStateCode == mean_field::eos::EvaluationErrorCode::nonfinite_result);
CHECK_FALSE(preparedOperator.IsPrepared());
CHECK_THROWS_AS(
preparedOperator.Prepare({.enthalpy = enthalpy, .displacement = displacement}, dependencies),
mean_field::eos::EvaluationError
);
enthalpy = 1.0;
++dependencies.enthalpy.revision;
const auto accepted =
preparedOperator.TryPrepare({.enthalpy = enthalpy, .displacement = displacement}, dependencies);
REQUIRE(accepted.has_value());
CHECK(preparedOperator.IsPrepared());
}
TEST_CASE(
"Prepared Pressure Force Reports Invalid Candidate Geometry Without Unwinding",
tags::barotrope &tags::pressure &tags::prepared &tags::geometry &tags::unit
) {
mean_field::utils::Args args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
REQUIRE(f.okay());
const prepared_pressure_force_test_utils::Maps maps(f);
const mean_field::eos::Polytrope equationOfState(3.0, 0.25);
mean_field::operators::PreparedPressureForceOperator preparedOperator(f, *f.domainMapperStateless, equationOfState);
mfem::Vector enthalpy(maps.enthalpy.reduced_size());
enthalpy = 1.0;
mfem::Vector displacement =
maps.displacement.gather(prepared_pressure_force_test_utils::make_affine_displacement(f, -2.0));
auto dependencies = prepared_pressure_force_test_utils::make_dependencies();
const auto rejected =
preparedOperator.TryPrepare({.enthalpy = enthalpy, .displacement = displacement}, dependencies);
REQUIRE_FALSE(rejected.has_value());
CHECK(rejected.error().reason == mean_field::operators::PressureForcePreparationRejectionReason::invalid_mapping);
CHECK(rejected.error().mappingStatus == mean_field::mapping::MappingStatus::non_positive_determinant);
CHECK_FALSE(preparedOperator.IsPrepared());
CHECK_THROWS_AS(
preparedOperator.Prepare({.enthalpy = enthalpy, .displacement = displacement}, dependencies), std::domain_error
);
displacement = 0.0;
++dependencies.displacement.revision;
const auto accepted =
preparedOperator.TryPrepare({.enthalpy = enthalpy, .displacement = displacement}, dependencies);
REQUIRE(accepted.has_value());
CHECK(preparedOperator.IsPrepared());
}
TEST_CASE(
"Prepared Pressure Force Jacobian Matches Full Stateless Columns "
"Through FieldDof Restriction",