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.
120 lines
6.9 KiB
C++
120 lines
6.9 KiB
C++
#include <cmath>
|
|
#include <catch2/catch_approx.hpp>
|
|
#include <catch2/catch_test_macros.hpp>
|
|
#include <catch2/matchers/catch_matchers_string.hpp>
|
|
|
|
#include "serif/model/finite_elements.hpp"
|
|
#include "serif/backend/mfem/discretization/field_values.hpp"
|
|
#include "serif/backend/mfem/serialize/discretization.hpp"
|
|
#include "../sandbox/polytropic_model.hpp"
|
|
|
|
TEST_CASE("Stellar factory selects compatible surface layers and preserves the ordinary default", "[model][reconstruction]") {
|
|
namespace reconstruction = serif::discretization::reconstruction;
|
|
namespace blocks = serif::discretization::blocks;
|
|
namespace model = serif::model;
|
|
using Density = blocks::density::mass;
|
|
const auto star = serif::examples::make_polytropic_model(4.0 / 3.0);
|
|
const auto ordinary = model::make_stellar_discretization(star);
|
|
CHECK_FALSE(ordinary.binding<Density>().reconstruction());
|
|
const auto identity = model::make_stellar_discretization(star, {}, reconstruction::SurfacePower<Density>{.density_exponent = 1.0});
|
|
CHECK_FALSE(identity.binding<Density>().reconstruction());
|
|
for (const int equatorial : {0, 2}) {
|
|
const auto weighted = model::make_stellar_discretization(star,
|
|
{.surface_refinement = {.levels = 1, .equatorial_levels = equatorial, .equatorial_half_angle_degrees = 10.0}},
|
|
reconstruction::SurfacePower<Density>{.density_exponent = 4.0 / 3.0,
|
|
.region = reconstruction::SurfaceAdjacent<serif::discretization::domain::StellarSurfaceBoundary>{}});
|
|
const auto& density = weighted.binding<Density>();
|
|
REQUIRE(density.reconstruction());
|
|
int selected = 0, ordinary_material = 0;
|
|
for (int element = 0; element < density.mesh().GetNE(); ++element) {
|
|
if (!density.supports(element)) { continue; }
|
|
if (density.reconstruction_element(element)) { ++selected; }
|
|
else { ++ordinary_material; }
|
|
}
|
|
CHECK(selected > 0);
|
|
CHECK(ordinary_material > 0);
|
|
CHECK_FALSE(weighted.binding<blocks::gravity::potential>().reconstruction());
|
|
}
|
|
CHECK_THROWS_WITH((model::make_stellar_discretization(star, {}, reconstruction::SurfacePower<blocks::gravity::potential>{})),
|
|
Catch::Matchers::ContainsSubstring("only scalar H1 mass density"));
|
|
CHECK_THROWS_WITH((model::make_stellar_discretization(star, {}, reconstruction::SurfacePower<Density>{}, reconstruction::SurfacePower<Density>{})),
|
|
Catch::Matchers::ContainsSubstring("at most one"));
|
|
}
|
|
|
|
TEST_CASE("Weighted archives preserve between-node density and reject missing interpretation", "[model][reconstruction]") {
|
|
namespace reconstruction = serif::discretization::reconstruction;
|
|
namespace backend = serif::backend::mfem::serialize;
|
|
namespace discretization = serif::discretization::backend::mfem;
|
|
using Density = serif::discretization::blocks::density::mass;
|
|
const auto star = serif::examples::make_polytropic_model(5.0 / 3.0);
|
|
const auto original = serif::model::make_stellar_discretization(star, {}, reconstruction::SurfacePower<Density>{.density_exponent = 5.0 / 3.0});
|
|
const auto record = backend::save_discretization(original);
|
|
auto restored = backend::restore_discretization<decltype(original)>(record);
|
|
INFO((restored ? "restored" : restored.error().message));
|
|
REQUIRE(restored);
|
|
CHECK(backend::save_discretization(*restored) == record);
|
|
const auto& binding = original.binding<Density>();
|
|
::mfem::Vector coefficients(binding.size());
|
|
for (int i = 0; i < coefficients.Size(); ++i) { coefficients[i] = std::sin(0.13 * i); }
|
|
discretization::FieldValues<Density> before(binding, coefficients), after(restored->binding<Density>(), coefficients);
|
|
::mfem::IntegrationPoint point;
|
|
point.Set3(0.17, 0.31, 0.87);
|
|
for (int element = 0; element < binding.mesh().GetNE(); ++element) {
|
|
if (!binding.supports(element)) { continue; }
|
|
CHECK(before.value(element, point) == Catch::Approx(after.value(element, point)).margin(1e-13));
|
|
}
|
|
auto malformed = record;
|
|
for (auto& [key, saved] : malformed.children.at("bindings").children) {
|
|
if (saved.children.contains("reconstruction")) {
|
|
CHECK(saved.version == 2);
|
|
saved.children.erase("reconstruction");
|
|
}
|
|
}
|
|
CHECK_FALSE(backend::restore_discretization<decltype(original)>(malformed));
|
|
}
|
|
|
|
TEST_CASE("Reconstruction plans reject attachment after the boundary changes", "[model][reconstruction]") {
|
|
namespace reconstruction = serif::discretization::reconstruction;
|
|
using Density = serif::discretization::blocks::density::mass;
|
|
const auto star = serif::examples::make_polytropic_model(1.5);
|
|
const auto finite_elements = serif::model::make_stellar_discretization(star, {}, reconstruction::SurfacePower<Density>{.density_exponent = 1.5});
|
|
const auto& density = finite_elements.binding<Density>();
|
|
const auto plan = density.reconstruction();
|
|
auto& mesh = density.mesh();
|
|
REQUIRE(mesh.GetNBE() > 0);
|
|
mesh.SetBdrAttribute(0, mesh.GetBdrAttribute(0) + 10);
|
|
using Binding = std::remove_cvref_t<decltype(density)>;
|
|
CHECK_THROWS_WITH(Binding(density.space(), plan), Catch::Matchers::ContainsSubstring("Rebuild the reconstruction plan"));
|
|
CHECK_THROWS(density.validate());
|
|
}
|
|
|
|
TEST_CASE("Reconstruction rejects radial hanging layers and stale refined-space plans", "[model][reconstruction]") {
|
|
namespace backend = serif::discretization::backend::mfem;
|
|
namespace reconstruction = serif::discretization::reconstruction;
|
|
using Density = serif::discretization::blocks::density::mass;
|
|
using Schema = serif::discretization::domain::schema::CoreEnvelopeVacuumDomainSchema;
|
|
auto mesh = ::mfem::Mesh::MakeCartesian3D(2, 1, 1, ::mfem::Element::HEXAHEDRON);
|
|
::mfem::Array<int> vertices;
|
|
for (int boundary = 0; boundary < mesh.GetNBE(); ++boundary) {
|
|
mesh.GetBdrElementVertices(boundary, vertices);
|
|
bool surface = true;
|
|
for (int vertex : vertices) { surface = surface && mesh.GetVertex(vertex)[2] == 0.0; }
|
|
mesh.GetBdrElement(boundary)->SetAttribute(surface ? 1 : 2);
|
|
}
|
|
mesh.SetAttributes();
|
|
::mfem::H1_FECollection collection(3, 3);
|
|
::mfem::FiniteElementSpace space(&mesh, &collection);
|
|
backend::TermBinding<Density, Schema> original(space);
|
|
const auto specification = reconstruction::SurfacePower<Density>{.density_exponent = 1.5};
|
|
const auto old_plan = backend::make_reconstruction(specification, original);
|
|
::mfem::Array<::mfem::Refinement> refinement;
|
|
refinement.Append(::mfem::Refinement(0, ::mfem::Refinement::Z));
|
|
mesh.GeneralRefinement(refinement, 1);
|
|
space.Update();
|
|
CHECK_THROWS_WITH((backend::TermBinding<Density, Schema>(space, old_plan)),
|
|
Catch::Matchers::ContainsSubstring("Rebuild the reconstruction plan"));
|
|
backend::TermBinding<Density, Schema> updated(space);
|
|
CHECK_THROWS_WITH(backend::make_reconstruction(specification, updated),
|
|
Catch::Matchers::ContainsSubstring("matching radial layers"));
|
|
}
|