feat(surface): surface deformation prescriptions

restricted the unknown state vector to surface deformation and implemented one prescription, NodalRadialSurface, while the full volumetric displacment field is reconstructed analytically from that. This reduced the number of degrees of freedom in the system by a factor of 80 while also removing many null vectors from the system.
This commit is contained in:
2026-09-01 11:50:13 -04:00
parent 0a7f18c5c7
commit 85500fef3b
40 changed files with 8924 additions and 1164 deletions

View File

@@ -8,6 +8,8 @@
#include <catch2/catch_test_macros.hpp>
#include <mfem.hpp>
import mean_field;
import test_helpers;
@@ -96,19 +98,18 @@ namespace {
} // namespace
TEST_CASE(
"Stellar Model Owns Structure And Surface Prescriptions",
tags::stellar_model_type_contract
"Stellar Model Owns Structure Prescription And Surface Condition",
tags::stellar_model_type_contract &tags::surface_condition_type_contract
) {
STATIC_CHECK(mean_field::models::StructurePrescription<mean_field::models::structure::PolytropicStructure>);
STATIC_CHECK(mean_field::models::StructurePrescription<StellarModelTestStructure>);
STATIC_CHECK_FALSE(mean_field::models::StructurePrescription<StructureWithoutSeed>);
STATIC_CHECK(
mean_field::models::SurfacePrescription<
mean_field::surface::ConstantPressureSurface, mean_field::eos::Polytrope>
mean_field::models::SurfaceCondition<mean_field::surface::ConstantPressureSurface, mean_field::eos::Polytrope>
);
STATIC_CHECK_FALSE(
mean_field::models::SurfacePrescription<SurfaceWithoutPhysicalQuantity, mean_field::eos::Polytrope>
mean_field::models::SurfaceCondition<SurfaceWithoutPhysicalQuantity, mean_field::eos::Polytrope>
);
STATIC_CHECK_FALSE(std::derived_from<StellarModelTestStructure, mean_field::models::structure::StructureBase>);
@@ -141,14 +142,68 @@ TEST_CASE(
decltype(model.structurePrescription()), const mean_field::models::structure::PolytropicStructure &>
);
STATIC_CHECK(
std::same_as<decltype(model.surfacePrescription()), const mean_field::surface::ConstantPressureSurface &>
std::same_as<decltype(model.surfaceCondition()), const mean_field::surface::ConstantPressureSurface &>
);
STATIC_CHECK(std::same_as<decltype(model.equationOfState()), const mean_field::eos::Polytrope &>);
STATIC_CHECK(
std::same_as<
typename PolytropicStellarModel::SurfaceDeformationPrescriptionType,
mean_field::deformation::NodalRadialSurface>
);
STATIC_CHECK(
std::same_as<
typename PolytropicStellarModel::StellarInteriorDeformationExtensionType,
mean_field::deformation::PowerLawRadialInteriorExtension>
);
STATIC_CHECK(
std::same_as<
typename PolytropicStellarModel::VacuumDeformationExtensionType,
mean_field::deformation::FixedInfinityRadialVacuumExtension>
);
CHECK(model.targetMass() == 1.0);
CHECK(model.compiledSurfaceConstraint().targetPressure() == mean_field::eos::PressureValue{0.0});
CHECK(&model.equationOfState() == &model.structurePrescription().equationOfState());
CHECK(model.surfacePrescription().targetPressure() == mean_field::eos::PressureValue{0.0});
CHECK(model.surfaceCondition().targetPressure() == mean_field::eos::PressureValue{0.0});
CHECK(model.surfaceDeformationPrescription().descriptor().name == "NodalRadialSurface");
CHECK(model.stellarInteriorDeformationExtension().radialPower() == 2.0);
CHECK(model.vacuumDeformationExtension().descriptor().name == "FixedInfinityRadialVacuumExtension");
}
TEST_CASE(
"Stellar Model Owns Explicit Surface Interior And Vacuum Deformation Policies",
tags::stellar_model_deformation_ownership
) {
mfem::Vector referenceCenter(3);
referenceCenter(0) = 0.125;
referenceCenter(1) = -0.25;
referenceCenter(2) = 0.375;
mean_field::models::StellarModel model{
mean_field::models::structure::PolytropicStructure{mean_field::eos::Polytrope{3.0, 0.25}, 1.0},
mean_field::surface::ConstantPressureSurface{mean_field::eos::PressureValue{0.0}},
mean_field::deformation::NodalRadialSurface{referenceCenter},
mean_field::deformation::PowerLawRadialInteriorExtension{3.0},
mean_field::deformation::FixedInfinityRadialVacuumExtension{}
};
CHECK(model.surfaceDeformationPrescription().referenceCenter()(0) == referenceCenter(0));
CHECK(model.surfaceDeformationPrescription().referenceCenter()(1) == referenceCenter(1));
CHECK(model.surfaceDeformationPrescription().referenceCenter()(2) == referenceCenter(2));
CHECK(model.stellarInteriorDeformationExtension().radialPower() == 3.0);
CHECK(
model.vacuumDeformationExtension().descriptor().outerBoundaryBehavior ==
mean_field::deformation::VacuumOuterBoundaryBehavior::FixedAtReferenceInfinity
);
const auto *surfaceDeformationAddress = &model.surfaceDeformationPrescription();
const auto *interiorDeformationAddress = &model.stellarInteriorDeformationExtension();
const auto *vacuumDeformationAddress = &model.vacuumDeformationExtension();
auto movedModel = std::move(model);
CHECK(&movedModel.surfaceDeformationPrescription() == surfaceDeformationAddress);
CHECK(&movedModel.stellarInteriorDeformationExtension() == interiorDeformationAddress);
CHECK(&movedModel.vacuumDeformationExtension() == vacuumDeformationAddress);
}
TEST_CASE(
@@ -184,7 +239,7 @@ TEST_CASE(
const mean_field::models::structure::PolytropicStructure *structureAddress = &originalModel.structurePrescription();
const mean_field::surface::ConstantPressureSurface *surfaceAddress = &originalModel.surfacePrescription();
const mean_field::surface::ConstantPressureSurface *surfaceAddress = &originalModel.surfaceCondition();
const mean_field::eos::Polytrope *equationOfStateAddress = &originalModel.equationOfState();
@@ -193,7 +248,7 @@ TEST_CASE(
mean_field::models::StellarModel movedModel{std::move(originalModel)};
CHECK(&movedModel.structurePrescription() == structureAddress);
CHECK(&movedModel.surfacePrescription() == surfaceAddress);
CHECK(&movedModel.surfaceCondition() == surfaceAddress);
CHECK(&movedModel.equationOfState() == equationOfStateAddress);
CHECK(&movedModel.compiledSurfaceConstraint() == compiledSurfaceConstraintAddress);
CHECK(movedModel.targetMass() == 1.0);
@@ -221,7 +276,7 @@ TEST_CASE(
}
TEST_CASE(
"Stellar Model Supports Custom Structure And Surface Prescriptions",
"Stellar Model Supports Custom Structure Prescriptions And Surface Conditions",
tags::barotrope &tags::unit &tags::model
) {
const auto tracker = std::make_shared<StellarModelExtensionTracker>();
@@ -261,9 +316,12 @@ TEST_CASE(
const mean_field::models::structure::PolytropicStructure *sourceStructureAddress =
&sourceModel.structurePrescription();
const mean_field::surface::ConstantPressureSurface *sourceSurfaceAddress = &sourceModel.surfacePrescription();
const mean_field::surface::ConstantPressureSurface *sourceSurfaceAddress = &sourceModel.surfaceCondition();
const mean_field::eos::Polytrope *sourceEquationOfStateAddress = &sourceModel.equationOfState();
const auto *sourceSurfaceDeformationAddress = &sourceModel.surfaceDeformationPrescription();
const auto *sourceInteriorDeformationAddress = &sourceModel.stellarInteriorDeformationExtension();
const auto *sourceVacuumDeformationAddress = &sourceModel.vacuumDeformationExtension();
const mean_field::eos::PressureValue sourceTargetPressure =
sourceModel.compiledSurfaceConstraint().targetPressure();
@@ -272,10 +330,16 @@ TEST_CASE(
CHECK(&destinationModel.structurePrescription() == sourceStructureAddress);
CHECK(&destinationModel.surfacePrescription() == sourceSurfaceAddress);
CHECK(&destinationModel.surfaceCondition() == sourceSurfaceAddress);
CHECK(&destinationModel.equationOfState() == sourceEquationOfStateAddress);
CHECK(&destinationModel.surfaceDeformationPrescription() == sourceSurfaceDeformationAddress);
CHECK(&destinationModel.stellarInteriorDeformationExtension() == sourceInteriorDeformationAddress);
CHECK(&destinationModel.vacuumDeformationExtension() == sourceVacuumDeformationAddress);
CHECK(destinationModel.targetMass() == 1.25);
CHECK(destinationModel.compiledSurfaceConstraint().targetPressure() == sourceTargetPressure);
@@ -307,6 +371,13 @@ TEST_CASE(
CHECK(views[0].targetMass() == 1.0);
CHECK(views[1].targetMass() == 2.5);
CHECK(views[1].surfaceCondition().targetPressure == 0.375);
CHECK(views[0].surfaceDeformation().name == "NodalRadialSurface");
CHECK(views[0].surfaceDeformation().motionKind == mean_field::deformation::SurfaceMotionKind::Radial);
CHECK(views[0].stellarInteriorDeformation().name == "PowerLawRadialInteriorExtension");
CHECK(
views[0].vacuumDeformation().outerBoundaryBehavior ==
mean_field::deformation::VacuumOuterBoundaryBehavior::FixedAtReferenceInfinity
);
REQUIRE(views[1].surfaceDependencies().stateFields.size() == 1);
CHECK(
views[1].surfaceDependencies().residualRowField ==
@@ -350,6 +421,9 @@ TEST_CASE(
REQUIRE(pressure.has_value());
CHECK(view.targetMass() == movedModel.targetMass());
CHECK(view.surfaceDeformation() == movedModel.surfaceDeformationPrescription().descriptor());
CHECK(view.stellarInteriorDeformation() == movedModel.stellarInteriorDeformationExtension().descriptor());
CHECK(view.vacuumDeformation() == movedModel.vacuumDeformationExtension().descriptor());
CHECK(
pressure->value() == mean_field::eos::evaluate<mean_field::eos::quantity::Pressure>(
movedModel.equationOfState(), mean_field::eos::DensityValue{0.7}
@@ -358,3 +432,48 @@ TEST_CASE(
);
CHECK(seed.radius.Size() == 8);
}
TEST_CASE(
"Stellar Model Compiles Its Deformation Policies Against The Finite Element Discretization",
tags::stellar_model_deformation_compilation
) {
mean_field::utils::Args args = test_utils::setup_args();
mean_field::fem::FEM fem = mean_field::fem::setup_fem(args.mesh_file, args, 0);
REQUIRE(fem.okay());
mfem::Vector referenceCenter(fem.mesh->SpaceDimension());
referenceCenter = 0.0;
const mean_field::models::StellarModel model{
mean_field::models::structure::PolytropicStructure{mean_field::eos::Polytrope{3.0, 0.25}, 1.0},
mean_field::surface::ConstantPressureSurface{mean_field::eos::PressureValue{0.0}},
mean_field::deformation::NodalRadialSurface{referenceCenter},
mean_field::deformation::PowerLawRadialInteriorExtension{3.0},
mean_field::deformation::FixedInfinityRadialVacuumExtension{}
};
auto prepared = model.compileDomainDeformation(fem);
STATIC_CHECK(mean_field::deformation::PreparedDomainDeformationOperator<decltype(prepared)>);
CHECK(prepared.matchesCurrentDiscretization());
CHECK(prepared.stellarInteriorExtension().radialPower() == 3.0);
CHECK(prepared.discretizationDependencies().physicalMeshIdentity == fem.mesh.get());
CHECK(prepared.discretizationDependencies().logicalReferenceMeshIdentity == fem.logicalReferenceMesh.get());
CHECK(prepared.parameterCount() == prepared.surfaceDeformationPrescription().parameterCount());
CHECK(prepared.volumeDisplacementSize() == fem.displacementFes->GetTrueVSize());
STATIC_CHECK_FALSE(std::is_copy_constructible_v<mean_field::deformation::PreparedDomainDeformationRuntime>);
STATIC_CHECK(std::is_nothrow_move_constructible_v<mean_field::deformation::PreparedDomainDeformationRuntime>);
mean_field::deformation::PreparedDomainDeformationRuntime runtime{std::move(prepared)};
STATIC_CHECK(mean_field::deformation::PreparedDomainDeformationOperator<decltype(runtime)>);
mean_field::deformation::PreparedDomainDeformationRuntime movedRuntime{std::move(runtime)};
CHECK(movedRuntime.matchesCurrentDiscretization());
CHECK(movedRuntime.parameterCount() > 0);
CHECK(movedRuntime.volumeDisplacementSize() == fem.displacementFes->GetTrueVSize());
CHECK(movedRuntime.compositionReport().assignedScalarDofCount() == fem.surfaceDeformationFes->GetTrueVSize());
mfem::Vector zeroParameters(movedRuntime.parameterCount());
mfem::Vector volumeDisplacement(movedRuntime.volumeDisplacementSize());
zeroParameters = 0.0;
movedRuntime.buildVolumeDisplacement(zeroParameters, volumeDisplacement);
CHECK(volumeDisplacement.Norml2() == 0.0);
}