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:
93
tests/equations/surface_potential_test.cpp
Normal file
93
tests/equations/surface_potential_test.cpp
Normal file
@@ -0,0 +1,93 @@
|
||||
#include <cmath>
|
||||
#include <numbers>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include "serif/tests/deformation/mesh.hpp"
|
||||
#include "serif/backend/mfem/model/surface_potential.hpp"
|
||||
#include "serif/backend/mfem/discretization/term_binding.hpp"
|
||||
#include "serif/backend/mfem/geometry/deformation/reference_geometry.hpp"
|
||||
|
||||
namespace {
|
||||
namespace deformation = serif::geometry::deformation;
|
||||
namespace backend = serif::model::backend::mfem;
|
||||
using Potential = serif::discretization::blocks::gravity::potential;
|
||||
|
||||
/** @brief Compare owned surface matrices with a coupled finite difference of the physical moments. */
|
||||
void check_surface(const int surface_order, const bool nodal) {
|
||||
serif::tests::deformation::PairedMesh mesh(0, 0.1, surface_order);
|
||||
::mfem::H1_FECollection potential_collection(Potential::FunctionalSpace::order, 3);
|
||||
::mfem::FiniteElementSpace potential_space(mesh.generated.mesh.get(), &potential_collection);
|
||||
serif::discretization::backend::mfem::TermBinding<Potential, serif::tests::deformation::Schema> potential_binding(potential_space);
|
||||
const auto reference = deformation::backend::mfem::sample_reference_surface(mesh.scalar_space, mesh.surface_attribute);
|
||||
REQUIRE(reference.has_value());
|
||||
const auto embedding = [&] {
|
||||
if (nodal) {
|
||||
return deformation::build_surface_embedding(deformation::NodalRadialSurface{}, reference->positions);
|
||||
}
|
||||
return deformation::build_surface_embedding(deformation::SphericalHarmonicSurface{.maximum_degree = 1}, reference->positions);
|
||||
}();
|
||||
REQUIRE(embedding.has_value());
|
||||
const deformation::backend::mfem::SurfaceDiscretization discretization{
|
||||
.scalar_space = mesh.scalar_space, .surface_attribute = mesh.surface_attribute};
|
||||
const serif::equations::SurfacePotentialBalance equation{{1.0, 2.0, 3.0}, {0.1, -0.2, 0.3}};
|
||||
std::vector<double> shape(embedding->input_size()), displacement(embedding->output_size());
|
||||
::mfem::Vector potential(potential_binding.size()), potential_direction(potential.Size()), shape_direction(static_cast<int>(shape.size()));
|
||||
for (int index = 0; index < potential.Size(); ++index) {
|
||||
potential[index] = -1.0 + 0.02 * std::sin(index + 0.3);
|
||||
potential_direction[index] = std::cos(index + 0.2);
|
||||
}
|
||||
for (int index = 0; index < shape_direction.Size(); ++index) {
|
||||
shape[index] = 0.01 * std::sin(index + 0.7);
|
||||
shape_direction[index] = 0.03 * std::cos(index + 0.4);
|
||||
}
|
||||
REQUIRE(embedding->apply(shape, displacement).has_value());
|
||||
const auto accepted = backend::assemble_surface_potential(equation, discretization, *reference, *embedding,
|
||||
displacement, potential_binding, potential, -1.2, 0.4);
|
||||
::mfem::Vector action(shape_direction.Size()), temporary(action.Size());
|
||||
accepted.potential->Mult(potential_direction, action);
|
||||
accepted.shape->Mult(shape_direction, temporary);
|
||||
action += temporary;
|
||||
for (int row = 0; row < action.Size(); ++row) {
|
||||
action[row] += 0.2 * (*accepted.level)(row, 0) - 0.3 * (*accepted.angular_velocity)(row, 0);
|
||||
}
|
||||
constexpr double step = 2.0e-6;
|
||||
auto plus_potential = potential, minus_potential = potential;
|
||||
plus_potential.Add(step, potential_direction);
|
||||
minus_potential.Add(-step, potential_direction);
|
||||
auto plus_shape = shape, minus_shape = shape;
|
||||
for (int index = 0; index < shape_direction.Size(); ++index) {
|
||||
plus_shape[index] += step * shape_direction[index];
|
||||
minus_shape[index] -= step * shape_direction[index];
|
||||
}
|
||||
REQUIRE(embedding->apply(plus_shape, displacement).has_value());
|
||||
const auto plus = backend::assemble_surface_potential(equation, discretization, *reference, *embedding,
|
||||
displacement, potential_binding, plus_potential, -1.2 + step * 0.2, 0.4 - step * 0.3);
|
||||
REQUIRE(embedding->apply(minus_shape, displacement).has_value());
|
||||
const auto minus = backend::assemble_surface_potential(equation, discretization, *reference, *embedding,
|
||||
displacement, potential_binding, minus_potential, -1.2 - step * 0.2, 0.4 + step * 0.3);
|
||||
for (int row = 0; row < action.Size(); ++row) {
|
||||
CHECK(std::abs(action[row] - (plus.residual[row] - minus.residual[row]) / (2.0 * step)) < 2.0e-9);
|
||||
}
|
||||
|
||||
// Constant potential and zero rotation give a known normalized moment, independent of surface area.
|
||||
potential = -0.8;
|
||||
const auto constant = backend::assemble_surface_potential(equation, discretization, *reference, *embedding,
|
||||
displacement, potential_binding, potential, -1.2, 0.0);
|
||||
double moment = 0.0;
|
||||
if (nodal) {
|
||||
for (const double value : constant.residual) { moment += value; }
|
||||
CHECK(std::abs(moment - 0.4) < 3.0e-12);
|
||||
} else {
|
||||
CHECK(std::abs(constant.residual.front() - 0.4 / std::sqrt(4.0 * std::numbers::pi)) < 3.0e-12);
|
||||
}
|
||||
constant.shape->Mult(shape_direction, temporary);
|
||||
CHECK(temporary.Norml2() == 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Isobar potential moments preserve constant mode and differentiate pullback geometry", "[model][surface_potential][unit]") {
|
||||
for (const int surface_order : {2, 3}) {
|
||||
check_surface(surface_order, false);
|
||||
check_surface(surface_order, true);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user