Files
MeanField_SERiF/tests/equations/closure_prepared.cpp
Emily Boudreaux cb20faeb3d test(tests): added test suite
The test suite is extensive. Note that LLMs were used to aid in the development of the test suite. All LLM generated code was manually reviewed.
2026-11-09 13:53:17 -05:00

151 lines
8.2 KiB
C++

// Prepared-state contracts ported from MeanField's prepared closure and context tests.
#include <limits>
#include <catch2/catch_test_macros.hpp>
#include "serif/tests/equations/fixture.hpp"
#include "serif/tests/test_tags.hpp"
namespace serif::tests::equations {
TEST_CASE("Prepared Equations Use Supported Field Dimensions", tags::unit & tags::equations) {
Fixture f;
PreparedClosure prepared(
Closure{eos::models::Polytrope{3.0, 1.5}}, f.density_binding, f.displacement_binding, f.compactification,
f.mapper
);
CHECK_FALSE(prepared.is_prepared());
CHECK(prepared.preparation_count() == 0);
CHECK(f.density_binding.size() > 0);
CHECK(f.density_binding.size() < f.density_space.GetTrueVSize());
CHECK(f.enthalpy_binding.size() < f.enthalpy_space.GetTrueVSize());
CHECK(f.displacement_binding.size() == f.displacement_space.GetTrueVSize());
CHECK(prepared.height() == f.density_binding.size());
const auto rho = f.density(), h = f.enthalpy(), u = f.displacement(0.0);
prepared.prepare(f.state(rho, h), u, f.workspace);
CHECK(prepared.width() == rho.Size() + h.Size() + u.Size());
const auto expanded = f.density_binding.scatter(rho);
CHECK(relative_error(f.density_binding.gather(expanded), rho) == 0.0);
}
TEST_CASE("Prepared Equations Reject EOS Candidates And Recover", tags::unit & tags::equations) {
Fixture f;
const auto rho = f.density(), u = f.displacement(0.0);
auto h = f.enthalpy();
PreparedClosure prepared(
Closure{eos::models::Polytrope{3.0, 1.5}}, f.density_binding, f.displacement_binding, f.compactification,
f.mapper
);
prepared.prepare(f.state(rho, h), u, f.workspace);
for (const double bad_value : {-1.0, 1.0e150}) {
CAPTURE(bad_value);
h = bad_value;
const auto rejected = prepared.try_prepare(f.state(rho, h), u, f.workspace);
REQUIRE_FALSE(rejected.has_value());
CHECK(rejected.error().reason == backend::PreparationRejectionReason::EQUATION);
CHECK(rejected.error().element >= 0);
CHECK(rejected.error().quadrature_point >= 0);
CHECK_FALSE(prepared.is_prepared());
::mfem::Vector result;
CHECK_THROWS_AS(prepared.residual(f.workspace, result), std::logic_error);
CHECK_THROWS_AS(prepared.prepare(f.state(rho, h), u, f.workspace), std::domain_error);
}
h = 1.0;
REQUIRE(prepared.try_prepare(f.state(rho, h), u, f.workspace).has_value());
CHECK(prepared.preparation_count() == 2);
}
TEST_CASE("Prepared Equations Reject Folding Geometry And Recover", tags::unit & tags::equations) {
Fixture f;
const auto rho = f.density(), h = f.enthalpy();
::mfem::GridFunction folded(&f.displacement_space);
::mfem::VectorFunctionCoefficient coefficient(3, [](const ::mfem::Vector& x, ::mfem::Vector& u) {
u.SetSize(3);
u = 0.0;
u[0] = -2.0 * x[0];
});
folded.ProjectCoefficient(coefficient);
auto u = f.displacement_binding.gather(folded);
PreparedClosure prepared(
Closure{eos::models::Polytrope{3.0, 1.5}}, f.density_binding, f.displacement_binding, f.compactification,
f.mapper
);
const auto rejected = prepared.try_prepare(f.state(rho, h), u, f.workspace);
REQUIRE_FALSE(rejected.has_value());
CHECK(rejected.error().reason == backend::PreparationRejectionReason::MAPPING);
CHECK(rejected.error().mapping_status == mapping::MappingStatus::NON_POSITIVE_DETERMINATE);
CHECK_FALSE(prepared.is_prepared());
CHECK_THROWS_AS(prepared.prepare(f.state(rho, h), u, f.workspace), std::domain_error);
u = 0.0;
CHECK(prepared.try_prepare(f.state(rho, h), u, f.workspace).has_value());
}
TEST_CASE("Prepared Closure Matches Manual Residual And Every Jacobian Column", tags::integration & tags::equations) {
for (const auto geometry : {::mfem::Element::HEXAHEDRON, ::mfem::Element::TETRAHEDRON}) {
CAPTURE(geometry);
Fixture f(geometry);
const eos::models::Polytrope eos(3.0, 1.5);
const auto rho = f.density(), h = f.enthalpy(), u = f.displacement(1.0);
const auto drho = deterministic(rho.Size(), 0.43), dh = deterministic(h.Size(), 0.52),
du = f.displacement(0.63);
const auto zero_rho = constant(rho.Size(), 0.0), zero_h = constant(h.Size(), 0.0),
zero_u = constant(u.Size(), 0.0);
PreparedClosure prepared(Closure{eos}, f.density_binding, f.displacement_binding, f.compactification, f.mapper);
prepared.prepare(f.state(rho, h), u, f.workspace);
::mfem::Vector residual, density_action, enthalpy_action, geometry_action, complete, packed_action;
prepared.residual(f.workspace, residual);
prepared.apply_jacobian(f.state(drho, zero_h), zero_u, f.workspace, density_action);
prepared.apply_jacobian(f.state(zero_rho, dh), zero_u, f.workspace, enthalpy_action);
prepared.apply_jacobian(f.state(zero_rho, zero_h), du, f.workspace, geometry_action);
prepared.apply_jacobian(f.state(drho, dh), du, f.workspace, complete);
backend::JacobianOperator jacobian(prepared.cache());
jacobian.Mult(packed(drho, dh, du), packed_action);
CHECK(relative_error(residual, manual_closure(f, eos, rho, h, u)) < 2.0e-12);
CHECK(relative_error(density_action, manual_closure(f, eos, rho, h, u, &drho)) < 2.0e-12);
CHECK(relative_error(enthalpy_action, manual_closure(f, eos, rho, h, u, nullptr, &dh)) < 2.0e-12);
CHECK(relative_error(geometry_action, manual_closure(f, eos, rho, h, u, nullptr, nullptr, &du)) < 2.0e-12);
density_action += enthalpy_action;
density_action += geometry_action;
CHECK(relative_error(complete, density_action) < 2.0e-12);
CHECK(relative_error(packed_action, complete) < 2.0e-12);
CHECK(prepared.preparation_count() == 1);
}
}
TEST_CASE("Prepared Equations Own Frozen Numerical State And Reprepare Every Dependency", tags::unit & tags::equations) {
Fixture f;
const eos::models::Polytrope eos(3.0, 1.5);
auto rho = f.density(), h = f.enthalpy(), u = f.displacement(0.35);
const auto drho = deterministic(rho.Size(), 0.12), dh = deterministic(h.Size(), 0.53),
du = f.displacement(0.63);
PreparedClosure prepared(Closure{eos}, f.density_binding, f.displacement_binding, f.compactification, f.mapper);
prepared.prepare(f.state(rho, h), u, f.workspace);
::mfem::Vector frozen, frozen_action;
prepared.residual(f.workspace, frozen);
prepared.apply_jacobian(f.state(drho, dh), du, f.workspace, frozen_action);
for (int dependency = 0; dependency < 3; ++dependency) {
CAPTURE(dependency);
if (dependency == 0) {
rho = f.density(1.0);
} else if (dependency == 1) {
h = f.enthalpy(1.0);
} else {
u = f.displacement(0.85);
}
::mfem::Vector unchanged, unchanged_action;
prepared.residual(f.workspace, unchanged);
prepared.apply_jacobian(f.state(drho, dh), du, f.workspace, unchanged_action);
CHECK(relative_error(unchanged, frozen) == 0.0);
CHECK(relative_error(unchanged_action, frozen_action) == 0.0);
prepared.prepare(f.state(rho, h), u, f.workspace);
prepared.residual(f.workspace, frozen);
prepared.apply_jacobian(f.state(drho, dh), du, f.workspace, frozen_action);
CHECK(relative_error(frozen, manual_closure(f, eos, rho, h, u)) < 2.0e-12);
CHECK(relative_error(frozen_action, manual_closure(f, eos, rho, h, u, &drho, &dh, &du)) < 2.0e-12);
}
CHECK(prepared.preparation_count() == 4);
prepared.invalidate();
CHECK_FALSE(prepared.is_prepared());
CHECK_THROWS_AS(prepared.residual(f.workspace, frozen), std::logic_error);
}
} // namespace serif::tests::equations