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,773 @@
// Ported from MeanField tests/mapping/compactification/kelvin.cpp.
#include <algorithm>
#include <array>
#include <cmath>
#include <limits>
#include <stdexcept>
#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_floating_point.hpp>
#include <mfem.hpp>
#include "serif/backend/mfem/mapping/compactification/kelvin.hpp"
#include "serif/tests/test_tags.hpp"
namespace mapping = serif::mapping;
using Catch::Matchers::WithinAbs;
namespace {
constexpr int dimension = 3;
mfem::Vector make_vector(const double x, const double y, const double z) {
mfem::Vector vector(dimension);
vector(0) = x;
vector(1) = y;
vector(2) = z;
return vector;
}
mfem::DenseMatrix make_identity() {
mfem::DenseMatrix matrix(dimension);
matrix = 0.0;
for (int i = 0; i < dimension; ++i) {
matrix(i, i) = 1.0;
}
return matrix;
}
void check_vector(const mfem::Vector& actual, const mfem::Vector& expected, const double tolerance) {
REQUIRE(actual.Size() == expected.Size());
for (int i = 0; i < actual.Size(); ++i) {
CHECK_THAT(actual(i), WithinAbs(expected(i), tolerance));
}
}
void check_matrix(const mfem::DenseMatrix& actual, const mfem::DenseMatrix& expected, const double tolerance) {
REQUIRE(actual.Height() == expected.Height());
REQUIRE(actual.Width() == expected.Width());
for (int i = 0; i < actual.Height(); ++i) {
for (int j = 0; j < actual.Width(); ++j) {
CHECK_THAT(actual(i, j), WithinAbs(expected(i, j), tolerance));
}
}
}
struct AnalyticFactors {
double computational_radius;
double scale;
double scale_derivative;
};
AnalyticFactors compute_analytic_factors(const double r_star, const double r_inf, const double coordinate) {
const double radial_extent = r_inf - r_star;
const double computational_radius = r_star + coordinate * radial_extent;
const double scale = r_star / (computational_radius * (1.0 - coordinate));
const double scale_derivative = scale * (1.0 / (1.0 - coordinate) - radial_extent / computational_radius);
return {.computational_radius = computational_radius, .scale = scale, .scale_derivative = scale_derivative};
}
mapping::MappingStatus evaluate_affine_map(
const mapping::compactification::ExteriorDomainMap& exterior_map,
const mfem::Vector& reference_position,
const mfem::DenseMatrix& affine_jacobian,
const mfem::Vector& offset,
const double compactification_coordinate,
const mfem::Vector& compactification_coordinate_gradient,
mapping::compactification::ExteriorMapResult& result
) {
mfem::Vector displaced_position(dimension);
affine_jacobian.Mult(reference_position, displaced_position);
displaced_position += offset;
const mapping::compactification::ExteriorMapInput input{
.reference_position = reference_position,
.displaced_position = displaced_position,
.displacement_jacobian = affine_jacobian,
.compactification_coordinate_gradient = compactification_coordinate_gradient,
.compactification_coordinate = compactification_coordinate
};
return exterior_map.evaluate(input, result);
}
} // namespace
TEST_CASE(
"Kelvin Prepared Jacobian Factors Match Full Variation",
tags::unit & tags::kelvin
) {
mapping::compactification::KelvinCompactification kelvin({.r_star_ref = 1.3, .r_inf_ref = 3.1});
const auto position = make_vector(1.1, 0.2, -0.1);
const auto coordinate_gradient = make_vector(0.2, 0.03, -0.01);
auto jacobian = make_identity();
jacobian(0, 1) = 0.07;
const auto direction = make_vector(-0.02, 0.07, 0.04);
auto direction_jacobian = make_identity();
direction_jacobian *= 0.03;
direction_jacobian(1, 2) = -0.05;
for (const double coordinate : {0.0, 0.4, 0.93, 0.999}) {
const mapping::compactification::ExteriorMapInput input{
position, position, jacobian, coordinate_gradient, coordinate
};
mapping::compactification::ExteriorMapResult base;
REQUIRE(kelvin.evaluate(input, base) == mapping::MappingStatus::VALID);
mapping::compactification::ExteriorMapVariation variation;
REQUIRE(
kelvin.evaluate_variation(input, base, {direction, direction_jacobian}, variation) ==
mapping::MappingStatus::VALID
);
double scale = 0.0;
mfem::Vector scale_gradient;
REQUIRE(
kelvin.evaluate_jacobian_variation_factors(coordinate, coordinate_gradient, scale, scale_gradient) ==
mapping::MappingStatus::VALID
);
for (int row = 0; row < 3; ++row) {
for (int column = 0; column < 3; ++column) {
const double expected = variation.mapping_jacobian_variation(row, column);
CHECK_THAT(
scale * direction_jacobian(row, column) + direction(row) * scale_gradient(column),
WithinAbs(expected, 1.0e-12 * std::max(1.0, std::abs(expected)))
);
}
}
}
double scale = 0.0;
mfem::Vector gradient;
CHECK(
kelvin.evaluate_jacobian_variation_factors(1.0, coordinate_gradient, scale, gradient) ==
mapping::MappingStatus::AT_COMPACTIFIED_INFINITY
);
CHECK(
kelvin.evaluate_jacobian_variation_factors(-0.1, coordinate_gradient, scale, gradient) ==
mapping::MappingStatus::OUTSIDE_REFERENCE_DOMAIN
);
auto invalid = coordinate_gradient;
invalid(1) = std::numeric_limits<double>::quiet_NaN();
CHECK(
kelvin.evaluate_jacobian_variation_factors(0.4, invalid, scale, gradient) ==
mapping::MappingStatus::NON_FINITE_MAPPING_INPUT
);
}
TEST_CASE(
"Kelvin Compactification Validates Its Configuration",
tags::unit & tags::kelvin
) {
CHECK_NOTHROW(mapping::compactification::KelvinCompactification({.r_star_ref = 1.0, .r_inf_ref = 4.0}));
CHECK_THROWS_AS(
mapping::compactification::KelvinCompactification({.r_star_ref = 0.0, .r_inf_ref = 4.0}), std::invalid_argument
);
CHECK_THROWS_AS(
mapping::compactification::KelvinCompactification({.r_star_ref = -1.0, .r_inf_ref = 4.0}), std::invalid_argument
);
CHECK_THROWS_AS(
mapping::compactification::KelvinCompactification({.r_star_ref = 2.0, .r_inf_ref = 2.0}), std::invalid_argument
);
CHECK_THROWS_AS(
mapping::compactification::KelvinCompactification({.r_star_ref = 3.0, .r_inf_ref = 2.0}), std::invalid_argument
);
CHECK_THROWS_AS(
mapping::compactification::KelvinCompactification(
{.r_star_ref = 1.0, .r_inf_ref = std::numeric_limits<double>::infinity()}
),
std::invalid_argument
);
CHECK_THROWS_AS(
mapping::compactification::KelvinCompactification(
{.r_star_ref = 1.0, .r_inf_ref = 4.0, .coordinate_tolerance = -1.0e-12}
),
std::invalid_argument
);
CHECK_THROWS_AS(
mapping::compactification::KelvinCompactification(
{.r_star_ref = 1.0, .r_inf_ref = 4.0, .coordinate_tolerance = 1.0}
),
std::invalid_argument
);
CHECK_THROWS_AS(
mapping::compactification::KelvinCompactification(
{.r_star_ref = 1.0, .r_inf_ref = 4.0, .coordinate_tolerance = std::numeric_limits<double>::quiet_NaN()}
),
std::invalid_argument
);
}
TEST_CASE(
"Kelvin Compactification Reports Its Configuration",
tags::unit & tags::kelvin
) {
constexpr double coordinate_tolerance = 3.0e-11;
mapping::compactification::KelvinCompactification compactification(
{.r_star_ref = 1.25, .r_inf_ref = 5.5, .coordinate_tolerance = coordinate_tolerance}
);
CHECK(compactification.get_name() == "KelvinCompactification");
CHECK_THAT(compactification.get_reference_stellar_radius(), WithinAbs(1.25, 0.0));
CHECK_THAT(compactification.get_reference_infinity_radius(), WithinAbs(5.5, 0.0));
CHECK_THAT(compactification.get_coordinate_tolerance(), WithinAbs(coordinate_tolerance, 0.0));
}
TEST_CASE(
"Kelvin Compactification Uses The Exterior Coordinate Rather Than "
"Euclidean Radius",
tags::unit & tags::kelvin
) {
constexpr double tolerance = 0.0;
constexpr double coordinate = 0.37;
mapping::compactification::KelvinCompactification compactification({.r_star_ref = 1.0, .r_inf_ref = 4.0});
const mfem::DenseMatrix displacement_jacobian = make_identity();
mfem::Vector displaced_position = make_vector(1.4, -0.2, 0.3);
mfem::Vector coordinate_gradient = make_vector(0.2, -0.1, 0.05);
mfem::Vector reference_a = make_vector(0.2, 0.1, -0.1);
mfem::Vector reference_b = make_vector(12.0, -7.0, 4.0);
const mapping::compactification::ExteriorMapInput input_a{
reference_a, displaced_position, displacement_jacobian, coordinate_gradient, coordinate
};
const mapping::compactification::ExteriorMapInput input_b{
reference_b, displaced_position, displacement_jacobian, coordinate_gradient, coordinate
};
mapping::compactification::ExteriorMapResult result_a;
mapping::compactification::ExteriorMapResult result_b;
REQUIRE(compactification.evaluate(input_a, result_a) == mapping::MappingStatus::VALID);
REQUIRE(compactification.evaluate(input_b, result_b) == mapping::MappingStatus::VALID);
check_vector(result_a.physical_position, result_b.physical_position, tolerance);
check_matrix(result_a.mapping_jacobian, result_b.mapping_jacobian, tolerance);
}
TEST_CASE(
"Kelvin Compactification Matches Its Analytic Radial Map",
tags::unit & tags::kelvin
) {
constexpr double r_star = 1.0;
constexpr double r_inf = 4.0;
constexpr double radial_extent = r_inf - r_star;
constexpr double tolerance = 2.0e-12;
mapping::compactification::KelvinCompactification compactification({.r_star_ref = r_star, .r_inf_ref = r_inf});
const mfem::DenseMatrix identity = make_identity();
mfem::Vector coordinate_gradient = make_vector(1.0 / radial_extent, 0.0, 0.0);
for (const double computational_radius : std::array{1.0, 1.25, 2.0, 3.0, 3.75}) {
CAPTURE(computational_radius);
const double coordinate = (computational_radius - r_star) / radial_extent;
mfem::Vector reference_position = make_vector(computational_radius, 0.0, 0.0);
mfem::Vector displaced_position(reference_position);
const mapping::compactification::ExteriorMapInput input{
reference_position, displaced_position, identity, coordinate_gradient, coordinate
};
mapping::compactification::ExteriorMapResult result;
REQUIRE(compactification.evaluate(input, result) == mapping::MappingStatus::VALID);
const double expected_radius = r_star * radial_extent / (r_inf - computational_radius);
const double expected_radial_derivative = r_star * radial_extent / std::pow(r_inf - computational_radius, 2.0);
const double expected_tangential_scale = expected_radius / computational_radius;
mfem::Vector expected_position = make_vector(expected_radius, 0.0, 0.0);
mfem::DenseMatrix expected_jacobian(dimension);
expected_jacobian = 0.0;
expected_jacobian(0, 0) = expected_radial_derivative;
expected_jacobian(1, 1) = expected_tangential_scale;
expected_jacobian(2, 2) = expected_tangential_scale;
check_vector(result.physical_position, expected_position, tolerance);
check_matrix(result.mapping_jacobian, expected_jacobian, tolerance);
CHECK(result.mapping_jacobian.Det() > 0.0);
}
}
TEST_CASE(
"Kelvin Compactification Matches Its Full Cartesian Formula",
tags::unit & tags::kelvin
) {
constexpr double r_star = 1.0;
constexpr double r_inf = 4.0;
constexpr double coordinate = 0.42;
constexpr double tolerance = 1.0e-12;
mapping::compactification::KelvinCompactification compactification({.r_star_ref = r_star, .r_inf_ref = r_inf});
mfem::Vector reference_position = make_vector(0.8, 0.4, -0.2);
mfem::Vector displaced_position = make_vector(1.1, 0.5, -0.1);
mfem::Vector coordinate_gradient = make_vector(0.20, -0.10, 0.05);
mfem::DenseMatrix displacement_jacobian(dimension);
displacement_jacobian(0, 0) = 1.10;
displacement_jacobian(0, 1) = 0.05;
displacement_jacobian(0, 2) = 0.00;
displacement_jacobian(1, 0) = -0.02;
displacement_jacobian(1, 1) = 0.95;
displacement_jacobian(1, 2) = 0.03;
displacement_jacobian(2, 0) = 0.01;
displacement_jacobian(2, 1) = -0.04;
displacement_jacobian(2, 2) = 1.05;
const mapping::compactification::ExteriorMapInput input{
reference_position, displaced_position, displacement_jacobian, coordinate_gradient, coordinate
};
mapping::compactification::ExteriorMapResult result;
REQUIRE(compactification.evaluate(input, result) == mapping::MappingStatus::VALID);
const AnalyticFactors factors = compute_analytic_factors(r_star, r_inf, coordinate);
mfem::Vector expected_position(displaced_position);
expected_position *= factors.scale;
mfem::DenseMatrix expected_jacobian(displacement_jacobian);
expected_jacobian *= factors.scale;
for (int i = 0; i < dimension; ++i) {
for (int j = 0; j < dimension; ++j) {
expected_jacobian(i, j) += displaced_position(i) * factors.scale_derivative * coordinate_gradient(j);
}
}
check_vector(result.physical_position, expected_position, tolerance);
check_matrix(result.mapping_jacobian, expected_jacobian, tolerance);
}
TEST_CASE(
"Kelvin Compactification Jacobian Matches Coordinate Finite Differences",
tags::unit & tags::kelvin
) {
constexpr double base_coordinate = 0.38;
constexpr double difference_step = 1.0e-6;
constexpr double tolerance = 3.0e-9;
mapping::compactification::KelvinCompactification compactification({.r_star_ref = 1.0, .r_inf_ref = 4.0});
mfem::DenseMatrix affine_jacobian(dimension);
affine_jacobian(0, 0) = 1.05;
affine_jacobian(0, 1) = 0.02;
affine_jacobian(0, 2) = 0.00;
affine_jacobian(1, 0) = -0.01;
affine_jacobian(1, 1) = 0.98;
affine_jacobian(1, 2) = 0.03;
affine_jacobian(2, 0) = 0.02;
affine_jacobian(2, 1) = 0.00;
affine_jacobian(2, 2) = 1.02;
mfem::Vector offset = make_vector(0.04, -0.03, 0.02);
mfem::Vector reference_position = make_vector(1.4, 0.3, -0.2);
mfem::Vector coordinate_gradient = make_vector(0.11, -0.07, 0.05);
mapping::compactification::ExteriorMapResult base_result;
REQUIRE(
evaluate_affine_map(
compactification, reference_position, affine_jacobian, offset, base_coordinate, coordinate_gradient,
base_result
) == mapping::MappingStatus::VALID
);
for (int coordinate = 0; coordinate < dimension; ++coordinate) {
mfem::Vector reference_plus(reference_position);
mfem::Vector reference_minus(reference_position);
reference_plus(coordinate) += difference_step;
reference_minus(coordinate) -= difference_step;
const double compactification_plus = base_coordinate + difference_step * coordinate_gradient(coordinate);
const double compactification_minus = base_coordinate - difference_step * coordinate_gradient(coordinate);
mapping::compactification::ExteriorMapResult result_plus;
mapping::compactification::ExteriorMapResult result_minus;
REQUIRE(
evaluate_affine_map(
compactification, reference_plus, affine_jacobian, offset, compactification_plus, coordinate_gradient,
result_plus
) == mapping::MappingStatus::VALID
);
REQUIRE(
evaluate_affine_map(
compactification, reference_minus, affine_jacobian, offset, compactification_minus, coordinate_gradient,
result_minus
) == mapping::MappingStatus::VALID
);
for (int component = 0; component < dimension; ++component) {
const double finite_difference =
(result_plus.physical_position(component) - result_minus.physical_position(component)) /
(2.0 * difference_step);
CHECK_THAT(finite_difference, WithinAbs(base_result.mapping_jacobian(component, coordinate), tolerance));
}
}
}
TEST_CASE(
"Kelvin Compactification Variation Matches State Finite Differences",
tags::unit & tags::kelvin
) {
constexpr double coordinate = 0.46;
constexpr double difference_step = 1.0e-6;
constexpr double tolerance = 2.0e-10;
mapping::compactification::KelvinCompactification compactification({.r_star_ref = 1.0, .r_inf_ref = 4.0});
mfem::Vector reference_position = make_vector(1.3, -0.2, 0.4);
mfem::Vector displaced_position = make_vector(1.4, -0.1, 0.35);
mfem::Vector coordinate_gradient = make_vector(0.12, -0.04, 0.08);
mfem::Vector position_direction = make_vector(0.03, -0.05, 0.02);
mfem::DenseMatrix displacement_jacobian = make_identity();
displacement_jacobian(0, 1) = 0.04;
displacement_jacobian(1, 2) = -0.03;
displacement_jacobian(2, 0) = 0.02;
mfem::DenseMatrix jacobian_direction(dimension);
jacobian_direction(0, 0) = 0.02;
jacobian_direction(0, 1) = -0.01;
jacobian_direction(0, 2) = 0.03;
jacobian_direction(1, 0) = 0.01;
jacobian_direction(1, 1) = -0.02;
jacobian_direction(1, 2) = 0.00;
jacobian_direction(2, 0) = -0.01;
jacobian_direction(2, 1) = 0.02;
jacobian_direction(2, 2) = 0.01;
const mapping::compactification::ExteriorMapInput input{
reference_position, displaced_position, displacement_jacobian, coordinate_gradient, coordinate
};
const mapping::compactification::ExteriorMapDirection direction{position_direction, jacobian_direction};
mapping::compactification::ExteriorMapResult base_result;
mapping::compactification::ExteriorMapVariation variation;
REQUIRE(compactification.evaluate(input, base_result) == mapping::MappingStatus::VALID);
REQUIRE(
compactification.evaluate_variation(input, base_result, direction, variation) == mapping::MappingStatus::VALID
);
mfem::Vector displaced_plus(displaced_position);
mfem::Vector displaced_minus(displaced_position);
displaced_plus.Add(difference_step, position_direction);
displaced_minus.Add(-difference_step, position_direction);
mfem::DenseMatrix jacobian_plus(displacement_jacobian);
mfem::DenseMatrix jacobian_minus(displacement_jacobian);
jacobian_plus.Add(difference_step, jacobian_direction);
jacobian_minus.Add(-difference_step, jacobian_direction);
const mapping::compactification::ExteriorMapInput input_plus{
reference_position, displaced_plus, jacobian_plus, coordinate_gradient, coordinate
};
const mapping::compactification::ExteriorMapInput input_minus{
reference_position, displaced_minus, jacobian_minus, coordinate_gradient, coordinate
};
mapping::compactification::ExteriorMapResult result_plus;
mapping::compactification::ExteriorMapResult result_minus;
REQUIRE(compactification.evaluate(input_plus, result_plus) == mapping::MappingStatus::VALID);
REQUIRE(compactification.evaluate(input_minus, result_minus) == mapping::MappingStatus::VALID);
for (int i = 0; i < dimension; ++i) {
const double position_finite_difference =
(result_plus.physical_position(i) - result_minus.physical_position(i)) / (2.0 * difference_step);
CHECK_THAT(position_finite_difference, WithinAbs(variation.physical_position_variation(i), tolerance));
for (int j = 0; j < dimension; ++j) {
const double jacobian_finite_difference =
(result_plus.mapping_jacobian(i, j) - result_minus.mapping_jacobian(i, j)) / (2.0 * difference_step);
CHECK_THAT(jacobian_finite_difference, WithinAbs(variation.mapping_jacobian_variation(i, j), tolerance));
}
}
}
TEST_CASE(
"Kelvin Compactification Preserves Rotational Covariance",
tags::unit & tags::kelvin
) {
constexpr double coordinate = 0.31;
constexpr double tolerance = 1.0e-12;
mapping::compactification::KelvinCompactification compactification({.r_star_ref = 1.0, .r_inf_ref = 4.0});
mfem::Vector reference_position = make_vector(1.3, 0.4, -0.2);
mfem::Vector displaced_position = make_vector(1.4, 0.2, -0.1);
mfem::Vector coordinate_gradient = make_vector(0.16, -0.08, 0.03);
mfem::DenseMatrix displacement_jacobian = make_identity();
displacement_jacobian(0, 1) = 0.05;
displacement_jacobian(1, 0) = -0.02;
displacement_jacobian(2, 1) = 0.03;
const mapping::compactification::ExteriorMapInput input{
reference_position, displaced_position, displacement_jacobian, coordinate_gradient, coordinate
};
mapping::compactification::ExteriorMapResult result;
REQUIRE(compactification.evaluate(input, result) == mapping::MappingStatus::VALID);
mfem::DenseMatrix rotation(dimension);
rotation = 0.0;
rotation(0, 1) = -1.0;
rotation(1, 0) = 1.0;
rotation(2, 2) = 1.0;
mfem::Vector rotated_reference(dimension);
mfem::Vector rotated_displaced(dimension);
mfem::Vector rotated_coordinate_gradient(dimension);
rotation.Mult(reference_position, rotated_reference);
rotation.Mult(displaced_position, rotated_displaced);
rotation.Mult(coordinate_gradient, rotated_coordinate_gradient);
mfem::DenseMatrix temporary(dimension);
mfem::DenseMatrix rotated_displacement_jacobian(dimension);
mfem::Mult(rotation, displacement_jacobian, temporary);
mfem::MultABt(temporary, rotation, rotated_displacement_jacobian);
const mapping::compactification::ExteriorMapInput rotated_input{
rotated_reference, rotated_displaced, rotated_displacement_jacobian, rotated_coordinate_gradient, coordinate
};
mapping::compactification::ExteriorMapResult rotated_result;
REQUIRE(compactification.evaluate(rotated_input, rotated_result) == mapping::MappingStatus::VALID);
mfem::Vector expected_position(dimension);
rotation.Mult(result.physical_position, expected_position);
mfem::DenseMatrix expected_jacobian(dimension);
mfem::Mult(rotation, result.mapping_jacobian, temporary);
mfem::MultABt(temporary, rotation, expected_jacobian);
check_vector(rotated_result.physical_position, expected_position, tolerance);
check_matrix(rotated_result.mapping_jacobian, expected_jacobian, tolerance);
}
TEST_CASE(
"Kelvin Compactification Is Continuous At The Mesh Defined Stellar Surface",
tags::unit & tags::kelvin
) {
constexpr double tolerance = 1.0e-14;
mapping::compactification::KelvinCompactification compactification({.r_star_ref = 1.0, .r_inf_ref = 4.0});
mfem::Vector reference_position = make_vector(-0.5260553366425769, 0.5260553366425769, -0.6553163792879153);
mfem::Vector displaced_position = make_vector(-0.55, 0.51, -0.63);
mfem::Vector coordinate_gradient = make_vector(-0.18, 0.18, -0.22);
const mfem::DenseMatrix displacement_jacobian = make_identity();
REQUIRE(reference_position.Norml2() < 1.0);
const mapping::compactification::ExteriorMapInput input{
reference_position, displaced_position, displacement_jacobian, coordinate_gradient, 0.0
};
mapping::compactification::ExteriorMapResult result;
REQUIRE(compactification.evaluate(input, result) == mapping::MappingStatus::VALID);
check_vector(result.physical_position, displaced_position, tolerance);
CHECK(result.mapping_jacobian.Det() > 0.0);
}
TEST_CASE(
"Kelvin Compactification Has Correct Infinity And Coordinate Bound "
"Behavior",
tags::unit & tags::kelvin
) {
constexpr double r_star = 1.0;
constexpr double r_inf = 4.0;
constexpr double radial_extent = r_inf - r_star;
constexpr double coordinate_tolerance = 1.0e-12;
constexpr double tolerance = 2.0e-11;
mapping::compactification::KelvinCompactification compactification(
{.r_star_ref = r_star, .r_inf_ref = r_inf, .coordinate_tolerance = coordinate_tolerance}
);
const mfem::DenseMatrix identity = make_identity();
mfem::Vector coordinate_gradient = make_vector(1.0 / radial_extent, 0.0, 0.0);
for (const double coordinate : std::array{0.0, 0.25, 0.75, 0.95, 0.99, 0.999}) {
CAPTURE(coordinate);
const double computational_radius = r_star + coordinate * radial_extent;
mfem::Vector reference_position = make_vector(computational_radius, 0.0, 0.0);
const mapping::compactification::ExteriorMapInput input{
reference_position, reference_position, identity, coordinate_gradient, coordinate
};
mapping::compactification::ExteriorMapResult result;
REQUIRE(compactification.evaluate(input, result) == mapping::MappingStatus::VALID);
CHECK_THAT(result.physical_position.Norml2() * (1.0 - coordinate), WithinAbs(r_star, tolerance));
}
mfem::Vector reference_position = make_vector(r_inf, 0.0, 0.0);
mapping::compactification::ExteriorMapResult result;
CHECK(
compactification.evaluate(
{reference_position, reference_position, identity, coordinate_gradient, 1.0}, result
) == mapping::MappingStatus::AT_COMPACTIFIED_INFINITY
);
CHECK(
compactification.evaluate(
{reference_position, reference_position, identity, coordinate_gradient, 1.0 - 0.5 * coordinate_tolerance},
result
) == mapping::MappingStatus::AT_COMPACTIFIED_INFINITY
);
CHECK(
compactification.evaluate(
{reference_position, reference_position, identity, coordinate_gradient, 1.0 + 0.5 * coordinate_tolerance},
result
) == mapping::MappingStatus::AT_COMPACTIFIED_INFINITY
);
CHECK(
compactification.evaluate(
{reference_position, reference_position, identity, coordinate_gradient, 1.0 + 2.0 * coordinate_tolerance},
result
) == mapping::MappingStatus::OUTSIDE_REFERENCE_DOMAIN
);
CHECK(
compactification.evaluate(
{reference_position, reference_position, identity, coordinate_gradient, -2.0 * coordinate_tolerance}, result
) == mapping::MappingStatus::OUTSIDE_REFERENCE_DOMAIN
);
mfem::Vector surface_position = make_vector(0.97, 0.0, 0.0);
CHECK(
compactification.evaluate(
{surface_position, surface_position, identity, coordinate_gradient, -0.5 * coordinate_tolerance}, result
) == mapping::MappingStatus::VALID
);
check_vector(result.physical_position, surface_position, tolerance);
}
TEST_CASE(
"Kelvin Compactification Rejects Invalid Inputs And Inverted Maps",
tags::unit & tags::kelvin
) {
mapping::compactification::KelvinCompactification compactification({.r_star_ref = 1.0, .r_inf_ref = 4.0});
mfem::Vector reference_position = make_vector(2.0, 0.0, 0.0);
mfem::Vector displaced_position(reference_position);
mfem::Vector coordinate_gradient = make_vector(1.0 / 3.0, 0.0, 0.0);
mfem::Vector zero_gradient = make_vector(0.0, 0.0, 0.0);
const mfem::DenseMatrix identity = make_identity();
mapping::compactification::ExteriorMapResult result;
mfem::Vector wrong_dimension(2);
wrong_dimension = 1.0;
CHECK(
compactification.evaluate(
{wrong_dimension, displaced_position, identity, coordinate_gradient, 1.0 / 3.0}, result
) == mapping::MappingStatus::INVALID_DIMENSION
);
CHECK(
compactification.evaluate(
{reference_position, displaced_position, identity, wrong_dimension, 1.0 / 3.0}, result
) == mapping::MappingStatus::INVALID_DIMENSION
);
mfem::Vector non_finite_position(reference_position);
non_finite_position(1) = std::numeric_limits<double>::quiet_NaN();
CHECK(
compactification.evaluate(
{non_finite_position, displaced_position, identity, coordinate_gradient, 1.0 / 3.0}, result
) == mapping::MappingStatus::NON_FINITE_MAPPING_INPUT
);
mfem::Vector non_finite_gradient(coordinate_gradient);
non_finite_gradient(2) = std::numeric_limits<double>::infinity();
CHECK(
compactification.evaluate(
{reference_position, displaced_position, identity, non_finite_gradient, 1.0 / 3.0}, result
) == mapping::MappingStatus::NON_FINITE_MAPPING_INPUT
);
CHECK(
compactification.evaluate(
{reference_position, displaced_position, identity, coordinate_gradient,
std::numeric_limits<double>::quiet_NaN()},
result
) == mapping::MappingStatus::NON_FINITE_MAPPING_INPUT
);
mfem::DenseMatrix singular_displacement_jacobian(dimension);
singular_displacement_jacobian = 0.0;
CHECK(
compactification.evaluate(
{reference_position, displaced_position, singular_displacement_jacobian, zero_gradient, 1.0 / 3.0}, result
) == mapping::MappingStatus::NON_POSITIVE_DETERMINATE
);
mfem::DenseMatrix inverted_displacement_jacobian = make_identity();
inverted_displacement_jacobian(0, 0) = -1.0;
CHECK(
compactification.evaluate(
{reference_position, displaced_position, inverted_displacement_jacobian, zero_gradient, 1.0 / 3.0}, result
) == mapping::MappingStatus::NON_POSITIVE_DETERMINATE
);
}
TEST_CASE(
"Kelvin Compactification Variation Rejects Invalid Inputs",
tags::unit & tags::kelvin
) {
mapping::compactification::KelvinCompactification compactification({.r_star_ref = 1.0, .r_inf_ref = 4.0});
mfem::Vector reference_position = make_vector(2.0, 0.0, 0.0);
mfem::Vector displaced_position(reference_position);
mfem::Vector coordinate_gradient = make_vector(1.0 / 3.0, 0.0, 0.0);
const mfem::DenseMatrix identity = make_identity();
const mapping::compactification::ExteriorMapInput input{
reference_position, displaced_position, identity, coordinate_gradient, 1.0 / 3.0
};
mapping::compactification::ExteriorMapResult result;
REQUIRE(compactification.evaluate(input, result) == mapping::MappingStatus::VALID);
mfem::Vector valid_position_direction = make_vector(0.01, -0.02, 0.03);
const mfem::DenseMatrix valid_jacobian_direction = make_identity();
mapping::compactification::ExteriorMapVariation variation;
mfem::Vector wrong_dimension(2);
wrong_dimension = 0.0;
CHECK(
compactification.evaluate_variation(input, result, {wrong_dimension, valid_jacobian_direction}, variation) ==
mapping::MappingStatus::INVALID_DIMENSION
);
mfem::DenseMatrix wrong_jacobian_dimension(2);
wrong_jacobian_dimension = 0.0;
CHECK(
compactification.evaluate_variation(
input, result, {valid_position_direction, wrong_jacobian_dimension}, variation
) == mapping::MappingStatus::INVALID_DIMENSION
);
mfem::Vector non_finite_direction(valid_position_direction);
non_finite_direction(0) = std::numeric_limits<double>::quiet_NaN();
CHECK(
compactification.evaluate_variation(
input, result, {non_finite_direction, valid_jacobian_direction}, variation
) == mapping::MappingStatus::NON_FINITE_MAPPING_INPUT
);
}
TEST_CASE(
"Kelvin Compactification Evaluations Are Independent",
tags::unit & tags::kelvin
) {
constexpr double tolerance = 0.0;
mapping::compactification::KelvinCompactification compactification({.r_star_ref = 1.0, .r_inf_ref = 4.0});
const mfem::DenseMatrix identity = make_identity();
mfem::Vector gradient_a = make_vector(0.12, 0.03, -0.02);
mfem::Vector gradient_b = make_vector(-0.04, 0.15, 0.01);
mfem::Vector reference_a = make_vector(1.5, 0.2, 0.1);
mfem::Vector reference_b = make_vector(2.5, -0.3, 0.4);
const mapping::compactification::ExteriorMapInput input_a{reference_a, reference_a, identity, gradient_a, 0.25};
const mapping::compactification::ExteriorMapInput input_b{reference_b, reference_b, identity, gradient_b, 0.70};
mapping::compactification::ExteriorMapResult first_a;
mapping::compactification::ExteriorMapResult result_b;
mapping::compactification::ExteriorMapResult second_a;
REQUIRE(compactification.evaluate(input_a, first_a) == mapping::MappingStatus::VALID);
REQUIRE(compactification.evaluate(input_b, result_b) == mapping::MappingStatus::VALID);
REQUIRE(compactification.evaluate(input_a, second_a) == mapping::MappingStatus::VALID);
check_vector(first_a.physical_position, second_a.physical_position, tolerance);
check_matrix(first_a.mapping_jacobian, second_a.mapping_jacobian, tolerance);
}