Files
MeanField_SERiF/tests/equations/closure_conditions.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

122 lines
6.7 KiB
C++

// EOS/geometry conditions and tolerances from MeanField's prepared closure tests.
#include <algorithm>
#include <array>
#include <cmath>
#include <catch2/catch_test_macros.hpp>
#include "serif/tests/equations/fixture.hpp"
#include "serif/tests/test_tags.hpp"
namespace serif::tests::equations {
struct ClosureCondition final {
double n, K, enthalpy_offset, enthalpy_gradient, density_factor, density_offset, density_gradient;
double deformation, direction_phase;
};
constexpr std::array<ClosureCondition, 3> closure_conditions{
{{1.0, 0.8, 0.65, 0.06, 0.80, 0.015, 0.004, 0.0, 0.31}, {1.5, 1.2, 0.90, 0.09, 1.15, -0.003, 0.003, 0.45, 0.53},
{3.0, 1.5, 1.20, 0.12, 1.40, 0.006, 0.002, 1.0, 0.79}}
};
TEST_CASE("Equation Closure Covers Multiple EOS And Geometry Conditions", tags::integration & tags::equations) {
Fixture f;
for (const auto& condition : closure_conditions) {
CAPTURE(condition.n, condition.K, condition.deformation);
const eos::models::Polytrope eos(condition.n, condition.K);
const auto enthalpy_function = [&](const ::mfem::Vector& x) {
return condition.enthalpy_offset +
condition.enthalpy_gradient * (0.50 * x[0] - 0.30 * x[1] + 0.20 * x[2]);
};
const auto h = f.project(f.enthalpy_binding, enthalpy_function);
const auto rho = f.project(f.density_binding, [&](const ::mfem::Vector& x) {
return condition.density_factor * std::pow(enthalpy_function(x) / eos.enthalpy_scale(), condition.n) +
condition.density_offset +
condition.density_gradient * (0.40 * x[0] + 0.25 * x[1] - 0.15 * x[2]);
});
const auto u = f.displacement(condition.deformation);
auto drho = deterministic(rho.Size(), condition.direction_phase);
auto dh = f.project(f.enthalpy_binding, [](const ::mfem::Vector& x) {
return 0.07 + 0.015 * x[0] + 0.008 * x[2];
});
auto du = f.displacement(0.63);
auto density_action = manual_closure(f, eos, rho, h, u, &drho);
auto enthalpy_action = manual_closure(f, eos, rho, h, u, nullptr, &dh);
auto geometry_action = manual_closure(f, eos, rho, h, u, nullptr, nullptr, &du);
const double target =
std::min({density_action.Norml2(), enthalpy_action.Norml2(), geometry_action.Norml2()});
REQUIRE(target > 1.0e-12);
drho *= target / density_action.Norml2();
dh *= target / enthalpy_action.Norml2();
du *= target / geometry_action.Norml2();
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, action;
prepared.residual(f.workspace, residual);
prepared.apply_jacobian(f.state(drho, dh), du, f.workspace, action);
CHECK(relative_error(residual, manual_closure(f, eos, rho, h, u)) < 5.0e-12);
CHECK(relative_error(action, manual_closure(f, eos, rho, h, u, &drho, &dh, &du)) < 5.0e-12);
const int expected_order = std::max(4, 5 + static_cast<int>(std::ceil((condition.n - 1.0) * 3))) + 6;
CHECK(prepared.integration_order(0) >= expected_order);
constexpr double step = 1.0e-5;
auto plus_rho = rho, minus_rho = rho, plus_h = h, minus_h = h, plus_u = u, minus_u = u;
plus_rho.Add(step, drho);
minus_rho.Add(-step, drho);
plus_h.Add(step, dh);
minus_h.Add(-step, dh);
plus_u.Add(step, du);
minus_u.Add(-step, du);
auto difference = manual_closure(f, eos, plus_rho, plus_h, plus_u);
difference -= manual_closure(f, eos, minus_rho, minus_h, minus_u);
difference /= 2.0 * step;
difference -= action;
CHECK(difference.Norml2() / (3.0 * target) < 2.0e-7);
}
}
TEST_CASE("Exact Equation Closure Remains Zero Under Deformation", tags::integration & tags::equations) {
Fixture f;
const auto h = constant(f.enthalpy_binding.size(), 1.20);
const auto rho = constant(f.density_binding.size(), std::pow(1.20 / 6.0, 3.0));
auto reference_rho = rho;
reference_rho += 1.0;
const auto zero_rho = constant(rho.Size(), 0.0), zero_h = constant(h.Size(), 0.0), du = f.displacement(0.67);
for (const double deformation : {0.0, 0.5, 1.0}) {
CAPTURE(deformation);
const auto u = f.displacement(deformation);
PreparedClosure prepared(
Closure{eos::models::Polytrope{3.0, 1.5}}, f.density_binding, f.displacement_binding, f.compactification,
f.mapper
);
::mfem::Vector exact, exact_geometry, reference, reference_geometry;
prepared.prepare(f.state(rho, h), u, f.workspace);
prepared.residual(f.workspace, exact);
prepared.apply_jacobian(f.state(zero_rho, zero_h), du, f.workspace, exact_geometry);
prepared.prepare(f.state(reference_rho, h), u, f.workspace);
prepared.residual(f.workspace, reference);
prepared.apply_jacobian(f.state(zero_rho, zero_h), du, f.workspace, reference_geometry);
REQUIRE(reference.Norml2() > 1.0e-12);
REQUIRE(reference_geometry.Norml2() > 1.0e-14);
CHECK(exact.Norml2() <= 5.0e-12 * reference.Norml2());
CHECK(exact_geometry.Norml2() <= 5.0e-12 * reference_geometry.Norml2());
}
}
TEST_CASE("Equation Quadrature Resolves Both Closure Forms Through The Existing Policy", tags::unit & tags::equations) {
Fixture f;
const auto rho = f.density(), h = f.enthalpy(), u = f.displacement(0.0);
using Mass = discretization::forms::mass<blocks::density::mass, blocks::density::mass>;
using Source = discretization::forms::source<::serif::tests::equations::Enthalpy, blocks::density::mass>;
discretization::quadrature::RuleSet rules;
rules.set_form_control<Mass>({.fixed_order = 18});
rules.set_form_control<Source>({.fixed_order = 8});
PreparedClosure prepared(
Closure{eos::models::Polytrope{1.0, 0.8}}, f.density_binding, f.displacement_binding, f.compactification,
f.mapper, discretization::quadrature::Policy{rules}
);
prepared.prepare(f.state(rho, h), u, f.workspace);
CHECK(prepared.integration_order(0) == ::mfem::IntRules.Get(::mfem::Geometry::CUBE, 18).GetOrder());
CHECK_THROWS_AS(prepared.integration_order(2), std::out_of_range);
}
} // namespace serif::tests::equations