feat(libmeanfield): centrifugal + pressure

This commit is contained in:
2026-08-04 14:24:55 -04:00
parent 9bc4f2758a
commit dc912fd15e
115 changed files with 260058 additions and 163261 deletions

View File

@@ -0,0 +1,922 @@
#include <array>
#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_floating_point.hpp>
#include <cmath>
#include <limits>
#include <mfem.hpp>
#include <stdexcept>
import mean_field;
import test_helpers;
using namespace mean_field;
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 = compactification_coordinate,
.compactification_coordinate_gradient =
compactification_coordinate_gradient
};
return exterior_map.Evaluate(input, result);
}
} // namespace
TEST_CASE(
"Kelvin Compactification Validates Its Configuration",
tags::unit &tags::mapping &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::mapping &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.GetName() == "KelvinCompactification");
CHECK_THAT(
compactification.GetReferenceStellarRadius(), WithinAbs(1.25, 0.0)
);
CHECK_THAT(
compactification.GetReferenceInfinityRadius(), WithinAbs(5.5, 0.0)
);
CHECK_THAT(
compactification.GetCoordinateTolerance(),
WithinAbs(coordinate_tolerance, 0.0)
);
}
TEST_CASE(
"Kelvin Compactification Uses The Exterior Coordinate Rather Than "
"Euclidean Radius",
tags::unit &tags::mapping &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();
const mfem::Vector displaced_position = make_vector(1.4, -0.2, 0.3);
const mfem::Vector coordinate_gradient = make_vector(0.2, -0.1, 0.05);
const mfem::Vector reference_a = make_vector(0.2, 0.1, -0.1);
const 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,
coordinate_gradient
};
const mapping::compactification::ExteriorMapInput input_b{
reference_b, displaced_position, displacement_jacobian, coordinate,
coordinate_gradient
};
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::mapping &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();
const 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;
const mfem::Vector reference_position =
make_vector(computational_radius, 0.0, 0.0);
const mfem::Vector displaced_position(reference_position);
const mapping::compactification::ExteriorMapInput input{
reference_position, displaced_position, identity, coordinate,
coordinate_gradient
};
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;
const 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::mapping &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}
);
const mfem::Vector reference_position = make_vector(0.8, 0.4, -0.2);
const mfem::Vector displaced_position = make_vector(1.1, 0.5, -0.1);
const 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, coordinate_gradient
};
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::mapping &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;
const mfem::Vector offset = make_vector(0.04, -0.03, 0.02);
const mfem::Vector reference_position = make_vector(1.4, 0.3, -0.2);
const 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::mapping &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}
);
const mfem::Vector reference_position = make_vector(1.3, -0.2, 0.4);
const mfem::Vector displaced_position = make_vector(1.4, -0.1, 0.35);
const mfem::Vector coordinate_gradient = make_vector(0.12, -0.04, 0.08);
const 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, coordinate_gradient
};
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.EvaluateVariation(
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,
coordinate_gradient
};
const mapping::compactification::ExteriorMapInput input_minus{
reference_position, displaced_minus, jacobian_minus, coordinate,
coordinate_gradient
};
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::mapping &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}
);
const mfem::Vector reference_position = make_vector(1.3, 0.4, -0.2);
const mfem::Vector displaced_position = make_vector(1.4, 0.2, -0.1);
const 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, coordinate_gradient
};
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,
coordinate, rotated_coordinate_gradient
};
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::mapping &tags::kelvin
) {
constexpr double tolerance = 1.0e-14;
mapping::compactification::KelvinCompactification compactification(
{.r_star_ref = 1.0, .r_inf_ref = 4.0}
);
const mfem::Vector reference_position = make_vector(
-0.5260553366425769, 0.5260553366425769, -0.6553163792879153
);
const mfem::Vector displaced_position = make_vector(-0.55, 0.51, -0.63);
const 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, 0.0,
coordinate_gradient
};
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::mapping &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();
const 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;
const mfem::Vector reference_position =
make_vector(computational_radius, 0.0, 0.0);
const mapping::compactification::ExteriorMapInput input{
reference_position, reference_position, identity, coordinate,
coordinate_gradient
};
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)
);
}
const mfem::Vector reference_position = make_vector(r_inf, 0.0, 0.0);
mapping::compactification::ExteriorMapResult result;
CHECK(
compactification.Evaluate(
{reference_position, reference_position, identity, 1.0,
coordinate_gradient},
result
) == mapping::MappingStatus::at_compactified_infinity
);
CHECK(
compactification.Evaluate(
{reference_position, reference_position, identity,
1.0 - 0.5 * coordinate_tolerance, coordinate_gradient},
result
) == mapping::MappingStatus::at_compactified_infinity
);
CHECK(
compactification.Evaluate(
{reference_position, reference_position, identity,
1.0 + 0.5 * coordinate_tolerance, coordinate_gradient},
result
) == mapping::MappingStatus::at_compactified_infinity
);
CHECK(
compactification.Evaluate(
{reference_position, reference_position, identity,
1.0 + 2.0 * coordinate_tolerance, coordinate_gradient},
result
) == mapping::MappingStatus::outside_reference_domain
);
CHECK(
compactification.Evaluate(
{reference_position, reference_position, identity,
-2.0 * coordinate_tolerance, coordinate_gradient},
result
) == mapping::MappingStatus::outside_reference_domain
);
const mfem::Vector surface_position = make_vector(0.97, 0.0, 0.0);
CHECK(
compactification.Evaluate(
{surface_position, surface_position, identity,
-0.5 * coordinate_tolerance, coordinate_gradient},
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::mapping &tags::kelvin
) {
mapping::compactification::KelvinCompactification compactification(
{.r_star_ref = 1.0, .r_inf_ref = 4.0}
);
const mfem::Vector reference_position = make_vector(2.0, 0.0, 0.0);
const mfem::Vector displaced_position(reference_position);
const mfem::Vector coordinate_gradient = make_vector(1.0 / 3.0, 0.0, 0.0);
const 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, 1.0 / 3.0,
coordinate_gradient},
result
) == mapping::MappingStatus::invalid_dimension
);
CHECK(
compactification.Evaluate(
{reference_position, displaced_position, identity, 1.0 / 3.0,
wrong_dimension},
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, 1.0 / 3.0,
coordinate_gradient},
result
) == mapping::MappingStatus::non_finite_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, 1.0 / 3.0,
non_finite_gradient},
result
) == mapping::MappingStatus::non_finite_input
);
CHECK(
compactification.Evaluate(
{reference_position, displaced_position, identity,
std::numeric_limits<double>::quiet_NaN(), coordinate_gradient},
result
) == mapping::MappingStatus::non_finite_input
);
mfem::DenseMatrix singular_displacement_jacobian(dimension);
singular_displacement_jacobian = 0.0;
CHECK(
compactification.Evaluate(
{reference_position, displaced_position,
singular_displacement_jacobian, 1.0 / 3.0, zero_gradient},
result
) == mapping::MappingStatus::non_positive_determinant
);
mfem::DenseMatrix inverted_displacement_jacobian = make_identity();
inverted_displacement_jacobian(0, 0) = -1.0;
CHECK(
compactification.Evaluate(
{reference_position, displaced_position,
inverted_displacement_jacobian, 1.0 / 3.0, zero_gradient},
result
) == mapping::MappingStatus::non_positive_determinant
);
}
TEST_CASE(
"Kelvin Compactification Variation Rejects Invalid Inputs",
tags::unit &tags::mapping &tags::kelvin
) {
mapping::compactification::KelvinCompactification compactification(
{.r_star_ref = 1.0, .r_inf_ref = 4.0}
);
const mfem::Vector reference_position = make_vector(2.0, 0.0, 0.0);
const mfem::Vector displaced_position(reference_position);
const 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, 1.0 / 3.0,
coordinate_gradient
};
mapping::compactification::ExteriorMapResult result;
REQUIRE(
compactification.Evaluate(input, result) ==
mapping::MappingStatus::valid
);
const 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.EvaluateVariation(
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.EvaluateVariation(
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.EvaluateVariation(
input, result, {non_finite_direction, valid_jacobian_direction},
variation
) == mapping::MappingStatus::non_finite_input
);
}
TEST_CASE(
"Kelvin Compactification Evaluations Are Independent",
tags::unit &tags::mapping &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();
const mfem::Vector gradient_a = make_vector(0.12, 0.03, -0.02);
const mfem::Vector gradient_b = make_vector(-0.04, 0.15, 0.01);
const mfem::Vector reference_a = make_vector(1.5, 0.2, 0.1);
const mfem::Vector reference_b = make_vector(2.5, -0.3, 0.4);
const mapping::compactification::ExteriorMapInput input_a{
reference_a, reference_a, identity, 0.25, gradient_a
};
const mapping::compactification::ExteriorMapInput input_b{
reference_b, reference_b, identity, 0.70, gradient_b
};
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
);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,454 @@
#include <array>
#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_floating_point.hpp>
#include <cmath>
#include <limits>
#include <mfem.hpp>
#include <string>
#include <vector>
import mean_field;
import test_helpers;
using namespace mean_field;
using Catch::Matchers::WithinAbs;
namespace {
constexpr int dimension = 3;
mfem::DenseMatrix make_matrix(
const std::array<
double,
9> &values
) {
mfem::DenseMatrix matrix(dimension);
for (int row = 0; row < dimension; ++row) {
for (int column = 0; column < dimension; ++column)
matrix(row, column) = values[row * dimension + column];
}
return matrix;
}
mfem::DenseMatrix make_identity_matrix() {
mfem::DenseMatrix identity(dimension);
identity = 0.0;
for (int i = 0; i < dimension; ++i)
identity(i, i) = 1.0;
return identity;
}
mapping::MappingPointContext
make_context(const mfem::DenseMatrix &jacobian) {
mapping::MappingPointContext context;
context.mapping_jacobian = jacobian;
context.mapping_determinant = jacobian.Det();
context.inverse_mapping_jacobian.SetSize(dimension);
mfem::CalcInverse(jacobian, context.inverse_mapping_jacobian);
context.physical_position.SetSize(dimension);
context.physical_position = 0.0;
return context;
}
double determinant_variation(
const mfem::DenseMatrix &jacobian,
const mfem::DenseMatrix &jacobian_variation
) {
mfem::DenseMatrix inverse_jacobian(dimension);
mfem::DenseMatrix product(dimension);
mfem::CalcInverse(jacobian, inverse_jacobian);
mfem::Mult(inverse_jacobian, jacobian_variation, product);
double trace = 0.0;
for (int i = 0; i < dimension; ++i)
trace += product(i, i);
return jacobian.Det() * trace;
}
mapping::MappingPointVariation make_variation(
const mfem::DenseMatrix &jacobian,
const mfem::DenseMatrix &jacobian_variation
) {
mapping::MappingPointVariation variation;
variation.mapping_jacobian_variation = jacobian_variation;
variation.mapping_determinant_variation =
determinant_variation(jacobian, jacobian_variation);
variation.physical_position_variation.SetSize(dimension);
variation.physical_position_variation = 0.0;
return variation;
}
double matrix_norm(const mfem::DenseMatrix &matrix) {
double norm_squared = 0.0;
for (int row = 0; row < matrix.Height(); ++row) {
for (int column = 0; column < matrix.Width(); ++column)
norm_squared += matrix(row, column) * matrix(row, column);
}
return std::sqrt(norm_squared);
}
double relative_matrix_error(
const mfem::DenseMatrix &computed,
const mfem::DenseMatrix &reference
) {
REQUIRE(computed.Height() == reference.Height());
REQUIRE(computed.Width() == reference.Width());
mfem::DenseMatrix difference(computed);
difference -= reference;
return matrix_norm(difference) /
std::max(
matrix_norm(reference),
std::numeric_limits<double>::epsilon()
);
}
double matrix_asymmetry(const mfem::DenseMatrix &matrix) {
double asymmetry_squared = 0.0;
for (int row = 0; row < matrix.Height(); ++row) {
for (int column = 0; column < matrix.Width(); ++column) {
const double difference =
matrix(row, column) - matrix(column, row);
asymmetry_squared += difference * difference;
}
}
return std::sqrt(asymmetry_squared);
}
mfem::DenseMatrix centered_mass_tensor_difference(
const mfem::DenseMatrix &jacobian,
const mfem::DenseMatrix &jacobian_variation,
const double step
) {
mfem::DenseMatrix plus_jacobian(jacobian);
mfem::DenseMatrix minus_jacobian(jacobian);
for (int row = 0; row < dimension; ++row) {
for (int column = 0; column < dimension; ++column) {
plus_jacobian(row, column) +=
step * jacobian_variation(row, column);
minus_jacobian(row, column) -=
step * jacobian_variation(row, column);
}
}
REQUIRE(plus_jacobian.Det() > 0.0);
REQUIRE(minus_jacobian.Det() > 0.0);
const mapping::MappingPointContext plus_context =
make_context(plus_jacobian);
const mapping::MappingPointContext minus_context =
make_context(minus_jacobian);
mfem::DenseMatrix plus_tensor;
mfem::DenseMatrix minus_tensor;
mapping::ComputeHDivMassTensor(plus_context, plus_tensor);
mapping::ComputeHDivMassTensor(minus_context, minus_tensor);
plus_tensor -= minus_tensor;
plus_tensor *= 1 / (2.0 * step);
return plus_tensor;
}
void check_zero_matrix(
const mfem::DenseMatrix &matrix,
const double tolerance
) {
for (int row = 0; row < matrix.Height(); ++row) {
for (int column = 0; column < matrix.Width(); ++column)
CHECK_THAT(matrix(row, column), WithinAbs(0.0, tolerance));
}
}
struct TensorVariationCase {
std::string name;
mfem::DenseMatrix jacobian;
mfem::DenseMatrix jacobian_variation;
};
} // namespace
TEST_CASE(
"Hdiv Mass Tensor Variation Matches Centered Differences",
tags::unit &tags::transformations
) {
std::vector<TensorVariationCase> cases;
cases.push_back(
{"identity with general variation", make_identity_matrix(),
make_matrix({0.12, -0.07, 0.03, 0.05, -0.09, 0.04, -0.02, 0.08, 0.06})}
);
cases.push_back(
{"anisotropic stretch",
make_matrix({1.20, 0.00, 0.00, 0.00, 0.85, 0.00, 0.00, 0.00, 1.10}),
make_matrix({0.08, 0.01, -0.03, 0.02, -0.05, 0.04, 0.01, -0.02, 0.07})}
);
cases.push_back(
{"sheared mapping",
make_matrix({1.10, 0.20, -0.05, 0.04, 0.90, 0.12, -0.03, 0.08, 1.15}),
make_matrix(
{0.06, -0.04, 0.02, 0.03, 0.05, -0.07, -0.01, 0.04, -0.02}
)}
);
cases.push_back(
{"strong general mapping",
make_matrix({1.35, 0.31, -0.18, -0.12, 0.78, 0.22, 0.09, -0.16, 1.27}),
make_matrix({-0.11, 0.08, 0.05, 0.07, 0.09, -0.04, -0.06, 0.03, 0.12})}
);
for (const TensorVariationCase &test_case : cases) {
DYNAMIC_SECTION(test_case.name) {
REQUIRE(test_case.jacobian.Det() > 0.0);
const mapping::MappingPointContext context =
make_context(test_case.jacobian);
const mapping::MappingPointVariation variation = make_variation(
test_case.jacobian, test_case.jacobian_variation
);
mfem::DenseMatrix analytic_variation;
mapping::ComputeHDivMassTensorVariation(
context, variation, analytic_variation
);
const mfem::DenseMatrix finite_difference =
centered_mass_tensor_difference(
test_case.jacobian, test_case.jacobian_variation, 1.0e-6
);
const double relative_error =
relative_matrix_error(analytic_variation, finite_difference);
const double asymmetry = matrix_asymmetry(analytic_variation);
INFO("Mapping determinant = " << context.mapping_determinant);
INFO(
"Determinant variation = "
<< variation.mapping_determinant_variation
);
INFO(
"Analytic variation norm = " << matrix_norm(analytic_variation)
);
INFO(
"Finite-difference variation norm = "
<< matrix_norm(finite_difference)
);
INFO("Relative tensor-variation error = " << relative_error);
INFO("Tensor-variation asymmetry = " << asymmetry);
CHECK(relative_error < 2.0e-9);
CHECK(asymmetry < 2.0e-14);
}
}
}
TEST_CASE(
"Hdiv Mass Tensor Variation Has Second Order Centered Difference "
"Convergence",
tags::unit &tags::transformations &tags::convergence
) {
const mfem::DenseMatrix jacobian =
make_matrix({1.18, 0.17, -0.09, -0.04, 0.92, 0.14, 0.07, -0.11, 1.23});
const mfem::DenseMatrix jacobian_variation =
make_matrix({0.09, -0.06, 0.04, 0.03, 0.07, -0.05, -0.02, 0.08, -0.03});
const mapping::MappingPointContext context = make_context(jacobian);
const mapping::MappingPointVariation variation =
make_variation(jacobian, jacobian_variation);
mfem::DenseMatrix analytic_variation;
mapping::ComputeHDivMassTensorVariation(
context, variation, analytic_variation
);
const std::array<double, 3> steps{4.0e-2, 2.0e-2, 1.0e-2};
std::array<double, 3> errors{};
for (int i = 0; i < static_cast<int>(steps.size()); ++i) {
const mfem::DenseMatrix finite_difference =
centered_mass_tensor_difference(
jacobian, jacobian_variation, steps[i]
);
errors[i] =
relative_matrix_error(finite_difference, analytic_variation);
INFO("Step = " << steps[i] << ", relative error = " << errors[i]);
}
const double first_reduction = errors[1] / errors[0];
const double second_reduction = errors[2] / errors[1];
INFO("First error-reduction ratio = " << first_reduction);
INFO("Second error-reduction ratio = " << second_reduction);
CHECK(first_reduction < 0.30);
CHECK(second_reduction < 0.30);
CHECK(errors[2] < 1.0e-5);
}
TEST_CASE(
"Hdiv Mass Tensor Variation Vanishes For Translation",
tags::unit &tags::transformations
) {
const mfem::DenseMatrix jacobian =
make_matrix({1.12, 0.08, -0.03, 0.02, 0.94, 0.07, -0.01, 0.05, 1.09});
mfem::DenseMatrix zero_jacobian_variation(dimension);
zero_jacobian_variation = 0.0;
mapping::MappingPointContext context = make_context(jacobian);
mapping::MappingPointVariation variation =
make_variation(jacobian, zero_jacobian_variation);
variation.physical_position_variation.SetSize(dimension);
variation.physical_position_variation(0) = 0.7;
variation.physical_position_variation(1) = -0.4;
variation.physical_position_variation(2) = 0.9;
mfem::DenseMatrix tensor_variation;
mapping::ComputeHDivMassTensorVariation(
context, variation, tensor_variation
);
CHECK_THAT(variation.mapping_determinant_variation, WithinAbs(0.0, 0.0));
check_zero_matrix(tensor_variation, 1.0e-14);
}
TEST_CASE(
"Hdiv Mass Tensor Variation Vanishes For Infinitesimal Rotation At "
"Identity",
tags::unit &tags::transformations
) {
const mfem::DenseMatrix identity = make_identity_matrix();
const mfem::DenseMatrix rotation_variation =
make_matrix({0.0, -0.30, 0.20, 0.30, 0.0, -0.15, -0.20, 0.15, 0.0});
const mapping::MappingPointContext context = make_context(identity);
const mapping::MappingPointVariation variation =
make_variation(identity, rotation_variation);
mfem::DenseMatrix tensor_variation;
mapping::ComputeHDivMassTensorVariation(
context, variation, tensor_variation
);
CHECK_THAT(
variation.mapping_determinant_variation, WithinAbs(0.0, 1.0e-15)
);
check_zero_matrix(tensor_variation, 1.0e-14);
}
TEST_CASE(
"Hdiv Mass Tensor Variation Matches Isotropic Scaling At Identity",
tags::unit &tags::transformations
) {
constexpr double scaling_variation = 0.17;
const mfem::DenseMatrix identity = make_identity_matrix();
mfem::DenseMatrix jacobian_variation(dimension);
jacobian_variation = 0.0;
for (int i = 0; i < dimension; ++i)
jacobian_variation(i, i) = scaling_variation;
const mapping::MappingPointContext context = make_context(identity);
const mapping::MappingPointVariation variation =
make_variation(identity, jacobian_variation);
mfem::DenseMatrix tensor_variation;
mapping::ComputeHDivMassTensorVariation(
context, variation, tensor_variation
);
CHECK_THAT(
variation.mapping_determinant_variation,
WithinAbs(3.0 * scaling_variation, 1.0e-14)
);
for (int row = 0; row < dimension; ++row) {
for (int column = 0; column < dimension; ++column) {
const double expected = row == column ? -scaling_variation : 0.0;
CHECK_THAT(
tensor_variation(row, column), WithinAbs(expected, 1.0e-14)
);
}
}
}
TEST_CASE(
"Hdiv Mass Tensor Variation Symmetrizes Simple Shear At Identity",
tags::unit &tags::transformations
) {
constexpr double shear_variation = 0.23;
const mfem::DenseMatrix identity = make_identity_matrix();
mfem::DenseMatrix jacobian_variation(dimension);
jacobian_variation = 0.0;
jacobian_variation(0, 1) = shear_variation;
const mapping::MappingPointContext context = make_context(identity);
const mapping::MappingPointVariation variation =
make_variation(identity, jacobian_variation);
mfem::DenseMatrix tensor_variation;
mapping::ComputeHDivMassTensorVariation(
context, variation, tensor_variation
);
CHECK_THAT(
variation.mapping_determinant_variation, WithinAbs(0.0, 1.0e-15)
);
CHECK_THAT(tensor_variation(0, 1), WithinAbs(shear_variation, 1.0e-14));
CHECK_THAT(tensor_variation(1, 0), WithinAbs(shear_variation, 1.0e-14));
for (int row = 0; row < dimension; ++row) {
for (int column = 0; column < dimension; ++column) {
if ((row == 0 && column == 1) || (row == 1 && column == 0))
continue;
CHECK_THAT(tensor_variation(row, column), WithinAbs(0.0, 1.0e-14));
}
}
}
TEST_CASE(
"Mapping Determinant Variation Matches Jacobi Formula",
tags::unit &tags::transformations
) {
const mfem::DenseMatrix jacobian =
make_matrix({1.24, 0.19, -0.07, -0.06, 0.88, 0.16, 0.04, -0.12, 1.19});
const mfem::DenseMatrix jacobian_variation =
make_matrix({0.08, -0.03, 0.05, 0.02, 0.06, -0.04, -0.01, 0.07, -0.02});
const mapping::MappingPointContext context = make_context(jacobian);
const mapping::MappingPointVariation variation =
make_variation(jacobian, jacobian_variation);
constexpr double difference_step = 1.0e-3;
mfem::DenseMatrix plus_one(context.mapping_jacobian);
mfem::DenseMatrix plus_two(context.mapping_jacobian);
mfem::DenseMatrix minus_one(context.mapping_jacobian);
mfem::DenseMatrix minus_two(context.mapping_jacobian);
plus_one.Add(difference_step, variation.mapping_jacobian_variation);
plus_two.Add(2.0 * difference_step, variation.mapping_jacobian_variation);
minus_one.Add(-difference_step, variation.mapping_jacobian_variation);
minus_two.Add(-2.0 * difference_step, variation.mapping_jacobian_variation);
const double finite_difference = (minus_two.Det() - 8.0 * minus_one.Det() +
8.0 * plus_one.Det() - plus_two.Det()) /
(12.0 * difference_step);
const double analytic = variation.mapping_determinant_variation;
const double relative_error = std::abs(finite_difference - analytic) /
std::max(std::abs(analytic), 1.0e-14);
INFO("Analytic determinant variation = " << analytic);
INFO("Finite-difference determinant variation = " << finite_difference);
INFO("Relative determinant-variation error = " << relative_error);
CHECK(relative_error < 2.0e-11);
}