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.
This commit is contained in:
2026-11-09 13:53:17 -05:00
parent 74e55a6859
commit cb20faeb3d
202 changed files with 35505 additions and 953 deletions

View File

@@ -0,0 +1,198 @@
#include <catch2/catch_approx.hpp>
#include <catch2/catch_test_macros.hpp>
#include <memory>
#include <tuple>
#include <type_traits>
#include <variant>
#include "serif/backend/mfem/model/volume_displacement.hpp"
#include "serif/tests/equations/test_equations.hpp"
#include "serif/equations/mass_normalization.hpp"
#include "serif/tests/equations/fixture.hpp"
namespace
{
using namespace serif;
using namespace serif::tests::equations;
namespace equations_backend = serif::operators::backend::mfem;
namespace dependencies = utils::dependencies;
using Status = dependencies::PreparationStatus;
using Mass = serif::equations::MassNormalization;
struct FirstClosure
{
};
struct SecondClosure
{
};
struct MassRow
{
};
using First = equations_backend::volume_keys::Scoped<FirstClosure>;
using Second = equations_backend::volume_keys::Scoped<SecondClosure>;
using MassStages = equations_backend::volume_keys::Scoped<MassRow>;
/** @brief Reuse the ordinary fixture bindings without introducing an equation-specific owner. */
auto closure_discretization(const Fixture& fixture)
{
return equations_backend::VolumeDiscretization{
.residual_binding = fixture.density_binding,
.displacement_binding = fixture.displacement_binding,
.compactification = fixture.compactification,
.mapper = fixture.mapper};
}
/** @brief Compose declarations once and retain the ordinary dependency-cache failure type. */
template <typename Context, typename Nodes>
auto make_cache(const Context& context, Nodes nodes)
{
return std::apply([&](auto... node)
{
return dependencies::make_computation_cache<equations_backend::PreparationRejection>(context, std::move(node)...);
},
std::move(nodes));
}
} // namespace
TEST_CASE("Scoped instances of one volume equation share fields without sharing private stages", "[unit][equations][scoped_computations]")
{
Fixture fixture;
const auto discretization = closure_discretization(fixture);
auto density = fixture.density();
const auto enthalpy = fixture.enthalpy();
const auto displacement = fixture.displacement(0.03);
const eos::models::Polytrope first_eos{1.5, 0.25};
const eos::models::Polytrope second_eos{3.0, 1.5};
const auto inputs = [&]()
{
const auto coefficients = fixture.state(density, enthalpy);
const auto first = equations_backend::make_volume_inputs<Closure, FirstClosure>(discretization, coefficients, displacement);
const auto second = equations_backend::make_volume_inputs<Closure, SecondClosure>(discretization, coefficients, displacement);
return dependencies::make_dependency_context(
first.resource<First::Bindings>(), second.resource<Second::Bindings>(),
first.resource<equations_backend::DisplacementCoefficients>(), first.resource<equations_backend::CompactificationCoefficients>(),
first.resource<blocks::density::mass>(), first.resource<::serif::tests::equations::Enthalpy>());
};
auto cache = make_cache(inputs(), std::tuple_cat(
equations_backend::make_volume_equation_computations<FirstClosure>(Closure{first_eos}, discretization),
equations_backend::make_volume_equation_computations<SecondClosure>(Closure{second_eos}, discretization)));
STATIC_CHECK_FALSE(std::same_as<First::Linearization, Second::Linearization>);
REQUIRE(cache.try_prepare(inputs()));
CHECK(relative_error(cache.get<First::Linearization>().residual, manual_closure(fixture, first_eos, density, enthalpy, displacement)) < 2.0e-12);
CHECK(relative_error(cache.get<Second::Linearization>().residual, manual_closure(fixture, second_eos, density, enthalpy, displacement)) < 2.0e-12);
const auto density_direction = deterministic(density.Size(), 0.3);
const auto enthalpy_direction = deterministic(enthalpy.Size(), 0.7);
const auto displacement_direction = fixture.displacement(0.1);
const auto direction = packed(density_direction, enthalpy_direction, displacement_direction);
const auto dual = deterministic(density.Size(), 0.9);
mfem::Vector action, transpose;
equations_backend::Workspace workspace;
equations_backend::apply_jacobian(cache.get<Second::Linearization>(), direction, workspace, action);
CHECK(relative_error(action, manual_closure(fixture, second_eos, density, enthalpy, displacement, &density_direction, &enthalpy_direction, &displacement_direction)) < 2.0e-12);
equations_backend::apply_jacobian_transpose(cache.get<Second::Linearization>(), dual, workspace, transpose);
CHECK((action * dual) == Catch::Approx(direction * transpose).epsilon(2.0e-12));
const auto frozen = cache.share<Second::Linearization>();
REQUIRE(cache.try_prepare(inputs()));
CHECK(cache.report().computed_count() == 0);
CHECK(cache.share<Second::Linearization>() == frozen);
density += 0.04;
REQUIRE(cache.try_prepare(inputs(), dependencies::RequestedResults<First::Linearization>{}));
CHECK(cache.report().status<First::Basis<0>>() == Status::reused);
CHECK(cache.report().status<First::QuadratureLinearization<0>>() == Status::computed);
CHECK_FALSE(cache.contains<Second::Linearization>());
CHECK(equations_backend::volume_work_report<FirstClosure>(cache).physics_points > 0);
REQUIRE(cache.try_prepare(inputs()));
CHECK(cache.report().status<First::Linearization>() == Status::reused);
CHECK(cache.report().status<Second::QuadratureLinearization<0>>() == Status::computed);
CHECK(cache.share<Second::Linearization>() != frozen);
CHECK(relative_error(cache.get<Second::Linearization>().residual, manual_closure(fixture, second_eos, density, enthalpy, displacement)) < 2.0e-12);
}
TEST_CASE("Distinct volume equations prepare in one graph from common coefficient sources", "[unit][equations][scoped_computations]")
{
Fixture fixture;
const eos::models::Polytrope eos{1.5, 0.25};
auto density = fixture.density();
auto enthalpy = fixture.enthalpy();
auto displacement = fixture.displacement(0.03);
const auto closure = closure_discretization(fixture);
const equations_backend::VolumeDiscretization mass{
.residual_binding = fixture.constant_binding,
.displacement_binding = fixture.displacement_binding,
.compactification = fixture.compactification,
.mapper = fixture.mapper};
const auto coefficients = [&]()
{
return equations_backend::make_term_coefficients(equations_backend::bind(fixture.density_binding, density), equations_backend::bind(fixture.enthalpy_binding, enthalpy));
};
const auto inputs = [&]()
{
const auto state = coefficients();
const auto volume = equations_backend::make_volume_inputs<Closure, FirstClosure>(closure, state, displacement);
const auto integral = equations_backend::make_volume_inputs<Mass, MassRow>(mass, state, displacement);
return dependencies::make_dependency_context(
volume.resource<First::Bindings>(), integral.resource<MassStages::Bindings>(),
volume.resource<equations_backend::DisplacementCoefficients>(), volume.resource<equations_backend::CompactificationCoefficients>(),
volume.resource<blocks::density::mass>(), volume.resource<::serif::tests::equations::Enthalpy>());
};
auto cache = make_cache(inputs(), std::tuple_cat(
equations_backend::make_volume_equation_computations<FirstClosure>(Closure{eos}, closure),
equations_backend::make_volume_equation_computations<MassRow>(Mass{0.8}, mass)));
REQUIRE(cache.try_prepare(inputs()));
const auto frozen_volume = cache.share<First::Linearization>();
REQUIRE(cache.try_prepare(inputs()));
CHECK(cache.report().computed_count() == 0);
enthalpy += 0.03;
REQUIRE(cache.try_prepare(inputs()));
CHECK(cache.report().status<First::Linearization>() == Status::computed);
CHECK(cache.report().status<MassStages::Linearization>() == Status::reused);
density += 0.02;
REQUIRE(cache.try_prepare(inputs()));
CHECK(cache.report().status<MassStages::Linearization>() == Status::computed);
enthalpy = -1.0;
const auto rejected = cache.try_prepare(inputs());
REQUIRE_FALSE(rejected);
CHECK_FALSE(cache.ready());
CHECK_FALSE(cache.contains<MassStages::Linearization>());
CHECK(frozen_volume->residual.Size() == fixture.density_binding.size());
}
TEST_CASE("Scoped equation metadata connects the shared deformation displacement source", "[unit][equations][scoped_computations]")
{
namespace deformation = serif::geometry::deformation;
namespace deformation_backend = deformation::backend::mfem;
using Failure = std::variant<equations_backend::PreparationRejection, deformation::DeformationFailure>;
Fixture fixture;
const auto discretization = closure_discretization(fixture);
const auto density = fixture.density();
const auto enthalpy = fixture.enthalpy();
const auto displacement = fixture.displacement(0.03);
std::vector<double> generated(displacement.GetData(), displacement.GetData() + displacement.Size());
const auto reference = deformation_backend::deformation_discretization(fixture.enthalpy_space, fixture.displacement_space, fixture.mesh);
const auto volume = equations_backend::make_volume_inputs<Closure, FirstClosure>(discretization, fixture.state(density, enthalpy));
const auto context = dependencies::make_dependency_context(
volume.resource<First::Bindings>(), volume.resource<equations_backend::CompactificationCoefficients>(),
volume.resource<blocks::density::mass>(), volume.resource<::serif::tests::equations::Enthalpy>(),
dependencies::own<deformation::keys::ReferenceDiscretization>(reference, deformation_backend::ReferenceDiscretizationTracking{}),
dependencies::borrow<deformation::keys::VolumeDisplacement>(generated));
const eos::models::Polytrope eos{1.5, 0.25};
auto cache = std::apply([&](auto... node)
{
return dependencies::make_computation_cache<Failure>(context, std::move(node)...);
}, std::tuple_cat(
deformation_backend::make_volume_displacement_computations<First::Bindings>(fixture.displacement_binding),
equations_backend::make_volume_equation_computations<FirstClosure>(Closure{eos}, discretization)));
REQUIRE(cache.try_prepare(context));
CHECK(relative_error(cache.get<equations_backend::DisplacementCoefficients>().values, displacement) == 0.0);
CHECK(relative_error(cache.get<First::Linearization>().residual, manual_closure(fixture, eos, density, enthalpy, displacement)) < 2.0e-12);
REQUIRE(cache.try_prepare(context));
CHECK(cache.report().computed_count() == 0);
generated[0] += 0.001;
REQUIRE(cache.try_prepare(context));
CHECK(cache.report().status<deformation_backend::volume_displacement_keys::CheckedBinding>() == Status::reused);
CHECK(cache.report().status<First::Geometry<0>>() == Status::computed);
}