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,118 @@
#include <cmath>
#include <catch2/catch_test_macros.hpp>
#include "mfem/fem/fe_coll.hpp"
#include "mfem/mesh/mesh.hpp"
#include "serif/backend/mfem/operators/operand_evaluation.hpp"
#include "serif/discretization/blocks/fields.hpp"
namespace {
namespace forms = serif::discretization::forms;
namespace backend = serif::operators::backend::mfem;
using Gradient = forms::Operand<serif::discretization::blocks::displacement::vector, forms::Operation::Gradient>;
using Evaluator = backend::OperandEvaluator<Gradient>;
}
TEST_CASE("Vector H1 gradients retain native physical components and transpose actions", "[equations][vector_gradient][unit]") {
for (const auto geometry : {::mfem::Element::HEXAHEDRON, ::mfem::Element::TETRAHEDRON}) {
auto mesh = ::mfem::Mesh::MakeCartesian3D(1, 1, 1, geometry, 1.7, 0.9, 1.2);
for (int vertex = 0; vertex < mesh.GetNV(); ++vertex) {
double* position = mesh.GetVertex(vertex);
position[0] += 0.23 * position[1];
position[2] -= 0.17 * position[1];
}
::mfem::H1_FECollection collection(3, 3);
backend::TensorBasisCache cache;
backend::BasisWorkspace workspace;
for (int index = 0; index < mesh.GetNE(); ++index) {
auto& transformation = *mesh.GetElementTransformation(index);
const auto& element = *collection.FiniteElementForGeometry(mesh.GetElementBaseGeometry(index));
const auto& rule = ::mfem::IntRules.Get(element.GetGeomType(), 8);
for (const auto mode : {backend::ContractionMode::automatic, backend::ContractionMode::dense}) {
backend::BasisEvaluation basis;
Evaluator::sample_basis(element, rule, basis, cache, mode, transformation, workspace);
CHECK(basis.uses_tensor() == (geometry == ::mfem::Element::HEXAHEDRON && mode == backend::ContractionMode::automatic));
::mfem::Vector coefficients(3 * element.GetDof()), values, dual(9 * rule.GetNPoints()), transpose;
for (int repeat = 0; repeat < 2; ++repeat) {
for (int dof = 0; dof < coefficients.Size(); ++dof) {
coefficients[dof] = repeat == 0 ? std::sin(0.37 * (dof + 1)) : 0.0;
}
Evaluator::evaluate(basis, coefficients, values, workspace);
::mfem::DenseMatrix physical_basis(element.GetDof(), 3);
for (int point = 0; point < rule.GetNPoints(); ++point) {
transformation.SetIntPoint(&rule.IntPoint(point));
element.CalcPhysDShape(transformation, physical_basis);
for (int component = 0; component < 3; ++component) {
for (int derivative = 0; derivative < 3; ++derivative) {
double expected = 0.0;
for (int dof = 0; dof < element.GetDof(); ++dof) {
expected += physical_basis(dof, derivative) * coefficients[component * element.GetDof() + dof];
}
const int entry = 9 * point + 3 * component + derivative;
CHECK(std::abs(values[entry] - expected) < 2.0e-11);
dual[entry] = repeat == 0 ? std::cos(0.19 * (entry + 1)) : 0.0;
}
}
}
Evaluator::integrate(basis, dual, transpose, workspace);
CHECK(std::abs(values * dual - coefficients * transpose) < 2.0e-10);
if (repeat != 0) {
CHECK(values.Norml2() == 0.0);
CHECK(transpose.Norml2() == 0.0);
}
}
}
}
}
}
TEST_CASE("Vector gradient mapping differentiates right multiplication by the inverse map", "[equations][vector_gradient][unit]") {
serif::mapping::MappingPointContext context;
context.mapping_jacobian.SetSize(3);
context.mapping_jacobian = 0.0;
context.mapping_jacobian(0, 0) = 1.2;
context.mapping_jacobian(1, 1) = 0.8;
context.mapping_jacobian(2, 2) = 1.1;
context.mapping_jacobian(0, 1) = 0.21;
context.mapping_jacobian(2, 0) = -0.13;
context.inverse_mapping_jacobian.SetSize(3);
::mfem::CalcInverse(context.mapping_jacobian, context.inverse_mapping_jacobian);
serif::mapping::MappingPointVariation variation;
variation.mapping_jacobian_variation.SetSize(3);
for (int row = 0; row < 3; ++row) {
for (int column = 0; column < 3; ++column) {
variation.mapping_jacobian_variation(row, column) = std::sin(0.41 * (3 * row + column + 1));
}
}
::mfem::DenseMatrix product(3);
variation.inverse_mapping_jacobian_variation.SetSize(3);
::mfem::Mult(context.inverse_mapping_jacobian, variation.mapping_jacobian_variation, product);
::mfem::Mult(product, context.inverse_mapping_jacobian, variation.inverse_mapping_jacobian_variation);
variation.inverse_mapping_jacobian_variation *= -1.0;
const Evaluator::ReferenceValue reference{0.7, -0.2, 0.4, -0.6, 0.3, 0.1, 0.8, -0.9, 0.5};
const auto physical = Evaluator::physical_value(reference, context);
const auto derivative = Evaluator::geometry_variation(reference, context, variation);
for (int component = 0; component < 3; ++component) {
for (int axis = 0; axis < 3; ++axis) {
double recovered = 0.0;
for (int inner = 0; inner < 3; ++inner) {
recovered += physical[component][inner] * context.mapping_jacobian(inner, axis);
}
CHECK(std::abs(recovered - reference[3 * component + axis]) < 2.0e-14);
}
}
for (const double step : {1.0e-5, 1.0e-6}) {
auto plus = context, minus = context;
plus.mapping_jacobian.Add(step, variation.mapping_jacobian_variation);
minus.mapping_jacobian.Add(-step, variation.mapping_jacobian_variation);
::mfem::CalcInverse(plus.mapping_jacobian, plus.inverse_mapping_jacobian);
::mfem::CalcInverse(minus.mapping_jacobian, minus.inverse_mapping_jacobian);
const auto plus_gradient = Evaluator::physical_value(reference, plus);
const auto minus_gradient = Evaluator::physical_value(reference, minus);
for (int component = 0; component < 3; ++component) {
for (int axis = 0; axis < 3; ++axis) {
CHECK(std::abs(derivative[component][axis] - (plus_gradient[component][axis] - minus_gradient[component][axis]) / (2.0 * step)) < 3.0e-9);
}
}
}
}