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,134 @@
#include <algorithm>
#include <limits>
#include "serif/tests/equations/gravity_port.hpp"
namespace {
using namespace serif;
using namespace serif::tests::equations;
using gravity::GravityFixture;
using gravity_port::project_vector;
using Vector = ::mfem::Vector;
using Displacement = blocks::displacement::vector;
using Divergence = discretization::forms::Operand<Displacement, discretization::forms::Operation::Divergence>;
struct DivergenceInput {
using Test = blocks::density::mass;
using Operands = utils::types::TypeList<Divergence>;
static constexpr std::string_view name = "H1 divergence input test";
serif::equations::PointLinearization<Operands> evaluate(const serif::equations::EquationPoint<Operands>& point) const {
serif::equations::PointLinearization<Operands> result;
result.residual = point.fields.get<Divergence>();
result.field_derivatives.get<Divergence>() = 1.0;
return result;
}
int additional_order(Divergence, int) const {
return 0;
}
};
/// A smooth nonlinear displacement direction, independent of the field being differentiated.
Vector direction_displacement(GravityFixture& fixture) {
return project_vector(fixture.displacement_binding, [](const Vector& position, Vector& value) {
const double x = position[0], y = position[1], z = position[2];
value.SetSize(3);
value[0] = 0.019 * x + 0.011 * y * z - 0.006 * z * z + 0.004 * 0.83 * y;
value[1] = -0.016 * y + 0.008 * x * z + 0.005 * x * x - 0.003 * 0.83 * z;
value[2] = 0.013 * z - 0.010 * x * y + 0.006 * y * y + 0.004 * 0.83 * x;
});
}
double relative(const Vector& actual, const Vector& expected) {
Vector error(actual);
error -= expected;
return error.Norml2() / std::max({actual.Norml2(), expected.Norml2(), 100.0 * std::numeric_limits<double>::epsilon()});
}
}
TEST_CASE("H1 divergence inputs retain component gradients under a nonsymmetric map", "[equations][h1_divergence][unit]") {
for (const auto geometry : {::mfem::Element::TETRAHEDRON, ::mfem::Element::HEXAHEDRON}) {
for (const auto ordering : {::mfem::Ordering::byNODES, ::mfem::Ordering::byVDIM}) {
GravityFixture fixture(geometry, ordering);
const auto value = project_vector(fixture.displacement_binding, [](const Vector& x, Vector& u) {
u.SetSize(3);
u[0] = 0.4 * x[0] - 0.2 * x[1] + 0.3 * x[2];
u[1] = 0.1 * x[0] + 0.5 * x[1] - 0.1 * x[2];
u[2] = -0.3 * x[0] + 0.2 * x[1] + 0.6 * x[2];
});
const auto geometry_value = project_vector(fixture.displacement_binding, [](const Vector& x, Vector& u) {
u.SetSize(3);
u[0] = 0.2 * x[0] + 0.07 * x[1];
u[1] = -0.04 * x[0] - 0.1 * x[1] + 0.05 * x[2];
u[2] = 0.03 * x[0] + 0.1 * x[2];
});
const auto direction = gravity_port::prepared_displacement(fixture, 0.83);
const auto geometry_direction = direction_displacement(fixture);
auto state = [&](const Vector& v) {
return backend::make_term_coefficients(backend::bind(fixture.displacement_binding, v));
};
VolumeEquationFixture prepared{
DivergenceInput{}, fixture.density_binding, fixture.displacement_binding,
fixture.compactification, fixture.mapper, gravity::reference_policy()
};
prepared.prepare(state(value), geometry_value, fixture.workspace);
Vector actual;
prepared.residual(fixture.workspace, actual);
::mfem::DenseMatrix A(3), inverse(3);
A = 0.0;
A(0, 0) = 1.2;
A(0, 1) = 0.07;
A(1, 0) = -0.04;
A(1, 1) = 0.9;
A(1, 2) = 0.05;
A(2, 0) = 0.03;
A(2, 2) = 1.1;
::mfem::CalcInverse(A, inverse);
constexpr double gradient[3][3] = {{0.4, -0.2, 0.3}, {0.1, 0.5, -0.1}, {-0.3, 0.2, 0.6}};
double divergence = 0.0;
for (int component = 0; component < 3; ++component) {
for (int derivative = 0; derivative < 3; ++derivative) {
divergence += gradient[component][derivative] * inverse(derivative, component);
}
}
::mfem::ConstantCoefficient coefficient(divergence * A.Det());
::mfem::LinearForm reference_load(&fixture.density_space);
::mfem::Array<int> stellar{1, 1, 0};
reference_load.AddDomainIntegrator(new ::mfem::DomainLFIntegrator(coefficient), stellar);
reference_load.Assemble();
CHECK(relative(actual, fixture.density_binding.gather(reference_load)) < 2.0e-12);
for (int column = 0; column < 3; ++column) {
auto selected = direction, selected_geometry = geometry_direction;
if (column == 0) {
selected_geometry = 0.0;
} else if (column == 1) {
selected = 0.0;
}
prepared.prepare(state(value), geometry_value, fixture.workspace);
Vector derivative;
prepared.apply_jacobian(state(selected), selected_geometry, fixture.workspace, derivative);
auto plus = value, minus = value, plus_geometry = geometry_value, minus_geometry = geometry_value;
constexpr double step = 1.0e-5;
plus.Add(step, selected);
minus.Add(-step, selected);
plus_geometry.Add(step, selected_geometry);
minus_geometry.Add(-step, selected_geometry);
Vector plus_residual, minus_residual;
prepared.prepare(state(plus), plus_geometry, fixture.workspace);
prepared.residual(fixture.workspace, plus_residual);
prepared.prepare(state(minus), minus_geometry, fixture.workspace);
prepared.residual(fixture.workspace, minus_residual);
plus_residual -= minus_residual;
plus_residual *= 0.5 / step;
CHECK(relative(derivative, plus_residual) < 3.0e-8);
}
}
}
}