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:
423
tests/utils/projection/radial.cpp
Normal file
423
tests/utils/projection/radial.cpp
Normal file
@@ -0,0 +1,423 @@
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <expected>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <numbers>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include "mfem/fem/coefficient.hpp"
|
||||
#include "serif/backend/mfem/mapping/compactification/kelvin.hpp"
|
||||
#include "serif/dimensions/type_alias.hpp"
|
||||
#include "serif/backend/mfem/mapping/evaluator.hpp"
|
||||
#include "serif/utils/laneemden/laneemden.hpp"
|
||||
#include "serif/utils/projection/projection.hpp"
|
||||
#include "serif/tests/deformation/mesh.hpp"
|
||||
#include "serif/backend/mfem/mapping/transformations.hpp"
|
||||
|
||||
namespace {
|
||||
namespace projection = serif::utils::projection;
|
||||
namespace backend = projection::backend::mfem;
|
||||
namespace laneemden = serif::utils::laneemden;
|
||||
namespace dimensions = serif::dimensions;
|
||||
namespace mapping = serif::mapping;
|
||||
|
||||
/** @brief Resolve radius without sharing the production sampler's distance implementation. */
|
||||
[[nodiscard]] double radius_squared(const mfem::Vector& point, const std::array<double, 3>& center) {
|
||||
double squared = 0.0;
|
||||
for (int component = 0; component < 3; ++component) {
|
||||
squared += std::pow(point[component] - center[component], 2);
|
||||
}
|
||||
return squared;
|
||||
}
|
||||
|
||||
/** @brief Check physical reconstruction through independent quadrature. */
|
||||
void check_linear_vector(mfem::FiniteElementSpace& space, const std::array<double, 3>& center) {
|
||||
const auto result = backend::project_vector(space, [](const double radius) { return 2.0 * radius; }, {.center = center});
|
||||
REQUIRE(result);
|
||||
mfem::VectorFunctionCoefficient exact(3, [center](const mfem::Vector& point, mfem::Vector& value) {
|
||||
value.SetSize(3);
|
||||
for (int component = 0; component < 3; ++component) {
|
||||
value[component] = 2.0 * (point[component] - center[component]);
|
||||
}
|
||||
});
|
||||
CHECK(result->ComputeL2Error(exact) < 2.0e-12);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Radial scalar projection reproduces physical polynomials in H1 and L2", "[unit][projection]") {
|
||||
const std::array<double, 3> center{0.5, 0.25, 0.75};
|
||||
for (const mfem::Element::Type geometry : {mfem::Element::HEXAHEDRON, mfem::Element::TETRAHEDRON}) {
|
||||
mfem::Mesh mesh = mfem::Mesh::MakeCartesian3D(2, 2, 2, geometry);
|
||||
mfem::H1_FECollection h1(2, 3);
|
||||
mfem::L2_FECollection l2(2, 3);
|
||||
mfem::L2_FECollection integral_l2(2, 3, mfem::BasisType::GaussLegendre, mfem::FiniteElement::INTEGRAL);
|
||||
for (mfem::FiniteElementCollection* collection : std::array<mfem::FiniteElementCollection*, 3>{&h1, &l2, &integral_l2}) {
|
||||
mfem::FiniteElementSpace space(&mesh, collection);
|
||||
const auto result = backend::project_scalar(space, [](const double radius) {
|
||||
return 3.0 - radius * radius;
|
||||
}, {.center = center});
|
||||
REQUIRE(result);
|
||||
CHECK(result->FESpace() == &space);
|
||||
mfem::FunctionCoefficient exact([center](const mfem::Vector& point) {
|
||||
return 3.0 - radius_squared(point, center);
|
||||
});
|
||||
CHECK(result->ComputeL2Error(exact) < 2.0e-12);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Radial vectors preserve component order", "[unit][projection]") {
|
||||
for (const mfem::Element::Type geometry : {mfem::Element::HEXAHEDRON, mfem::Element::TETRAHEDRON}) {
|
||||
mfem::Mesh mesh = mfem::Mesh::MakeCartesian3D(2, 2, 2, geometry);
|
||||
mfem::H1_FECollection h1(1, 3);
|
||||
mfem::L2_FECollection l2(1, 3);
|
||||
for (const mfem::Ordering::Type order : {mfem::Ordering::byNODES, mfem::Ordering::byVDIM}) {
|
||||
mfem::FiniteElementSpace continuous(&mesh, &h1, 3, order);
|
||||
mfem::FiniteElementSpace discontinuous(&mesh, &l2, 3, order);
|
||||
check_linear_vector(continuous, {0.5, 0.5, 0.5});
|
||||
check_linear_vector(continuous, {0.0, 0.0, 0.0});
|
||||
check_linear_vector(discontinuous, {0.5, 0.5, 0.5});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Radial projection samples curved physical mesh coordinates", "[unit][projection]") {
|
||||
mfem::Mesh mesh = mfem::Mesh::MakeCartesian3D(2, 2, 2, mfem::Element::HEXAHEDRON);
|
||||
mesh.SetCurvature(2);
|
||||
mesh.Transform([](const mfem::Vector& point, mfem::Vector& image) {
|
||||
image = point;
|
||||
image[1] += 0.1 * point[0] * point[0];
|
||||
image[2] *= 1.0 + 0.1 * point[0];
|
||||
});
|
||||
mfem::H1_FECollection h1(4, 3);
|
||||
mfem::FiniteElementSpace space(&mesh, &h1);
|
||||
const auto result = backend::project_scalar(space, [](const double radius) { return radius * radius; });
|
||||
REQUIRE(result);
|
||||
mfem::FunctionCoefficient exact([](const mfem::Vector& point) { return point * point; });
|
||||
CHECK(result->ComputeL2Error(exact) < 3.0e-12);
|
||||
}
|
||||
|
||||
TEST_CASE("Radial projection pulls physical values back through stellar mapping", "[unit][projection]") {
|
||||
mfem::Mesh mesh = mfem::Mesh::MakeCartesian3D(2, 2, 2, mfem::Element::HEXAHEDRON);
|
||||
mfem::H1_FECollection h1(2, 3);
|
||||
mfem::FiniteElementSpace displacement_space(&mesh, &h1, 3);
|
||||
mfem::FiniteElementSpace scalar_space(&mesh, &h1);
|
||||
mfem::GridFunction displacement(&displacement_space);
|
||||
mfem::GridFunction compactification(&scalar_space);
|
||||
const std::array<double, 3> stretch{1.2, 0.8, 1.1};
|
||||
mfem::VectorFunctionCoefficient affine(3, [stretch](const mfem::Vector& point, mfem::Vector& value) {
|
||||
value.SetSize(3);
|
||||
for (int component = 0; component < 3; ++component) {
|
||||
value[component] = (stretch[component] - 1.0) * point[component];
|
||||
}
|
||||
});
|
||||
displacement.ProjectCoefficient(affine);
|
||||
compactification = 0.0;
|
||||
mapping::DomainMapper mapper(
|
||||
{.dimension = 3, .vacuum_element_attribute_ID = 3},
|
||||
std::make_unique<mapping::compactification::KelvinCompactification>(
|
||||
serif::utils::user_options::KelvinCompactificationOptions{.r_star_ref = 1.0, .r_inf_ref = 4.0}
|
||||
)
|
||||
);
|
||||
mapping::GridFunctionMappingEvaluator evaluator(mapper, displacement, compactification);
|
||||
CHECK(evaluator.mesh() == &mesh);
|
||||
// Equal element counts do not make geometry from another mesh interchangeable.
|
||||
mfem::Mesh other_mesh = mfem::Mesh::MakeCartesian3D(2, 2, 2, mfem::Element::HEXAHEDRON);
|
||||
mfem::FiniteElementSpace other_scalar(&other_mesh, &h1), other_vector(&other_mesh, &h1, 3);
|
||||
CHECK_THROWS_AS(backend::project_scalar(other_scalar, [](const double r) { return r; }, {.mapping = &evaluator}), std::invalid_argument);
|
||||
CHECK_THROWS_AS(backend::project_vector(other_vector, [](const double r) { return r; }, {.mapping = &evaluator}), std::invalid_argument);
|
||||
const auto scalar = backend::project_scalar(scalar_space, [](const double radius) { return radius * radius; }, {.mapping = &evaluator});
|
||||
REQUIRE(scalar);
|
||||
mfem::FunctionCoefficient exact_scalar([stretch](const mfem::Vector& point) {
|
||||
double value = 0.0;
|
||||
for (int component = 0; component < 3; ++component) {
|
||||
value += std::pow(stretch[component] * point[component], 2);
|
||||
}
|
||||
return value;
|
||||
});
|
||||
CHECK(scalar->ComputeL2Error(exact_scalar) < 3.0e-12);
|
||||
|
||||
const auto vector = backend::project_vector(displacement_space, [](const double radius) { return 2.0 * radius; }, {.mapping = &evaluator});
|
||||
REQUIRE(vector);
|
||||
// Vector H1 values are physical: 2 x_phys = 2 A x_mesh.
|
||||
mfem::VectorFunctionCoefficient exact_vector(3, [stretch](const mfem::Vector& point, mfem::Vector& value) {
|
||||
value.SetSize(3);
|
||||
for (int component = 0; component < 3; ++component) {
|
||||
value[component] = 2.0 * stretch[component] * point[component];
|
||||
}
|
||||
});
|
||||
CHECK(vector->ComputeL2Error(exact_vector) < 3.0e-12);
|
||||
const double determinant = stretch[0] * stretch[1] * stretch[2];
|
||||
|
||||
mfem::L2_FECollection integral_l2(2, 3, mfem::BasisType::GaussLegendre, mfem::FiniteElement::INTEGRAL);
|
||||
mfem::FiniteElementSpace density_space(&mesh, &integral_l2);
|
||||
const auto density = backend::project_scalar(density_space, [](const double) { return 2.0; }, {.mapping = &evaluator});
|
||||
REQUIRE(density);
|
||||
mfem::ConstantCoefficient exact_density(2.0 * determinant);
|
||||
CHECK(density->ComputeL2Error(exact_density) < 3.0e-12);
|
||||
|
||||
displacement *= -6.0;
|
||||
evaluator.refresh();
|
||||
CHECK_FALSE(backend::project_scalar(scalar_space, [](const double radius) { return radius; }, {.mapping = &evaluator}));
|
||||
|
||||
displacement = 0.0;
|
||||
compactification = 1.0;
|
||||
for (int element = 0; element < mesh.GetNE(); ++element) {
|
||||
mesh.GetElement(element)->SetAttribute(3);
|
||||
}
|
||||
mesh.SetAttributes();
|
||||
evaluator.refresh();
|
||||
const auto infinite = backend::project_scalar(scalar_space, [](const double) { return 0.0; }, {.mapping = &evaluator});
|
||||
REQUIRE_FALSE(infinite);
|
||||
CHECK(infinite.error().find("compactified infinity") != std::string::npos);
|
||||
}
|
||||
|
||||
TEST_CASE("Lane Emden physical profiles project density enthalpy potential and gravity", "[unit][projection][laneemden]") {
|
||||
const auto solution = laneemden::solve(0.0);
|
||||
REQUIRE(solution);
|
||||
const auto scales = laneemden::scale_from_mass(*solution,
|
||||
dimensions::QuantityValue<dimensions::Mass>{3.0},
|
||||
dimensions::QuantityValue<dimensions::MassDensity>{2.0},
|
||||
dimensions::QuantityValue<dimensions::GravitationalConstant>{1.5});
|
||||
REQUIRE(scales);
|
||||
const double radius = scales->radius.value();
|
||||
const double central_enthalpy = scales->central_enthalpy.value();
|
||||
const double potential_at_surface = -1.5 * 3.0 / radius;
|
||||
const double extent = 0.5 * radius;
|
||||
mfem::Mesh mesh = mfem::Mesh::MakeCartesian3D(2, 2, 2, mfem::Element::HEXAHEDRON, extent, extent, extent);
|
||||
mfem::H1_FECollection h1(2, 3);
|
||||
mfem::L2_FECollection l2(2, 3);
|
||||
mfem::FiniteElementSpace enthalpy_space(&mesh, &h1), density_space(&mesh, &l2), gravity_space(&mesh, &h1, 3);
|
||||
const auto field = [&](const double physical_radius) {
|
||||
return laneemden::evaluate_physical(*solution, *scales, dimensions::QuantityValue<dimensions::Length>{physical_radius});
|
||||
};
|
||||
const auto density = backend::project_scalar(density_space, [&](const double r) -> projection::RadialValue {
|
||||
return field(r).transform([](const auto& value) { return value.density.value(); });
|
||||
});
|
||||
const auto enthalpy = backend::project_scalar(enthalpy_space, [&](const double r) -> projection::RadialValue {
|
||||
return field(r).transform([](const auto& value) { return value.enthalpy.value(); });
|
||||
});
|
||||
const auto potential = backend::project_scalar(density_space, [&](const double r) -> projection::RadialValue {
|
||||
return field(r).transform([](const auto& value) { return value.potential.value(); });
|
||||
});
|
||||
const auto gravity = backend::project_vector(gravity_space, [&](const double r) -> projection::RadialValue {
|
||||
return field(r).transform([](const auto& value) { return value.gravity.value(); });
|
||||
});
|
||||
REQUIRE(density);
|
||||
REQUIRE(enthalpy);
|
||||
REQUIRE(potential);
|
||||
REQUIRE(gravity);
|
||||
mfem::ConstantCoefficient exact_density(2.0);
|
||||
mfem::FunctionCoefficient exact_enthalpy([=](const mfem::Vector& point) {
|
||||
return central_enthalpy * (1.0 - (point * point) / (radius * radius));
|
||||
});
|
||||
mfem::FunctionCoefficient exact_potential([=](const mfem::Vector& point) {
|
||||
return potential_at_surface - central_enthalpy * (1.0 - (point * point) / (radius * radius));
|
||||
});
|
||||
mfem::VectorFunctionCoefficient exact_gravity(3, [](const mfem::Vector& point, mfem::Vector& value) {
|
||||
value = point;
|
||||
value *= 4.0 * std::numbers::pi * 1.5 * 2.0 / 3.0;
|
||||
});
|
||||
CHECK(density->ComputeL2Error(exact_density) < 1.0e-11);
|
||||
CHECK(enthalpy->ComputeL2Error(exact_enthalpy) < 1.0e-9);
|
||||
CHECK(potential->ComputeL2Error(exact_potential) < 1.0e-9);
|
||||
CHECK(gravity->ComputeL2Error(exact_gravity) < 1.0e-9);
|
||||
}
|
||||
|
||||
TEST_CASE("Projection retains profile exterior choices and rejects invalid samples atomically", "[unit][projection]") {
|
||||
mfem::Mesh mesh = mfem::Mesh::MakeCartesian3D(2, 2, 2, mfem::Element::HEXAHEDRON);
|
||||
mfem::H1_FECollection h1(1, 3);
|
||||
mfem::FiniteElementSpace scalar(&mesh, &h1), vector(&mesh, &h1, 3);
|
||||
const auto exterior = backend::project_scalar(scalar, [](const double radius) {
|
||||
return radius > 0.25 ? -7.0 : 3.0;
|
||||
});
|
||||
REQUIRE(exterior);
|
||||
CHECK(exterior->Min() == -7.0);
|
||||
CHECK(exterior->Max() == 3.0);
|
||||
for (const double invalid : {std::numeric_limits<double>::quiet_NaN(), std::numeric_limits<double>::infinity()}) {
|
||||
CHECK_FALSE(backend::project_scalar(scalar, [invalid](const double) { return invalid; }));
|
||||
CHECK_FALSE(backend::project_vector(vector, [invalid](const double) { return invalid; }));
|
||||
CHECK_FALSE(backend::project_scalar(scalar, [](const double r) { return r; }, {.center = {invalid, 0.0, 0.0}}));
|
||||
}
|
||||
CHECK_FALSE(backend::project_vector(vector, [](const double) { return 1.0; }));
|
||||
int successful_samples = 0;
|
||||
const auto failure = backend::project_scalar(scalar, [&](const double r) -> projection::RadialValue {
|
||||
if (r > 0.5) {
|
||||
return std::unexpected("profile is only available inside radius 0.5");
|
||||
}
|
||||
++successful_samples;
|
||||
return 2.0;
|
||||
});
|
||||
REQUIRE_FALSE(failure);
|
||||
CHECK(successful_samples > 0);
|
||||
CHECK(failure.error().find("profile is only available") != std::string::npos);
|
||||
const auto recovered = backend::project_scalar(scalar, [](const double) { return 4.0; });
|
||||
REQUIRE(recovered);
|
||||
CHECK(recovered->Min() == 4.0);
|
||||
CHECK(recovered->Max() == 4.0);
|
||||
CHECK_THROWS_AS(backend::project_scalar(vector, [](const double r) { return r; }), std::invalid_argument);
|
||||
CHECK_THROWS_AS(backend::project_vector(scalar, [](const double r) { return r; }), std::invalid_argument);
|
||||
}
|
||||
|
||||
TEST_CASE("Projected n one polytropes converge to their analytic radial fields", "[unit][projection][laneemden]") {
|
||||
const auto solution = laneemden::solve(1.0, {.step = 0.002});
|
||||
REQUIRE(solution);
|
||||
const auto scales = laneemden::scale_from_mass(*solution,
|
||||
dimensions::QuantityValue<dimensions::Mass>{2.0},
|
||||
dimensions::QuantityValue<dimensions::MassDensity>{1.0},
|
||||
dimensions::QuantityValue<dimensions::GravitationalConstant>{1.0});
|
||||
REQUIRE(scales);
|
||||
const double radial_scale = scales->radial_scale;
|
||||
double coarse_error = 0.0;
|
||||
for (const int count : {2, 4}) {
|
||||
const double extent = scales->radius.value() / std::sqrt(3.0);
|
||||
mfem::Mesh mesh = mfem::Mesh::MakeCartesian3D(count, count, count, mfem::Element::HEXAHEDRON, extent, extent, extent);
|
||||
mfem::H1_FECollection h1(2, 3);
|
||||
mfem::FiniteElementSpace space(&mesh, &h1);
|
||||
const auto density = backend::project_scalar(space, [&](const double radius) -> projection::RadialValue {
|
||||
return laneemden::evaluate_physical(*solution, *scales, dimensions::QuantityValue<dimensions::Length>{radius})
|
||||
.transform([](const auto& value) { return value.density.value(); });
|
||||
});
|
||||
REQUIRE(density);
|
||||
mfem::FunctionCoefficient exact([radial_scale](const mfem::Vector& point) {
|
||||
const double xi = point.Norml2() / radial_scale;
|
||||
if (xi == 0.0) {
|
||||
return 1.0;
|
||||
}
|
||||
return std::sin(xi) / xi;
|
||||
});
|
||||
const double error = density->ComputeL2Error(exact);
|
||||
if (count == 2) {
|
||||
coarse_error = error;
|
||||
CHECK(coarse_error > 1.0e-7);
|
||||
} else {
|
||||
CHECK(error < coarse_error / 6.0);
|
||||
CHECK(error < 2.0e-4 * std::pow(extent, 1.5));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Radial projection completes the compactified stellar mesh with explicit finite limits", "[unit][projection][laneemden][infinity]") {
|
||||
auto generated = serif::tests::deformation::make_mesh(0);
|
||||
mfem::H1_FECollection h1(2, 3);
|
||||
mfem::FiniteElementSpace scalar_space(generated.mesh.get(), &h1);
|
||||
mfem::FiniteElementSpace vector_space(generated.mesh.get(), &h1, 3);
|
||||
mfem::GridFunction displacement(&vector_space);
|
||||
displacement = 0.0;
|
||||
mapping::DomainMapper mapper(
|
||||
{.dimension = 3, .vacuum_element_attribute_ID = 3},
|
||||
std::make_unique<mapping::compactification::KelvinCompactification>(
|
||||
serif::utils::user_options::KelvinCompactificationOptions{.r_star_ref = 1.0, .r_inf_ref = 4.0}
|
||||
)
|
||||
);
|
||||
mapping::GridFunctionMappingEvaluator evaluator(mapper, displacement, *generated.exterior_coordinate->values);
|
||||
const auto solution = laneemden::solve(1.0, {.step = 0.002});
|
||||
REQUIRE(solution);
|
||||
// R = 1/2 leaves the entire curved exterior mesh in the analytic vacuum region.
|
||||
const auto scales = laneemden::scale_from_mass(*solution,
|
||||
dimensions::MassValue{2.0}, dimensions::DensityValue{4.0 * std::numbers::pi},
|
||||
dimensions::QuantityValue<dimensions::GravitationalConstant>{0.7});
|
||||
REQUIRE(scales);
|
||||
const double gravitational_mass = 0.7 * scales->mass.value();
|
||||
const auto fields = [&](double radius) {
|
||||
return laneemden::evaluate_physical(*solution, *scales, dimensions::QuantityValue<dimensions::Length>{radius});
|
||||
};
|
||||
int scalar_limits = 0;
|
||||
int vector_limits = 0;
|
||||
const auto scalar_limit = [&](const mfem::Vector&) -> projection::RadialValue {
|
||||
++scalar_limits;
|
||||
return 0.0;
|
||||
};
|
||||
const auto vector_limit = [&](const mfem::Vector& point) -> std::expected<mfem::Vector, std::string> {
|
||||
++vector_limits;
|
||||
mfem::Vector value(point);
|
||||
value *= gravitational_mass / std::pow(point.Norml2(), 3);
|
||||
return value;
|
||||
};
|
||||
const auto potential = backend::project_scalar(scalar_space,
|
||||
[&](double radius) -> projection::RadialValue {
|
||||
return fields(radius).transform([](const laneemden::PhysicalSample& sample) { return sample.potential.value(); });
|
||||
}, {.mapping = &evaluator, .scalar_at_infinity = scalar_limit});
|
||||
const auto gravity = backend::project_vector(vector_space,
|
||||
[&](double radius) -> projection::RadialValue {
|
||||
return fields(radius).transform([](const laneemden::PhysicalSample& sample) { return sample.gravity.value(); });
|
||||
}, {.mapping = &evaluator, .vector_at_infinity = vector_limit});
|
||||
REQUIRE(potential);
|
||||
REQUIRE(gravity);
|
||||
CHECK(scalar_limits > 0);
|
||||
CHECK(vector_limits > 0);
|
||||
mfem::Array<int> infinity_marker(generated.mesh->bdr_attributes.Max());
|
||||
infinity_marker = 0;
|
||||
infinity_marker[serif::tests::deformation::PairedMesh::infinity_attribute - 1] = 1;
|
||||
mfem::Array<int> infinity_dofs;
|
||||
scalar_space.GetEssentialTrueDofs(infinity_marker, infinity_dofs);
|
||||
REQUIRE(infinity_dofs.Size() > 0);
|
||||
for (const int dof : infinity_dofs) {
|
||||
CHECK((*potential)[dof] == 0.0);
|
||||
}
|
||||
|
||||
CHECK(gravity->Norml2() > 0.0);
|
||||
}
|
||||
|
||||
TEST_CASE("Compactified projection validates explicit limits before publishing", "[unit][projection][infinity]") {
|
||||
mfem::Mesh mesh = mfem::Mesh::MakeCartesian3D(1, 1, 1, mfem::Element::HEXAHEDRON);
|
||||
mesh.GetElement(0)->SetAttribute(3);
|
||||
mesh.SetAttributes();
|
||||
mfem::H1_FECollection h1(1, 3);
|
||||
mfem::FiniteElementSpace scalar_space(&mesh, &h1), vector_space(&mesh, &h1, 3);
|
||||
mfem::GridFunction displacement(&vector_space), compactification(&scalar_space);
|
||||
displacement = 0.0;
|
||||
compactification = 1.0;
|
||||
mapping::DomainMapper mapper(
|
||||
{.dimension = 3, .vacuum_element_attribute_ID = 3},
|
||||
std::make_unique<mapping::compactification::KelvinCompactification>(
|
||||
serif::utils::user_options::KelvinCompactificationOptions{.r_star_ref = 1.0, .r_inf_ref = 4.0}
|
||||
)
|
||||
);
|
||||
mapping::GridFunctionMappingEvaluator evaluator(mapper, displacement, compactification);
|
||||
int ordinary_samples = 0;
|
||||
const auto profile = [&](double) {
|
||||
++ordinary_samples;
|
||||
return 1.0;
|
||||
};
|
||||
CHECK_FALSE(backend::project_scalar(scalar_space, profile, {.mapping = &evaluator}));
|
||||
CHECK_FALSE(backend::project_vector(vector_space, profile, {.mapping = &evaluator}));
|
||||
for (double invalid : {std::numeric_limits<double>::quiet_NaN(), std::numeric_limits<double>::infinity()}) {
|
||||
CHECK_FALSE(backend::project_scalar(scalar_space, profile, {.mapping = &evaluator,
|
||||
.scalar_at_infinity = [invalid](const mfem::Vector&) -> projection::RadialValue { return invalid; }}));
|
||||
CHECK_FALSE(backend::project_vector(vector_space, profile, {.mapping = &evaluator,
|
||||
.vector_at_infinity = [invalid](const mfem::Vector&) -> std::expected<mfem::Vector, std::string> {
|
||||
mfem::Vector value(3);
|
||||
value = invalid;
|
||||
return value;
|
||||
}}));
|
||||
}
|
||||
const auto rejected_scalar = backend::project_scalar(scalar_space, profile, {.mapping = &evaluator,
|
||||
.scalar_at_infinity = [](const mfem::Vector&) -> projection::RadialValue {
|
||||
return std::unexpected("scalar asymptote unavailable");
|
||||
}});
|
||||
const auto rejected_vector = backend::project_vector(vector_space, profile, {.mapping = &evaluator,
|
||||
.vector_at_infinity = [](const mfem::Vector&) -> std::expected<mfem::Vector, std::string> {
|
||||
return std::unexpected("vector asymptote unavailable");
|
||||
}});
|
||||
REQUIRE_FALSE(rejected_scalar);
|
||||
REQUIRE_FALSE(rejected_vector);
|
||||
CHECK(rejected_scalar.error().find("scalar asymptote unavailable") != std::string::npos);
|
||||
CHECK(rejected_vector.error().find("vector asymptote unavailable") != std::string::npos);
|
||||
CHECK_FALSE(backend::project_vector(vector_space, profile, {.mapping = &evaluator,
|
||||
.vector_at_infinity = [](const mfem::Vector&) -> std::expected<mfem::Vector, std::string> {
|
||||
mfem::Vector value(2);
|
||||
value = 0.0;
|
||||
return value;
|
||||
}}));
|
||||
const auto recovered = backend::project_scalar(scalar_space, profile, {.mapping = &evaluator,
|
||||
.scalar_at_infinity = [](const mfem::Vector& point) -> projection::RadialValue { return point[0]; }});
|
||||
REQUIRE(recovered);
|
||||
CHECK(recovered->Min() == 0.0);
|
||||
CHECK(recovered->Max() == 1.0);
|
||||
CHECK(ordinary_samples == 0);
|
||||
}
|
||||
Reference in New Issue
Block a user