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,210 @@
#include <algorithm>
#include <cmath>
#include <memory>
#include <numbers>
#include <vector>
#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_floating_point.hpp>
#include "mfem/fem/coefficient.hpp"
#include "mfem/fem/gridfunc.hpp"
#include "serif/tests/equations/test_equations.hpp"
#include "serif/backend/mfem/discretization/boundary_trace.hpp"
#include "serif/discretization/blocks/fields.hpp"
#include "serif/backend/mfem/geometry/deformation/inputs.hpp"
#include "serif/backend/mfem/geometry/deformation/reference_geometry.hpp"
#include "serif/backend/mfem/geometry/deformation/surface_metric.hpp"
#include "serif/tests/deformation/mesh.hpp"
namespace
{
namespace deformation = serif::geometry::deformation;
namespace backend = deformation::backend::mfem;
namespace finite_elements = serif::discretization::backend::mfem;
namespace domain = serif::discretization::domain;
using Schema = domain::schema::CoreEnvelopeVacuumDomainSchema;
using Catch::Matchers::WithinAbs;
using Catch::Matchers::WithinRel;
/** @brief Retain the original eight-coordinate indexing example without imposing a higher-order physical field. */
struct LinearEnthalpy : serif::discretization::blocks::term<::serif::tests::equations::enthalpy>
{
using FunctionalSpace = serif::discretization::functional_space::DiscreteFunctionalSpace<serif::discretization::functional_space::H1, 1, 0>;
using Support = domain::StellarDomains;
using Quantity = serif::dimensions::SpecificEnthalpy;
struct value : serif::discretization::blocks::value_block_base
{
};
struct residual : serif::discretization::blocks::residual_block_base
{
};
};
using AlternateBoundarySchema = domain::schema::DomainSchema<typename Schema::domain_ids_type, domain::ids::lists::BoundaryIDList<domain::ids::BoundaryID<domain::StellarSurfaceBoundary, 2>, domain::ids::BoundaryID<domain::InfinitySurfaceBoundary, 1>>, domain::relation::lists::RelationList<>>;
/** @brief Resolve boundary coordinates independently using MFEM's serial essential true-DOF list. */
[[nodiscard]] std::vector<int> essential_dofs(const mfem::FiniteElementSpace& space, const int attribute)
{
mfem::Array<int> marker(space.GetMesh()->bdr_attributes.Max()), selected;
marker = 0;
marker[attribute - 1] = 1;
space.GetEssentialTrueDofs(marker, selected);
return std::vector<int>(selected.begin(), selected.end());
}
} // namespace
TEST_CASE("MeanField canonical scalar coordinates retain exact gather and transpose scatter indexing", "[unit][deformation][meanfield]")
{
mfem::Mesh mesh = mfem::Mesh::MakeCartesian3D(1, 1, 1, mfem::Element::HEXAHEDRON);
mfem::H1_FECollection collection(1, 3);
mfem::FiniteElementSpace space(&mesh, &collection);
finite_elements::TermBinding<LinearEnthalpy, Schema> binding(space);
REQUIRE(binding.size() == 8);
const mfem::Array<int> selected{1, 3, 6};
const auto rows = std::make_shared<const finite_elements::DofSelection>(binding, selected);
const auto trace = finite_elements::make_boundary_trace(rows, binding);
mfem::Vector full(binding.size()), sampled(rows->size()), scattered(binding.size());
for (int dof = 0; dof < full.Size(); ++dof)
{
full[dof] = 10.0 + dof;
}
trace.interpolation().Mult(full, sampled);
CHECK(sampled[0] == 11.0);
CHECK(sampled[1] == 13.0);
CHECK(sampled[2] == 16.0);
trace.interpolation().MultTranspose(sampled, scattered);
for (int dof = 0; dof < scattered.Size(); ++dof)
{
CHECK(scattered[dof] == (dof == 1 || dof == 3 || dof == 6 ? full[dof] : 0.0));
}
CHECK_THROWS_AS((finite_elements::DofSelection{binding, mfem::Array<int>{3, 1}}), std::invalid_argument);
CHECK_THROWS_AS((finite_elements::DofSelection{binding, mfem::Array<int>{1, 1}}), std::invalid_argument);
CHECK_THROWS_AS((finite_elements::DofSelection{binding, mfem::Array<int>{-1}}), std::invalid_argument);
CHECK_THROWS_AS((finite_elements::DofSelection{binding, mfem::Array<int>{8}}), std::invalid_argument);
}
TEST_CASE("MeanField surface coordinates resolve semantic boundary IDs and scalar companion nodes", "[unit][deformation][meanfield]")
{
serif::tests::deformation::PairedMesh fixture(0, 0.1, 3, 4);
const auto primary = backend::deformation_discretization<Schema>(fixture.scalar_space, fixture.vector_space, *fixture.generated.reference_mesh);
const auto alternate = backend::deformation_discretization<AlternateBoundarySchema>(fixture.scalar_space, fixture.vector_space, *fixture.generated.reference_mesh);
const auto stellar = backend::sample_reference_surface(fixture.scalar_space, primary.surface_attribute);
const auto infinity = backend::select_surface_scalar_dofs(fixture.scalar_space, alternate.surface_attribute);
REQUIRE(stellar);
REQUIRE(infinity);
CHECK(stellar->scalar_dofs == essential_dofs(fixture.scalar_space, 1));
CHECK(*infinity == essential_dofs(fixture.scalar_space, 2));
CHECK(stellar->scalar_dofs != *infinity);
CHECK(std::is_sorted(stellar->scalar_dofs.begin(), stellar->scalar_dofs.end()));
REQUIRE(stellar->scalar_dofs.size() > 0);
CHECK(stellar->scalar_dofs.size() < static_cast<std::size_t>(fixture.scalar_space.GetTrueVSize()));
const auto repeated = backend::select_surface_scalar_dofs(fixture.scalar_space, primary.surface_attribute);
REQUIRE(repeated);
CHECK(*repeated == stellar->scalar_dofs);
for (const mfem::Ordering::Type ordering : {mfem::Ordering::byNODES, mfem::Ordering::byVDIM})
{
mfem::FiniteElementSpace vector_space(fixture.generated.mesh.get(), &fixture.collection, 3, ordering);
const std::vector<int> vector_dofs = essential_dofs(vector_space, primary.surface_attribute);
CHECK(vector_dofs.size() == 3 * stellar->scalar_dofs.size());
mfem::VectorFunctionCoefficient coordinates(3, [](const mfem::Vector& position, mfem::Vector& value)
{
value = position;
});
mfem::GridFunction projected(&vector_space);
projected.ProjectCoefficient(coordinates);
for (std::size_t node = 0; node < stellar->positions.size(); ++node)
{
for (int component = 0; component < 3; ++component)
{
CHECK_THAT(stellar->positions[node][component], WithinAbs(projected[vector_space.DofToVDof(stellar->scalar_dofs[node], component)], 2.0e-13));
}
}
}
}
TEST_CASE("MeanField stellar surface metrics include tagged internal interfaces", "[unit][deformation][meanfield]")
{
serif::tests::deformation::PairedMesh fixture;
int selected_faces = 0;
for (int boundary = 0; boundary < fixture.generated.mesh->GetNBE(); ++boundary)
{
if (fixture.generated.mesh->GetBdrAttribute(boundary) == fixture.surface_attribute)
{
const int face = fixture.generated.mesh->GetBdrElementFaceIndex(boundary);
REQUIRE(face >= 0);
CHECK(fixture.generated.mesh->GetFaceInformation(face).IsOfFaceType(mfem::FaceType::Interior));
++selected_faces;
}
}
REQUIRE(selected_faces > 0);
const auto reference = backend::sample_reference_surface(fixture.scalar_space, fixture.surface_attribute);
REQUIRE(reference);
const auto embedding = deformation::build_surface_embedding(deformation::NodalRadialSurface{}, reference->positions);
REQUIRE(embedding);
const auto metric = backend::surface_parameter_mass_diagonal(fixture.scalar_space, fixture.surface_attribute, *reference, *embedding);
REQUIRE(metric);
REQUIRE(metric->size() == reference->scalar_dofs.size());
for (const double diagonal : *metric)
{
CHECK(std::isfinite(diagonal));
CHECK(diagonal > 0.0);
}
}
TEST_CASE("MeanField STROID spherical volume retains its original mesh accuracy", "[integration][deformation][geometry][meanfield]")
{
// Match sandbox.smesh: two refinements, quartic geometry, stellar radius 1 and reference infinity 5.
auto generated = serif::tests::deformation::make_mesh(2, 0.0, 4, 5.0);
long double volume = 0.0L;
int stellar_elements = 0;
for (int element = 0; element < generated.mesh->GetNE(); ++element)
{
if (!Schema::domain_id_belongs_to<domain::StellarDomains>(generated.mesh->GetAttribute(element)))
{
continue;
}
mfem::ElementTransformation& transformation = *generated.mesh->GetElementTransformation(element);
const mfem::IntegrationRule& rule = mfem::IntRules.Get(transformation.GetGeometryType(), transformation.OrderW());
for (int point = 0; point < rule.GetNPoints(); ++point)
{
const mfem::IntegrationPoint& sample = rule.IntPoint(point);
transformation.SetIntPoint(&sample);
volume += sample.weight * transformation.Weight();
}
++stellar_elements;
}
REQUIRE(stellar_elements > 0);
CHECK_THAT(static_cast<double>(volume), WithinRel(4.0 * std::numbers::pi / 3.0, 1.0e-6));
}
TEST_CASE("Shared MFEM reference tracking distinguishes coordinate and label dependencies", "[unit][deformation][reference_tracking]") {
namespace shared = serif::backend::mfem::shared;
mfem::Mesh mesh = mfem::Mesh::MakeCartesian3D(1, 1, 1, mfem::Element::HEXAHEDRON);
mfem::H1_FECollection collection(1, 3);
mfem::FiniteElementSpace space(&mesh, &collection);
const auto topology = shared::mesh_snapshot(&mesh, false, false);
const auto materials = shared::mesh_snapshot(&mesh, false, true);
const auto coordinates = shared::mesh_snapshot(&mesh, true, true);
const auto original_space = shared::space_snapshot(space);
REQUIRE(shared::mesh_matches(topology, &mesh));
REQUIRE(shared::mesh_matches(coordinates, &mesh));
mesh.Transform([](const mfem::Vector& position, mfem::Vector& transformed) {
transformed = position;
transformed[0] *= 1.2;
});
CHECK(shared::mesh_matches(topology, &mesh));
CHECK(shared::mesh_matches(materials, &mesh));
CHECK_FALSE(shared::mesh_matches(coordinates, &mesh));
CHECK(shared::space_snapshot(space) == original_space);
mesh.GetElement(0)->SetAttribute(9);
CHECK(shared::mesh_matches(topology, &mesh));
CHECK_FALSE(shared::mesh_matches(materials, &mesh));
mesh.GetBdrElement(0)->SetAttribute(9);
CHECK_FALSE(shared::mesh_matches(topology, &mesh));
mfem::FiniteElementSpace other(&mesh, &collection);
CHECK_FALSE(shared::space_snapshot(other) == original_space);
CHECK_FALSE(shared::mesh_matches(topology, nullptr));
CHECK(shared::mesh_matches(shared::mesh_snapshot(nullptr, true, true), nullptr));
}