Files
MeanField_SERiF/tests/equations/h1_vector_gradient.cpp
Emily Boudreaux cb20faeb3d 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.
2026-11-09 13:53:17 -05:00

119 lines
6.6 KiB
C++

#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);
}
}
}
}