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,119 @@
#include <cmath>
#include <catch2/catch_approx.hpp>
#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_string.hpp>
#include "serif/model/finite_elements.hpp"
#include "serif/backend/mfem/discretization/field_values.hpp"
#include "serif/backend/mfem/serialize/discretization.hpp"
#include "../sandbox/polytropic_model.hpp"
TEST_CASE("Stellar factory selects compatible surface layers and preserves the ordinary default", "[model][reconstruction]") {
namespace reconstruction = serif::discretization::reconstruction;
namespace blocks = serif::discretization::blocks;
namespace model = serif::model;
using Density = blocks::density::mass;
const auto star = serif::examples::make_polytropic_model(4.0 / 3.0);
const auto ordinary = model::make_stellar_discretization(star);
CHECK_FALSE(ordinary.binding<Density>().reconstruction());
const auto identity = model::make_stellar_discretization(star, {}, reconstruction::SurfacePower<Density>{.density_exponent = 1.0});
CHECK_FALSE(identity.binding<Density>().reconstruction());
for (const int equatorial : {0, 2}) {
const auto weighted = model::make_stellar_discretization(star,
{.surface_refinement = {.levels = 1, .equatorial_levels = equatorial, .equatorial_half_angle_degrees = 10.0}},
reconstruction::SurfacePower<Density>{.density_exponent = 4.0 / 3.0,
.region = reconstruction::SurfaceAdjacent<serif::discretization::domain::StellarSurfaceBoundary>{}});
const auto& density = weighted.binding<Density>();
REQUIRE(density.reconstruction());
int selected = 0, ordinary_material = 0;
for (int element = 0; element < density.mesh().GetNE(); ++element) {
if (!density.supports(element)) { continue; }
if (density.reconstruction_element(element)) { ++selected; }
else { ++ordinary_material; }
}
CHECK(selected > 0);
CHECK(ordinary_material > 0);
CHECK_FALSE(weighted.binding<blocks::gravity::potential>().reconstruction());
}
CHECK_THROWS_WITH((model::make_stellar_discretization(star, {}, reconstruction::SurfacePower<blocks::gravity::potential>{})),
Catch::Matchers::ContainsSubstring("only scalar H1 mass density"));
CHECK_THROWS_WITH((model::make_stellar_discretization(star, {}, reconstruction::SurfacePower<Density>{}, reconstruction::SurfacePower<Density>{})),
Catch::Matchers::ContainsSubstring("at most one"));
}
TEST_CASE("Weighted archives preserve between-node density and reject missing interpretation", "[model][reconstruction]") {
namespace reconstruction = serif::discretization::reconstruction;
namespace backend = serif::backend::mfem::serialize;
namespace discretization = serif::discretization::backend::mfem;
using Density = serif::discretization::blocks::density::mass;
const auto star = serif::examples::make_polytropic_model(5.0 / 3.0);
const auto original = serif::model::make_stellar_discretization(star, {}, reconstruction::SurfacePower<Density>{.density_exponent = 5.0 / 3.0});
const auto record = backend::save_discretization(original);
auto restored = backend::restore_discretization<decltype(original)>(record);
INFO((restored ? "restored" : restored.error().message));
REQUIRE(restored);
CHECK(backend::save_discretization(*restored) == record);
const auto& binding = original.binding<Density>();
::mfem::Vector coefficients(binding.size());
for (int i = 0; i < coefficients.Size(); ++i) { coefficients[i] = std::sin(0.13 * i); }
discretization::FieldValues<Density> before(binding, coefficients), after(restored->binding<Density>(), coefficients);
::mfem::IntegrationPoint point;
point.Set3(0.17, 0.31, 0.87);
for (int element = 0; element < binding.mesh().GetNE(); ++element) {
if (!binding.supports(element)) { continue; }
CHECK(before.value(element, point) == Catch::Approx(after.value(element, point)).margin(1e-13));
}
auto malformed = record;
for (auto& [key, saved] : malformed.children.at("bindings").children) {
if (saved.children.contains("reconstruction")) {
CHECK(saved.version == 2);
saved.children.erase("reconstruction");
}
}
CHECK_FALSE(backend::restore_discretization<decltype(original)>(malformed));
}
TEST_CASE("Reconstruction plans reject attachment after the boundary changes", "[model][reconstruction]") {
namespace reconstruction = serif::discretization::reconstruction;
using Density = serif::discretization::blocks::density::mass;
const auto star = serif::examples::make_polytropic_model(1.5);
const auto finite_elements = serif::model::make_stellar_discretization(star, {}, reconstruction::SurfacePower<Density>{.density_exponent = 1.5});
const auto& density = finite_elements.binding<Density>();
const auto plan = density.reconstruction();
auto& mesh = density.mesh();
REQUIRE(mesh.GetNBE() > 0);
mesh.SetBdrAttribute(0, mesh.GetBdrAttribute(0) + 10);
using Binding = std::remove_cvref_t<decltype(density)>;
CHECK_THROWS_WITH(Binding(density.space(), plan), Catch::Matchers::ContainsSubstring("Rebuild the reconstruction plan"));
CHECK_THROWS(density.validate());
}
TEST_CASE("Reconstruction rejects radial hanging layers and stale refined-space plans", "[model][reconstruction]") {
namespace backend = serif::discretization::backend::mfem;
namespace reconstruction = serif::discretization::reconstruction;
using Density = serif::discretization::blocks::density::mass;
using Schema = serif::discretization::domain::schema::CoreEnvelopeVacuumDomainSchema;
auto mesh = ::mfem::Mesh::MakeCartesian3D(2, 1, 1, ::mfem::Element::HEXAHEDRON);
::mfem::Array<int> vertices;
for (int boundary = 0; boundary < mesh.GetNBE(); ++boundary) {
mesh.GetBdrElementVertices(boundary, vertices);
bool surface = true;
for (int vertex : vertices) { surface = surface && mesh.GetVertex(vertex)[2] == 0.0; }
mesh.GetBdrElement(boundary)->SetAttribute(surface ? 1 : 2);
}
mesh.SetAttributes();
::mfem::H1_FECollection collection(3, 3);
::mfem::FiniteElementSpace space(&mesh, &collection);
backend::TermBinding<Density, Schema> original(space);
const auto specification = reconstruction::SurfacePower<Density>{.density_exponent = 1.5};
const auto old_plan = backend::make_reconstruction(specification, original);
::mfem::Array<::mfem::Refinement> refinement;
refinement.Append(::mfem::Refinement(0, ::mfem::Refinement::Z));
mesh.GeneralRefinement(refinement, 1);
space.Update();
CHECK_THROWS_WITH((backend::TermBinding<Density, Schema>(space, old_plan)),
Catch::Matchers::ContainsSubstring("Rebuild the reconstruction plan"));
backend::TermBinding<Density, Schema> updated(space);
CHECK_THROWS_WITH(backend::make_reconstruction(specification, updated),
Catch::Matchers::ContainsSubstring("matching radial layers"));
}