feat(newton): first newton solver implementation

This commit is contained in:
2026-09-08 06:36:39 -04:00
parent 76818f2f82
commit b3c04d507a
98 changed files with 20397 additions and 11040 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -141,55 +141,50 @@ TEST_CASE(
tags::model_specification_type_contract
) {
using namespace mean_field;
using Request = models::FixedAngularMomentumLayoutRequest;
using Form = operators::CompiledStellarEquilibriumForm<AngularMomentumPolytropicMassModel>;
using Jacobian = operators::CompiledStellarEquilibriumJacobianForm<AngularMomentumPolytropicMassModel>;
using AngularValue = utils::blocks::fixed_angular_momentum::angular_velocity::value;
using Request = models::FixedAngularMomentumLayoutRequest;
using Form = operators::CompiledStellarEquilibriumForm<AngularMomentumPolytropicMassModel>;
using Jacobian = operators::CompiledStellarEquilibriumJacobianForm<AngularMomentumPolytropicMassModel>;
using AngularValue = utils::blocks::fixed_angular_momentum::angular_velocity::value;
using AngularResidual = utils::blocks::fixed_angular_momentum::angular_velocity::residual;
STATIC_CHECK(models::ConstraintLayoutRequestType<Request>);
STATIC_CHECK(models::CompiledConstraint<models::CompiledFixedAngularMomentum>);
STATIC_CHECK(std::same_as<
typename Request::GeneratedValueType,
models::PhysicalCoordinateFor<models::FixedAngularMomentum>>);
STATIC_CHECK(std::same_as<
typename AngularValue::GeneratedType,
models::PhysicalCoordinateFor<models::FixedAngularMomentum>>);
STATIC_CHECK(std::same_as<
typename models::CompiledFixedAngularMomentum::AngularVelocityField,
field::AngularVelocity>);
STATIC_CHECK(
std::same_as<typename Request::GeneratedValueType, models::PhysicalCoordinateFor<models::FixedAngularMomentum>>
);
STATIC_CHECK(
std::same_as<typename AngularValue::GeneratedType, models::PhysicalCoordinateFor<models::FixedAngularMomentum>>
);
STATIC_CHECK(
std::same_as<typename models::CompiledFixedAngularMomentum::AngularVelocityField, field::AngularVelocity>
);
STATIC_CHECK(Form::value_block_count == 7);
STATIC_CHECK(Form::residual_block_count == 7);
STATIC_CHECK(Request::valueBlock<Form>().index == 6);
STATIC_CHECK(Request::residualBlock<Form>().index == 6);
STATIC_CHECK(utils::blocks::valid_jacobian_form<Form, Jacobian>);
STATIC_CHECK(utils::blocks::has_jacobian_coupling_v<
AngularResidual,
utils::blocks::density::mass::value,
Jacobian>);
STATIC_CHECK(utils::blocks::has_jacobian_coupling_v<
AngularResidual,
utils::blocks::surface_deformation::parameters::value,
Jacobian>);
STATIC_CHECK(
utils::blocks::has_jacobian_coupling_v<AngularResidual, utils::blocks::density::mass::value, Jacobian>
);
STATIC_CHECK(
utils::blocks::has_jacobian_coupling_v<
AngularResidual, utils::blocks::surface_deformation::parameters::value, Jacobian>
);
STATIC_CHECK(utils::blocks::has_jacobian_coupling_v<AngularResidual, AngularValue, Jacobian>);
STATIC_CHECK(utils::blocks::has_jacobian_coupling_v<
utils::blocks::surface_deformation::shape_equilibrium::residual,
AngularValue,
Jacobian>);
STATIC_CHECK(utils::blocks::has_jacobian_coupling_v<
utils::blocks::enthalpy::specific::residual,
AngularValue,
Jacobian>);
STATIC_CHECK_FALSE(utils::blocks::has_jacobian_coupling_v<
utils::blocks::gravity::poisson::residual,
AngularValue,
Jacobian>);
STATIC_CHECK(
utils::blocks::has_jacobian_coupling_v<
utils::blocks::surface_deformation::shape_equilibrium::residual, AngularValue, Jacobian>
);
STATIC_CHECK(
utils::blocks::has_jacobian_coupling_v<utils::blocks::enthalpy::specific::residual, AngularValue, Jacobian>
);
STATIC_CHECK_FALSE(
utils::blocks::has_jacobian_coupling_v<utils::blocks::gravity::poisson::residual, AngularValue, Jacobian>
);
const integral::FixedAngularMomentum specification({
.Jtotal = dimensions::AngularMomentumValue{2.75},
.axis = {0.0, 3.0, 4.0},
.center = {0.25, -0.5, 0.75}
});
const integral::FixedAngularMomentum specification(
{.Jtotal = dimensions::AngularMomentumValue{2.75}, .axis = {0.0, 3.0, 4.0}, .center = {0.25, -0.5, 0.75}}
);
const models::CompiledFixedAngularMomentum compiled = models::compileConstraint(specification);
CHECK(compiled.targetAngularMomentum() == dimensions::AngularMomentumValue{2.75});
CHECK(std::abs(compiled.specification().axis()[0]) < 1.0e-15);
@@ -281,9 +276,7 @@ TEST_CASE(
) {
const mean_field::models::FixedTotalMass mass{mean_field::dimensions::MassValue{1.25}};
const mean_field::models::FixedCentralDensity centralDensity{mean_field::eos::DensityValue{2.5}};
const mean_field::models::FixedAngularMomentum angularMomentum{
mean_field::dimensions::AngularMomentumValue{0.75}
};
const mean_field::models::FixedAngularMomentum angularMomentum{mean_field::dimensions::AngularMomentumValue{0.75}};
CHECK(mass.targetMass() == mean_field::dimensions::MassValue{1.25});
CHECK(centralDensity.targetDensity() == mean_field::eos::DensityValue{2.5});
@@ -308,18 +301,17 @@ TEST_CASE(
std::invalid_argument
);
CHECK_THROWS_AS(
mean_field::models::FixedAngularMomentum({
.Jtotal = mean_field::dimensions::AngularMomentumValue{1.0},
.axis = {0.0, 0.0, 0.0}
}),
mean_field::models::FixedAngularMomentum(
{.Jtotal = mean_field::dimensions::AngularMomentumValue{1.0}, .axis = {0.0, 0.0, 0.0}}
),
std::invalid_argument
);
CHECK_THROWS_AS(
mean_field::models::FixedAngularMomentum({
.Jtotal = mean_field::dimensions::AngularMomentumValue{1.0},
.axis = {0.0, 0.0, 1.0},
.center = {0.0, std::numeric_limits<double>::quiet_NaN(), 0.0}
}),
mean_field::models::FixedAngularMomentum(
{.Jtotal = mean_field::dimensions::AngularMomentumValue{1.0},
.axis = {0.0, 0.0, 1.0},
.center = {0.0, std::numeric_limits<double>::quiet_NaN(), 0.0}}
),
std::invalid_argument
);

File diff suppressed because it is too large Load Diff

View File

@@ -27,16 +27,15 @@ namespace {
int marker;
};
using ModelDefinition =
mean_field::eos::ConstitutiveLaw<MoveOnlyEquationOfState, "MoveOnlyEquationOfState">;
using ModelDefinition = mean_field::eos::ConstitutiveLaw<MoveOnlyEquationOfState, "MoveOnlyEquationOfState">;
explicit MoveOnlyEquationOfState(const Parameters parameters)
: m_marker(std::make_unique<int>(parameters.marker)) {
}
MoveOnlyEquationOfState(const MoveOnlyEquationOfState &) = delete;
MoveOnlyEquationOfState &operator=(const MoveOnlyEquationOfState &) = delete;
MoveOnlyEquationOfState(MoveOnlyEquationOfState &&) noexcept = default;
MoveOnlyEquationOfState(const MoveOnlyEquationOfState &) = delete;
MoveOnlyEquationOfState &operator=(const MoveOnlyEquationOfState &) = delete;
MoveOnlyEquationOfState(MoveOnlyEquationOfState &&) noexcept = default;
MoveOnlyEquationOfState &operator=(MoveOnlyEquationOfState &&) noexcept = default;
[[nodiscard]] const std::unique_ptr<int> &marker() const noexcept {
@@ -60,9 +59,9 @@ namespace {
: m_marker(std::make_unique<int>(parameters.marker)) {
}
MoveOnlySurfaceCondition(const MoveOnlySurfaceCondition &) = delete;
MoveOnlySurfaceCondition &operator=(const MoveOnlySurfaceCondition &) = delete;
MoveOnlySurfaceCondition(MoveOnlySurfaceCondition &&) noexcept = default;
MoveOnlySurfaceCondition(const MoveOnlySurfaceCondition &) = delete;
MoveOnlySurfaceCondition &operator=(const MoveOnlySurfaceCondition &) = delete;
MoveOnlySurfaceCondition(MoveOnlySurfaceCondition &&) noexcept = default;
MoveOnlySurfaceCondition &operator=(MoveOnlySurfaceCondition &&) noexcept = default;
[[nodiscard]] const std::unique_ptr<int> &marker() const noexcept {
@@ -87,7 +86,7 @@ namespace {
"C_move",
"move_only_integral.residual",
"R_move">;
using TargetValue = typename ScalarDescription::TargetValue;
using TargetValue = typename ScalarDescription::TargetValue;
using ModelDefinition = mean_field::integral::FixedScalarWithMultiplier<
MoveOnlyIntegralConstraint,
"MoveOnlyIntegralConstraint",
@@ -99,9 +98,9 @@ namespace {
: m_marker(std::make_unique<int>(parameters.marker)) {
}
MoveOnlyIntegralConstraint(const MoveOnlyIntegralConstraint &) = delete;
MoveOnlyIntegralConstraint &operator=(const MoveOnlyIntegralConstraint &) = delete;
MoveOnlyIntegralConstraint(MoveOnlyIntegralConstraint &&) noexcept = default;
MoveOnlyIntegralConstraint(const MoveOnlyIntegralConstraint &) = delete;
MoveOnlyIntegralConstraint &operator=(const MoveOnlyIntegralConstraint &) = delete;
MoveOnlyIntegralConstraint(MoveOnlyIntegralConstraint &&) noexcept = default;
MoveOnlyIntegralConstraint &operator=(MoveOnlyIntegralConstraint &&) noexcept = default;
[[nodiscard]] const std::unique_ptr<int> &marker() const noexcept {
@@ -167,20 +166,15 @@ TEST_CASE(
tags::stellar_model_specification_api
) {
using namespace mean_field;
using Qualified = models::SpecificationSet<
const eos::Polytrope &,
volatile surface::Isobaric &&,
const integral::FixedTotalMass>;
using Plain = models::SpecificationSet<
eos::Polytrope,
surface::Isobaric,
integral::FixedTotalMass>;
using Qualified =
models::SpecificationSet<const eos::Polytrope &, volatile surface::Isobaric &&, const integral::FixedTotalMass>;
using Plain = models::SpecificationSet<eos::Polytrope, surface::Isobaric, integral::FixedTotalMass>;
STATIC_CHECK(std::same_as<Qualified, Plain>);
STATIC_CHECK(models::ValidModelSpecificationPack<
const eos::Polytrope &,
volatile surface::Isobaric &&,
const integral::FixedTotalMass>);
STATIC_CHECK(
models::ValidModelSpecificationPack<
const eos::Polytrope &, volatile surface::Isobaric &&, const integral::FixedTotalMass>
);
STATIC_CHECK(model::StellarModelType<model::StellarModel<Qualified>>);
}
@@ -226,23 +220,20 @@ TEST_CASE(
using namespace mean_field;
auto stellarModel = model::StellarModel(
MoveOnlyIntegralConstraint({.marker = 307}),
MoveOnlySurfaceCondition({.marker = 211}),
MoveOnlyIntegralConstraint({.marker = 307}), MoveOnlySurfaceCondition({.marker = 211}),
MoveOnlyEquationOfState({.marker = 101})
);
using Model = std::remove_cvref_t<decltype(stellarModel)>;
using Expected = model::StellarModel<models::SpecificationSet<
MoveOnlyEquationOfState,
MoveOnlySurfaceCondition,
MoveOnlyIntegralConstraint>>;
using Model = std::remove_cvref_t<decltype(stellarModel)>;
using Expected = model::StellarModel<
models::SpecificationSet<MoveOnlyEquationOfState, MoveOnlySurfaceCondition, MoveOnlyIntegralConstraint>>;
STATIC_CHECK(std::same_as<Model, Expected>);
STATIC_CHECK_FALSE(std::copy_constructible<MoveOnlyEquationOfState>);
STATIC_CHECK_FALSE(std::copy_constructible<MoveOnlySurfaceCondition>);
STATIC_CHECK_FALSE(std::copy_constructible<MoveOnlyIntegralConstraint>);
const auto &equationOfState = stellarModel.specification<MoveOnlyEquationOfState>();
const auto &surfaceCondition = stellarModel.specification<MoveOnlySurfaceCondition>();
const auto &equationOfState = stellarModel.specification<MoveOnlyEquationOfState>();
const auto &surfaceCondition = stellarModel.specification<MoveOnlySurfaceCondition>();
const auto &integralConstraint = stellarModel.specification<MoveOnlyIntegralConstraint>();
REQUIRE(equationOfState.marker() != nullptr);

File diff suppressed because it is too large Load Diff

View File

@@ -15,34 +15,28 @@ import mean_field;
import test_helpers;
namespace {
namespace blocks = mean_field::utils::blocks;
namespace blocks = mean_field::utils::blocks;
namespace normalization = mean_field::normalization;
namespace models = mean_field::models;
namespace models = mean_field::models;
struct ModelWithoutFixedTotalMass final { };
template <typename Model>
concept SupportsModelDerivedStellarScales = requires(
const normalization::PhysicalRieszDiagonal<> &policy,
const Model &model
) {
{
normalization::deriveStellarCharacteristicScales(policy, model)
} -> std::same_as<normalization::StellarCharacteristicScales>;
};
concept SupportsModelDerivedStellarScales =
requires(const normalization::PhysicalRieszDiagonal<> &policy, const Model &model) {
{
normalization::deriveStellarCharacteristicScales(policy, model)
} -> std::same_as<normalization::StellarCharacteristicScales>;
};
struct TestValue final : blocks::value_block_base { };
struct TestResidual final : blocks::residual_block_base { };
using TestForm = blocks::block_form<
blocks::type_list<TestValue>,
blocks::type_list<TestResidual>>;
using TestForm = blocks::block_form<blocks::type_list<TestValue>, blocks::type_list<TestResidual>>;
using GlobalSpecificEnergyNormalization = models::CoordinateNormalization<
models::RieszTopology::global_scalar,
models::PhysicalScaleLaw::specific_energy>;
using VolumeSpecificEnergyNormalization = models::CoordinateNormalization<
models::RieszTopology::scalar_volume_l2,
models::PhysicalScaleLaw::specific_energy>;
using GlobalSpecificEnergyNormalization = models::
CoordinateNormalization<models::RieszTopology::global_scalar, models::PhysicalScaleLaw::specific_energy>;
using VolumeSpecificEnergyNormalization = models::
CoordinateNormalization<models::RieszTopology::scalar_volume_l2, models::PhysicalScaleLaw::specific_energy>;
class SelfDescribingMagneticSpecificEnergy final {
public:
@@ -98,9 +92,7 @@ namespace {
"NormalizationMockVolumeCoordinate",
models::DependsOn<blocks::density::mass::value>,
models::Affects<blocks::enthalpy::specific::residual>,
models::GeneratedNormalization<
VolumeSpecificEnergyNormalization,
GlobalSpecificEnergyNormalization>>;
models::GeneratedNormalization<VolumeSpecificEnergyNormalization, GlobalSpecificEnergyNormalization>>;
explicit constexpr GeneratedVolumeCoordinateWithoutMetricSource(const Parameters parameters) noexcept
: m_target(parameters.target) {
@@ -134,39 +126,36 @@ namespace {
};
template <typename Specification>
using GeneratedValueBlock = blocks::generated_value_block<
models::PhysicalCoordinateFor<Specification>>;
using GeneratedValueBlock = blocks::generated_value_block<models::PhysicalCoordinateFor<Specification>>;
template <typename Specification>
using GeneratedMultiplierBlock = blocks::generated_value_block<
models::MultiplierFor<Specification>>;
using GeneratedMultiplierBlock = blocks::generated_value_block<models::MultiplierFor<Specification>>;
template <typename Specification>
using GeneratedResidualBlock = blocks::generated_residual_block<
models::ResidualFor<Specification>>;
using GeneratedResidualBlock = blocks::generated_residual_block<models::ResidualFor<Specification>>;
using SelfDescribingValue = GeneratedValueBlock<SelfDescribingMagneticSpecificEnergy>;
using SelfDescribingValue = GeneratedValueBlock<SelfDescribingMagneticSpecificEnergy>;
using SelfDescribingResidual = GeneratedResidualBlock<SelfDescribingMagneticSpecificEnergy>;
using SelfDescribingForm = blocks::block_form<
blocks::type_list<SelfDescribingValue>,
blocks::type_list<SelfDescribingResidual>>;
using SelfDescribingForm =
blocks::block_form<blocks::type_list<SelfDescribingValue>, blocks::type_list<SelfDescribingResidual>>;
using MissingValue = GeneratedMultiplierBlock<MissingGeneratedNormalization>;
using MissingValue = GeneratedMultiplierBlock<MissingGeneratedNormalization>;
using MissingResidual = GeneratedResidualBlock<MissingGeneratedNormalization>;
using MissingNormalizationForm = blocks::block_form<
blocks::type_list<MissingValue>,
blocks::type_list<MissingResidual>>;
using MissingNormalizationForm =
blocks::block_form<blocks::type_list<MissingValue>, blocks::type_list<MissingResidual>>;
using UnpreparedVolumeValue = GeneratedValueBlock<GeneratedVolumeCoordinateWithoutMetricSource>;
using UnpreparedVolumeValue = GeneratedValueBlock<GeneratedVolumeCoordinateWithoutMetricSource>;
using UnpreparedVolumeResidual = GeneratedResidualBlock<GeneratedVolumeCoordinateWithoutMetricSource>;
using UnpreparedVolumeForm = blocks::block_form<
blocks::type_list<UnpreparedVolumeValue>,
blocks::type_list<UnpreparedVolumeResidual>>;
using UnpreparedVolumeForm =
blocks::block_form<blocks::type_list<UnpreparedVolumeValue>, blocks::type_list<UnpreparedVolumeResidual>>;
class DenseOperator final : public mfem::Operator {
public:
explicit DenseOperator(const mfem::DenseMatrix &matrix)
: mfem::Operator(matrix.Height(), matrix.Width()),
: mfem::Operator(
matrix.Height(),
matrix.Width()
),
m_matrix(matrix) {
}
@@ -184,7 +173,10 @@ namespace {
class DenseInverseSolver final : public mfem::Solver {
public:
explicit DenseInverseSolver(const mfem::DenseMatrix &inverse)
: mfem::Solver(inverse.Height(), inverse.Width()),
: mfem::Solver(
inverse.Height(),
inverse.Width()
),
m_inverse(inverse) {
}
@@ -247,75 +239,54 @@ TEST_CASE(
) {
using Map = normalization::DiagonalNormalization;
STATIC_CHECK(std::constructible_from<
normalization::ScaledJacobianOperator,
const DenseOperator &,
const Map &>);
STATIC_CHECK_FALSE(std::constructible_from<
normalization::ScaledJacobianOperator,
DenseOperator &&,
const Map &>);
STATIC_CHECK_FALSE(std::constructible_from<
normalization::ScaledJacobianOperator,
const DenseOperator &&,
const Map &>);
STATIC_CHECK_FALSE(std::constructible_from<
normalization::ScaledJacobianOperator,
const DenseOperator &,
Map &&>);
STATIC_CHECK(std::constructible_from<normalization::ScaledJacobianOperator, const DenseOperator &, const Map &>);
STATIC_CHECK_FALSE(std::constructible_from<normalization::ScaledJacobianOperator, DenseOperator &&, const Map &>);
STATIC_CHECK_FALSE(
std::constructible_from<normalization::ScaledJacobianOperator, const DenseOperator &&, const Map &>
);
STATIC_CHECK_FALSE(std::constructible_from<normalization::ScaledJacobianOperator, const DenseOperator &, Map &&>);
STATIC_CHECK(std::constructible_from<
normalization::ScaledInverseOperator,
const DenseOperator &,
const Map &>);
STATIC_CHECK_FALSE(std::constructible_from<
normalization::ScaledInverseOperator,
DenseOperator &&,
const Map &>);
STATIC_CHECK_FALSE(std::constructible_from<
normalization::ScaledInverseOperator,
const DenseOperator &,
Map &&>);
STATIC_CHECK(std::constructible_from<normalization::ScaledInverseOperator, const DenseOperator &, const Map &>);
STATIC_CHECK_FALSE(std::constructible_from<normalization::ScaledInverseOperator, DenseOperator &&, const Map &>);
STATIC_CHECK_FALSE(std::constructible_from<normalization::ScaledInverseOperator, const DenseOperator &, Map &&>);
STATIC_CHECK(std::constructible_from<
normalization::ScaledPreconditioner,
DenseInverseSolver &,
const DenseOperator &,
const DenseOperator &,
const Map &>);
STATIC_CHECK_FALSE(std::constructible_from<
normalization::ScaledPreconditioner,
DenseInverseSolver &&,
const DenseOperator &,
const DenseOperator &,
const Map &>);
STATIC_CHECK_FALSE(std::constructible_from<
normalization::ScaledPreconditioner,
DenseInverseSolver &,
DenseOperator &&,
const DenseOperator &,
const Map &>);
STATIC_CHECK_FALSE(std::constructible_from<
normalization::ScaledPreconditioner,
DenseInverseSolver &,
const DenseOperator &,
DenseOperator &&,
const Map &>);
STATIC_CHECK_FALSE(std::constructible_from<
normalization::ScaledPreconditioner,
DenseInverseSolver &,
const DenseOperator &,
const DenseOperator &,
Map &&>);
STATIC_CHECK(
std::constructible_from<
normalization::ScaledPreconditioner, DenseInverseSolver &, const DenseOperator &, const DenseOperator &,
const Map &>
);
STATIC_CHECK_FALSE(
std::constructible_from<
normalization::ScaledPreconditioner, DenseInverseSolver &&, const DenseOperator &, const DenseOperator &,
const Map &>
);
STATIC_CHECK_FALSE(
std::constructible_from<
normalization::ScaledPreconditioner, DenseInverseSolver &, DenseOperator &&, const DenseOperator &,
const Map &>
);
STATIC_CHECK_FALSE(
std::constructible_from<
normalization::ScaledPreconditioner, DenseInverseSolver &, const DenseOperator &, DenseOperator &&,
const Map &>
);
STATIC_CHECK_FALSE(
std::constructible_from<
normalization::ScaledPreconditioner, DenseInverseSolver &, const DenseOperator &, const DenseOperator &,
Map &&>
);
}
TEST_CASE("Characteristic Stellar Scales Satisfy Gravity Virial And Rotation Identities", "[normalization][physics]") {
TEST_CASE(
"Characteristic Stellar Scales Satisfy Gravity Virial And Rotation Identities",
"[normalization][physics]"
) {
using namespace mean_field;
constexpr double mass = 7.0;
constexpr double radius = 3.0;
constexpr double mass = 7.0;
constexpr double radius = 3.0;
constexpr double gravity = 5.0;
const auto scales = normalization::deriveStellarCharacteristicScales(
const auto scales = normalization::deriveStellarCharacteristicScales(
dimensions::MassValue{mass}, dimensions::LengthValue{radius}, gravity
);
@@ -332,66 +303,87 @@ TEST_CASE("Characteristic Stellar Scales Satisfy Gravity Virial And Rotation Ide
CHECK(virial == Catch::Approx(scales.force * radius).epsilon(2.0e-15));
// Omega_0 is the Kepler/break-up scale and J_0 = M R^2 Omega_0.
CHECK(scales.angularVelocity * scales.angularVelocity * radius ==
Catch::Approx(scales.acceleration).epsilon(2.0e-15));
CHECK(scales.angularMomentum ==
Catch::Approx(mass * radius * radius * scales.angularVelocity).epsilon(2.0e-15));
CHECK(
scales.angularVelocity * scales.angularVelocity * radius == Catch::Approx(scales.acceleration).epsilon(2.0e-15)
);
CHECK(scales.angularMomentum == Catch::Approx(mass * radius * radius * scales.angularVelocity).epsilon(2.0e-15));
}
TEST_CASE("Characteristic Scales Obey The Expected Stellar Homology Exponents", "[normalization][physics]") {
TEST_CASE(
"Characteristic Scales Obey The Expected Stellar Homology Exponents",
"[normalization][physics]"
) {
using namespace mean_field;
const auto reference = normalization::deriveStellarCharacteristicScales(
dimensions::MassValue{2.5}, dimensions::LengthValue{4.0}, 3.0
);
constexpr double massFactor = 11.0;
constexpr double radiusFactor = 0.2;
const auto reference =
normalization::deriveStellarCharacteristicScales(dimensions::MassValue{2.5}, dimensions::LengthValue{4.0}, 3.0);
constexpr double massFactor = 11.0;
constexpr double radiusFactor = 0.2;
constexpr double gravityFactor = 7.0;
const auto transformed = normalization::deriveStellarCharacteristicScales(
dimensions::MassValue{2.5 * massFactor},
dimensions::LengthValue{4.0 * radiusFactor},
3.0 * gravityFactor
const auto transformed = normalization::deriveStellarCharacteristicScales(
dimensions::MassValue{2.5 * massFactor}, dimensions::LengthValue{4.0 * radiusFactor}, 3.0 * gravityFactor
);
CHECK(transformed.density / reference.density ==
Catch::Approx(massFactor / std::pow(radiusFactor, 3)).epsilon(4.0e-15));
CHECK(transformed.acceleration / reference.acceleration ==
Catch::Approx(gravityFactor * massFactor / std::pow(radiusFactor, 2)).epsilon(4.0e-15));
CHECK(transformed.inverseTimeSquared / reference.inverseTimeSquared ==
Catch::Approx(gravityFactor * massFactor / std::pow(radiusFactor, 3)).epsilon(4.0e-15));
CHECK(transformed.specificEnergy / reference.specificEnergy ==
Catch::Approx(gravityFactor * massFactor / radiusFactor).epsilon(4.0e-15));
CHECK(transformed.pressure / reference.pressure ==
Catch::Approx(gravityFactor * massFactor * massFactor / std::pow(radiusFactor, 4)).epsilon(4.0e-15));
CHECK(transformed.angularVelocity / reference.angularVelocity == Catch::Approx(
std::sqrt(gravityFactor * massFactor / std::pow(radiusFactor, 3))
).epsilon(4.0e-15));
CHECK(transformed.angularMomentum / reference.angularMomentum == Catch::Approx(
massFactor * std::sqrt(gravityFactor * massFactor * radiusFactor)
).epsilon(4.0e-15));
CHECK(
transformed.density / reference.density ==
Catch::Approx(massFactor / std::pow(radiusFactor, 3)).epsilon(4.0e-15)
);
CHECK(
transformed.acceleration / reference.acceleration ==
Catch::Approx(gravityFactor * massFactor / std::pow(radiusFactor, 2)).epsilon(4.0e-15)
);
CHECK(
transformed.inverseTimeSquared / reference.inverseTimeSquared ==
Catch::Approx(gravityFactor * massFactor / std::pow(radiusFactor, 3)).epsilon(4.0e-15)
);
CHECK(
transformed.specificEnergy / reference.specificEnergy ==
Catch::Approx(gravityFactor * massFactor / radiusFactor).epsilon(4.0e-15)
);
CHECK(
transformed.pressure / reference.pressure ==
Catch::Approx(gravityFactor * massFactor * massFactor / std::pow(radiusFactor, 4)).epsilon(4.0e-15)
);
CHECK(
transformed.angularVelocity / reference.angularVelocity ==
Catch::Approx(std::sqrt(gravityFactor * massFactor / std::pow(radiusFactor, 3))).epsilon(4.0e-15)
);
CHECK(
transformed.angularMomentum / reference.angularMomentum ==
Catch::Approx(massFactor * std::sqrt(gravityFactor * massFactor * radiusFactor)).epsilon(4.0e-15)
);
}
TEST_CASE("Physical Block Scales Distinguish Invariants From Numerical Phase Conditions", "[normalization][physics]") {
TEST_CASE(
"Physical Block Scales Distinguish Invariants From Numerical Phase Conditions",
"[normalization][physics]"
) {
using namespace mean_field;
const auto scales = normalization::deriveStellarCharacteristicScales(
dimensions::MassValue{9.0}, dimensions::LengthValue{2.0}, 4.0
);
const auto scales =
normalization::deriveStellarCharacteristicScales(dimensions::MassValue{9.0}, dimensions::LengthValue{2.0}, 4.0);
CHECK(normalization::physicalScale<blocks::density::mass::value>(scales) == scales.density);
CHECK(normalization::physicalScale<blocks::gravity::gradient::value>(scales) == scales.acceleration);
CHECK(normalization::physicalScale<blocks::gravity::poisson::residual>(scales) == scales.inverseTimeSquared);
CHECK(normalization::physicalScale<blocks::fixed_total_mass::mass_normalization::residual>(scales) == 9.0);
CHECK(normalization::physicalScale<blocks::fixed_angular_momentum::angular_velocity::value>(scales) ==
scales.angularVelocity);
CHECK(normalization::physicalScale<blocks::fixed_angular_momentum::angular_velocity::residual>(scales) ==
scales.angularMomentum);
CHECK(
normalization::physicalScale<blocks::fixed_angular_momentum::angular_velocity::value>(scales) ==
scales.angularVelocity
);
CHECK(
normalization::physicalScale<blocks::fixed_angular_momentum::angular_velocity::residual>(scales) ==
scales.angularMomentum
);
// The central-density condition is implemented as h(0)-h_target, so its residual scale is energy/mass,
// despite the physical target being expressed as a density.
CHECK(normalization::physicalScale<blocks::fixed_central_density::central_value::residual>(scales) ==
scales.specificEnergy);
CHECK(normalization::physicalScale<blocks::fixed_central_density::central_value::residual>(scales) !=
scales.density);
CHECK(
normalization::physicalScale<blocks::fixed_central_density::central_value::residual>(scales) ==
scales.specificEnergy
);
CHECK(
normalization::physicalScale<blocks::fixed_central_density::central_value::residual>(scales) != scales.density
);
}
TEST_CASE(
@@ -399,39 +391,40 @@ TEST_CASE(
"[normalization][type][extension]"
) {
using namespace mean_field;
using ValueTraits = normalization::PhysicalRieszBlockTraits<SelfDescribingValue>;
using ValueTraits = normalization::PhysicalRieszBlockTraits<SelfDescribingValue>;
using ResidualTraits = normalization::PhysicalRieszBlockTraits<SelfDescribingResidual>;
STATIC_CHECK(models::SelfDescribingModelSpecification<SelfDescribingMagneticSpecificEnergy>);
STATIC_CHECK(models::CompleteGeneratedNormalizationFor<SelfDescribingMagneticSpecificEnergy>);
STATIC_CHECK(operators::StellarEquilibriumSpecificationCompilable<SelfDescribingMagneticSpecificEnergy>);
STATIC_CHECK(normalization::GeneratedValuePhysicalRieszNormalizable<
models::PhysicalCoordinateFor<SelfDescribingMagneticSpecificEnergy>>);
STATIC_CHECK(normalization::GeneratedResidualPhysicalRieszNormalizable<
models::ResidualFor<SelfDescribingMagneticSpecificEnergy>>);
STATIC_CHECK(normalization::CompleteGeneratedPhysicalRieszNormalizationFor<
SelfDescribingMagneticSpecificEnergy>);
STATIC_CHECK(normalization::CompilableNormalizationFor<
normalization::PhysicalRieszDiagonal<>,
SelfDescribingForm>);
STATIC_CHECK(normalization::RegisteredStellarSpecificationNormalization<
SelfDescribingMagneticSpecificEnergy>);
STATIC_CHECK(normalization::CompleteStellarSpecificationNormalizationFor<
SelfDescribingMagneticSpecificEnergy,
SelfDescribingForm>);
STATIC_CHECK(
normalization::GeneratedValuePhysicalRieszNormalizable<
models::PhysicalCoordinateFor<SelfDescribingMagneticSpecificEnergy>>
);
STATIC_CHECK(
normalization::GeneratedResidualPhysicalRieszNormalizable<
models::ResidualFor<SelfDescribingMagneticSpecificEnergy>>
);
STATIC_CHECK(normalization::CompleteGeneratedPhysicalRieszNormalizationFor<SelfDescribingMagneticSpecificEnergy>);
STATIC_CHECK(normalization::CompilableNormalizationFor<normalization::PhysicalRieszDiagonal<>, SelfDescribingForm>);
STATIC_CHECK(normalization::RegisteredStellarSpecificationNormalization<SelfDescribingMagneticSpecificEnergy>);
STATIC_CHECK(
normalization::CompleteStellarSpecificationNormalizationFor<
SelfDescribingMagneticSpecificEnergy, SelfDescribingForm>
);
STATIC_CHECK(ValueTraits::Method::topology == normalization::RieszTopology::global_scalar);
STATIC_CHECK(ValueTraits::Method::scale == normalization::PhysicalScaleKind::dimensionless);
STATIC_CHECK(ResidualTraits::Method::topology == normalization::RieszTopology::global_scalar);
STATIC_CHECK(ResidualTraits::Method::scale == normalization::PhysicalScaleKind::specific_energy);
const auto scales = normalization::deriveStellarCharacteristicScales(
dimensions::MassValue{9.0}, dimensions::LengthValue{2.0}, 4.0
);
const auto scales =
normalization::deriveStellarCharacteristicScales(dimensions::MassValue{9.0}, dimensions::LengthValue{2.0}, 4.0);
const blocks::form_layout<SelfDescribingForm> layout({1}, {1});
normalization::DiagonalNormalizationBuilder<SelfDescribingForm> builder(layout);
normalization::StellarSpecificationNormalizationContribution<
SelfDescribingMagneticSpecificEnergy>::Apply(builder, scales);
normalization::StellarSpecificationNormalizationContribution<SelfDescribingMagneticSpecificEnergy>::Apply(
builder, scales
);
const normalization::DiagonalNormalization map = std::move(builder).Build();
REQUIRE(map.StateFactors().Size() == 1);
@@ -452,47 +445,51 @@ TEST_CASE(
STATIC_CHECK(models::ModelSpecification<MissingGeneratedNormalization>);
STATIC_CHECK_FALSE(models::CompleteGeneratedNormalizationFor<MissingGeneratedNormalization>);
STATIC_CHECK_FALSE(normalization::CompleteGeneratedPhysicalRieszNormalizationFor<
MissingGeneratedNormalization>);
STATIC_CHECK_FALSE(normalization::CompilableNormalizationFor<
normalization::PhysicalRieszDiagonal<>,
MissingNormalizationForm>);
STATIC_CHECK_FALSE(normalization::RegisteredStellarSpecificationNormalization<
MissingGeneratedNormalization>);
STATIC_CHECK_FALSE(normalization::CompleteStellarSpecificationNormalizationFor<
MissingGeneratedNormalization,
MissingNormalizationForm>);
STATIC_CHECK_FALSE(normalization::CompleteGeneratedPhysicalRieszNormalizationFor<MissingGeneratedNormalization>);
STATIC_CHECK_FALSE(
normalization::CompilableNormalizationFor<normalization::PhysicalRieszDiagonal<>, MissingNormalizationForm>
);
STATIC_CHECK_FALSE(normalization::RegisteredStellarSpecificationNormalization<MissingGeneratedNormalization>);
STATIC_CHECK_FALSE(
normalization::CompleteStellarSpecificationNormalizationFor<
MissingGeneratedNormalization, MissingNormalizationForm>
);
STATIC_CHECK(models::ModelSpecification<MalformedGeneratedNormalizationConstraint>);
STATIC_CHECK_FALSE(models::CompleteGeneratedNormalizationFor<
MalformedGeneratedNormalizationConstraint>);
STATIC_CHECK_FALSE(normalization::CompleteGeneratedPhysicalRieszNormalizationFor<
MalformedGeneratedNormalizationConstraint>);
STATIC_CHECK_FALSE(normalization::RegisteredStellarSpecificationNormalization<
MalformedGeneratedNormalizationConstraint>);
STATIC_CHECK_FALSE(models::CompleteGeneratedNormalizationFor<MalformedGeneratedNormalizationConstraint>);
STATIC_CHECK_FALSE(
normalization::CompleteGeneratedPhysicalRieszNormalizationFor<MalformedGeneratedNormalizationConstraint>
);
STATIC_CHECK_FALSE(
normalization::RegisteredStellarSpecificationNormalization<MalformedGeneratedNormalizationConstraint>
);
// The declaration itself is a valid Riesz law, but runtime stellar
// preparation has no finite-element Gram source for a generated volume
// field. The stronger runtime concept must therefore reject it.
STATIC_CHECK(normalization::CompleteGeneratedPhysicalRieszNormalizationFor<
GeneratedVolumeCoordinateWithoutMetricSource>);
STATIC_CHECK(normalization::CompilableNormalizationFor<
normalization::PhysicalRieszDiagonal<>,
UnpreparedVolumeForm>);
STATIC_CHECK_FALSE(normalization::RegisteredStellarSpecificationNormalization<
GeneratedVolumeCoordinateWithoutMetricSource>);
STATIC_CHECK_FALSE(normalization::CompleteStellarSpecificationNormalizationFor<
GeneratedVolumeCoordinateWithoutMetricSource,
UnpreparedVolumeForm>);
STATIC_CHECK(
normalization::CompleteGeneratedPhysicalRieszNormalizationFor<GeneratedVolumeCoordinateWithoutMetricSource>
);
STATIC_CHECK(
normalization::CompilableNormalizationFor<normalization::PhysicalRieszDiagonal<>, UnpreparedVolumeForm>
);
STATIC_CHECK_FALSE(
normalization::RegisteredStellarSpecificationNormalization<GeneratedVolumeCoordinateWithoutMetricSource>
);
STATIC_CHECK_FALSE(
normalization::CompleteStellarSpecificationNormalizationFor<
GeneratedVolumeCoordinateWithoutMetricSource, UnpreparedVolumeForm>
);
}
TEST_CASE("Characteristic Scale Construction Rejects Invalid Or Overflowing References", "[normalization][validation]") {
TEST_CASE(
"Characteristic Scale Construction Rejects Invalid Or Overflowing References",
"[normalization][validation]"
) {
using namespace mean_field;
CHECK_THROWS_AS(
normalization::deriveStellarCharacteristicScales(
dimensions::MassValue{0.0}, dimensions::LengthValue{1.0}, 1.0
),
normalization::deriveStellarCharacteristicScales(dimensions::MassValue{0.0}, dimensions::LengthValue{1.0}, 1.0),
std::invalid_argument
);
CHECK_THROWS_AS(
@@ -502,8 +499,7 @@ TEST_CASE("Characteristic Scale Construction Rejects Invalid Or Overflowing Refe
std::invalid_argument
);
CHECK_THROWS_AS(
(normalization::PhysicalRieszDiagonal{dimensions::LengthValue{1.0},
std::numeric_limits<double>::quiet_NaN()}),
(normalization::PhysicalRieszDiagonal{dimensions::LengthValue{1.0}, std::numeric_limits<double>::quiet_NaN()}),
std::invalid_argument
);
CHECK_THROWS_AS(
@@ -514,25 +510,26 @@ TEST_CASE("Characteristic Scale Construction Rejects Invalid Or Overflowing Refe
);
}
TEST_CASE("Diagonal Riesz Maps Reproduce Primal And Dual Norms Across Extreme Metrics", "[normalization][math]") {
TEST_CASE(
"Diagonal Riesz Maps Reproduce Primal And Dual Norms Across Extreme Metrics",
"[normalization][math]"
) {
const blocks::form_layout<TestForm> layout({3}, {3});
normalization::DiagonalNormalizationBuilder<TestForm> builder(layout);
const mfem::Vector gram = vector({1.0e-20, 4.0, 9.0e20});
constexpr double stateScale = 10.0;
const mfem::Vector gram = vector({1.0e-20, 4.0, 9.0e20});
constexpr double stateScale = 10.0;
constexpr double residualScale = 0.25;
builder.SetValueBlock<TestValue>(stateScale, gram);
builder.SetResidualBlock<TestResidual>(residualScale, gram);
const normalization::DiagonalNormalization map = std::move(builder).Build();
const mfem::Vector state = vector({3.0e10, -2.0, 4.0e-10});
const mfem::Vector residual = vector({2.0e-10, -3.0, 5.0e10});
double expectedPrimalNormSquared = 0.0;
double expectedDualNormSquared = 0.0;
const mfem::Vector state = vector({3.0e10, -2.0, 4.0e-10});
const mfem::Vector residual = vector({2.0e-10, -3.0, 5.0e10});
double expectedPrimalNormSquared = 0.0;
double expectedDualNormSquared = 0.0;
for (int index = 0; index < gram.Size(); ++index) {
expectedPrimalNormSquared += gram(index) * state(index) * state(index) /
(stateScale * stateScale);
expectedDualNormSquared += residual(index) * residual(index) /
(gram(index) * residualScale * residualScale);
expectedPrimalNormSquared += gram(index) * state(index) * state(index) / (stateScale * stateScale);
expectedDualNormSquared += residual(index) * residual(index) / (gram(index) * residualScale * residualScale);
}
CHECK(map.LocalStateNormSquared(state) == Catch::Approx(expectedPrimalNormSquared).epsilon(3.0e-15));
CHECK(map.LocalResidualNormSquared(residual) == Catch::Approx(expectedDualNormSquared).epsilon(3.0e-15));
@@ -549,10 +546,12 @@ TEST_CASE("Diagonal Riesz Maps Reproduce Primal And Dual Norms Across Extreme Me
checkVector(recoveredResidual, residual, 3.0e-15);
}
TEST_CASE("Hybrid Riesz Rows Replace Missing Volume Metrics With Point Metrics", "[normalization][math]") {
TEST_CASE(
"Hybrid Riesz Rows Replace Missing Volume Metrics With Point Metrics",
"[normalization][math]"
) {
using HybridForm = blocks::block_form<
blocks::type_list<blocks::enthalpy::specific::value>,
blocks::type_list<blocks::enthalpy::specific::residual>>;
blocks::type_list<blocks::enthalpy::specific::value>, blocks::type_list<blocks::enthalpy::specific::residual>>;
const blocks::form_layout<HybridForm> layout({4}, {4});
normalization::DiagonalNormalizationBuilder<HybridForm> builder(layout);
builder.SetValueBlock<blocks::enthalpy::specific::value>(2.0, vector({2.0, 3.0, 5.0, 7.0}));
@@ -580,7 +579,10 @@ TEST_CASE("Hybrid Riesz Rows Replace Missing Volume Metrics With Point Metrics",
);
}
TEST_CASE("Runtime Normalization Assembly Rejects Missing Duplicate And Invalid Data", "[normalization][validation]") {
TEST_CASE(
"Runtime Normalization Assembly Rejects Missing Duplicate And Invalid Data",
"[normalization][validation]"
) {
const blocks::form_layout<TestForm> layout({2}, {2});
normalization::DiagonalNormalizationBuilder<TestForm> missing(layout);
@@ -603,8 +605,11 @@ TEST_CASE("Runtime Normalization Assembly Rejects Missing Duplicate And Invalid
);
}
TEST_CASE("Scaled Jacobian And Inverse Implement The Exact Coordinate Change", "[normalization][linear-algebra]") {
const mfem::Vector stateFactors = vector({1.0e-9, 2.0e7});
TEST_CASE(
"Scaled Jacobian And Inverse Implement The Exact Coordinate Change",
"[normalization][linear-algebra]"
) {
const mfem::Vector stateFactors = vector({1.0e-9, 2.0e7});
const mfem::Vector residualFactors = vector({5.0e8, 3.0e-6});
const normalization::DiagonalNormalization map(stateFactors, residualFactors);
@@ -617,10 +622,8 @@ TEST_CASE("Scaled Jacobian And Inverse Implement The Exact Coordinate Change", "
mfem::DenseMatrix inverse(2);
for (int row = 0; row < 2; ++row) {
for (int column = 0; column < 2; ++column) {
matrix(row, column) = normalizedMatrix[row][column] * stateFactors(column) /
residualFactors(row);
inverse(row, column) = normalizedInverse[row][column] * residualFactors(column) /
stateFactors(row);
matrix(row, column) = normalizedMatrix[row][column] * stateFactors(column) / residualFactors(row);
inverse(row, column) = normalizedInverse[row][column] * residualFactors(column) / stateFactors(row);
}
}
const DenseOperator physicalJacobian(matrix);
@@ -641,8 +644,11 @@ TEST_CASE("Scaled Jacobian And Inverse Implement The Exact Coordinate Change", "
checkVector(recovered, direction, 2.0e-13);
}
TEST_CASE("Scaled Preconditioning Routes An Exact Physical Inverse Through FGMRES", "[normalization][solver]") {
const mfem::Vector stateFactors = vector({1.0e-9, 2.0e7});
TEST_CASE(
"Scaled Preconditioning Routes An Exact Physical Inverse Through FGMRES",
"[normalization][solver]"
) {
const mfem::Vector stateFactors = vector({1.0e-9, 2.0e7});
const mfem::Vector residualFactors = vector({5.0e8, 3.0e-6});
const normalization::DiagonalNormalization map(stateFactors, residualFactors);
@@ -652,19 +658,16 @@ TEST_CASE("Scaled Preconditioning Routes An Exact Physical Inverse Through FGMRE
mfem::DenseMatrix physicalInverseMatrix(2);
for (int row = 0; row < 2; ++row) {
for (int column = 0; column < 2; ++column) {
physicalMatrix(row, column) = normalizedMatrix[row][column] * stateFactors(column) /
residualFactors(row);
physicalInverseMatrix(row, column) = normalizedInverse[row][column] * residualFactors(column) /
stateFactors(row);
physicalMatrix(row, column) = normalizedMatrix[row][column] * stateFactors(column) / residualFactors(row);
physicalInverseMatrix(row, column) =
normalizedInverse[row][column] * residualFactors(column) / stateFactors(row);
}
}
const DenseOperator physicalJacobian(physicalMatrix);
const normalization::ScaledJacobianOperator scaledJacobian(physicalJacobian, map);
DenseInverseSolver physicalInverse(physicalInverseMatrix);
normalization::ScaledPreconditioner scaledPreconditioner(
physicalInverse, physicalJacobian, scaledJacobian, map
);
normalization::ScaledPreconditioner scaledPreconditioner(physicalInverse, physicalJacobian, scaledJacobian, map);
CHECK(physicalInverse.BoundOperator() == &physicalJacobian);
CHECK(&scaledPreconditioner.GetPhysicalJacobian() == &physicalJacobian);
@@ -697,18 +700,18 @@ TEST_CASE("Scaled Preconditioning Routes An Exact Physical Inverse Through FGMRE
CHECK(scaledPreconditioner.GetStatistics().applications >= 2);
mfem::IdentityOperator differentNormalizedJacobian(2);
CHECK_THROWS_AS(
scaledPreconditioner.SetOperator(differentNormalizedJacobian),
std::invalid_argument
);
CHECK_THROWS_AS(scaledPreconditioner.SetOperator(differentNormalizedJacobian), std::invalid_argument);
mfem::IdentityOperator wrongSize(3);
CHECK_THROWS_AS(scaledPreconditioner.SetOperator(wrongSize), std::invalid_argument);
mfem::Vector wrongCorrection(1);
CHECK_THROWS_AS(scaledPreconditioner.Mult(rightHandSide, wrongCorrection), std::invalid_argument);
}
TEST_CASE("Physical Riesz Scaling Collapses A Forty-Eight-Decade Diagonal Imbalance", "[normalization][numerics]") {
const mfem::Vector stateFactors = vector({1.0e-12, 1.0, 1.0e12});
TEST_CASE(
"Physical Riesz Scaling Collapses A Forty-Eight-Decade Diagonal Imbalance",
"[normalization][numerics]"
) {
const mfem::Vector stateFactors = vector({1.0e-12, 1.0, 1.0e12});
const mfem::Vector residualFactors = vector({1.0e12, 1.0, 1.0e-12});
const normalization::DiagonalNormalization map(stateFactors, residualFactors);
@@ -727,38 +730,38 @@ TEST_CASE("Physical Riesz Scaling Collapses A Forty-Eight-Decade Diagonal Imbala
checkVector(action, direction, 4.0e-15);
}
TEST_CASE("A Compiled Stellar Problem Prepares Reference Physical Riesz Coordinates", "[normalization][integration]") {
TEST_CASE(
"A Compiled Stellar Problem Prepares Reference Physical Riesz Coordinates",
"[normalization][integration]"
) {
using namespace mean_field;
utils::Args args = test_utils::setup_args();
utils::Args args = test_utils::setup_args();
fem::FEM finiteElements = fem::setup_fem(args.mesh_file, args, 0);
REQUIRE(finiteElements.okay());
constexpr double targetMass = 2.0;
constexpr double referenceRadius = 1.25;
constexpr double targetMass = 2.0;
constexpr double referenceRadius = 1.25;
constexpr double gravitationalConstant = 3.0;
const normalization::PhysicalRieszDiagonal policy{
dimensions::LengthValue{referenceRadius}, gravitationalConstant
};
const auto discretization = equilibrium::makeStellarDiscretization(finiteElements, policy);
auto problem = equilibrium::discretize(
const normalization::PhysicalRieszDiagonal policy{dimensions::LengthValue{referenceRadius}, gravitationalConstant};
auto discretization = equilibrium::makeStellarDiscretization(std::move(finiteElements), policy);
auto problem = equilibrium::discretize(
model::StellarModel(
eos::Polytrope({.n = 3.0, .K = 0.25}),
surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}),
eos::Polytrope({.n = 3.0, .K = 0.25}), surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}),
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{targetMass}}),
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{1.0}})
),
discretization
std::move(discretization)
);
using ProblemType = std::remove_cvref_t<decltype(problem)>;
using Form = typename ProblemType::FormType;
using ModelType = std::remove_cvref_t<decltype(problem.GetStellarModel())>;
using Form = typename ProblemType::FormType;
using ModelType = std::remove_cvref_t<decltype(problem.GetStellarModel())>;
STATIC_CHECK(SupportsModelDerivedStellarScales<ModelType>);
STATIC_CHECK_FALSE(SupportsModelDerivedStellarScales<ModelWithoutFixedTotalMass>);
STATIC_CHECK(std::same_as<
typename ProblemType::NormalizationPrescriptionType,
std::remove_cvref_t<decltype(policy)>>);
STATIC_CHECK(
std::same_as<typename ProblemType::NormalizationPrescriptionType, std::remove_cvref_t<decltype(policy)>>
);
CHECK(problem.GetNormalizationPrescription().referenceRadius() == dimensions::LengthValue{referenceRadius});
const normalization::DiagonalNormalization map = normalization::prepareNormalization(problem);
@@ -773,39 +776,41 @@ TEST_CASE("A Compiled Stellar Problem Prepares Reference Physical Riesz Coordina
CHECK(map.ResidualFactors()(index) > 0.0);
}
const auto scales = normalization::deriveStellarCharacteristicScales(policy, problem.GetStellarModel());
const auto scales = normalization::deriveStellarCharacteristicScales(policy, problem.GetStellarModel());
const auto &layout = problem.GetManifest().layout();
constexpr int massValueBlock = blocks::type_index_v<
blocks::fixed_total_mass::mass_normalization::value,
typename Form::value_blocks>;
constexpr int massResidualBlock = blocks::type_index_v<
blocks::fixed_total_mass::mass_normalization::residual,
typename Form::residual_blocks>;
constexpr int phaseValueBlock = blocks::type_index_v<
blocks::fixed_central_density::central_value::value,
typename Form::value_blocks>;
constexpr int phaseResidualBlock = blocks::type_index_v<
blocks::fixed_central_density::central_value::residual,
typename Form::residual_blocks>;
constexpr int enthalpyResidualBlock = blocks::type_index_v<
blocks::enthalpy::specific::residual,
typename Form::residual_blocks>;
constexpr int massValueBlock =
blocks::type_index_v<blocks::fixed_total_mass::mass_normalization::value, typename Form::value_blocks>;
constexpr int massResidualBlock =
blocks::type_index_v<blocks::fixed_total_mass::mass_normalization::residual, typename Form::residual_blocks>;
constexpr int phaseValueBlock =
blocks::type_index_v<blocks::fixed_central_density::central_value::value, typename Form::value_blocks>;
constexpr int phaseResidualBlock =
blocks::type_index_v<blocks::fixed_central_density::central_value::residual, typename Form::residual_blocks>;
constexpr int enthalpyResidualBlock =
blocks::type_index_v<blocks::enthalpy::specific::residual, typename Form::residual_blocks>;
CHECK(map.StateFactors()(layout.value_offsets()[massValueBlock]) ==
Catch::Approx(1.0 / scales.specificEnergy).epsilon(2.0e-15));
CHECK(map.ResidualFactors()(layout.residual_offsets()[massResidualBlock]) ==
Catch::Approx(1.0 / targetMass).epsilon(2.0e-15));
CHECK(map.StateFactors()(layout.value_offsets()[phaseValueBlock]) ==
Catch::Approx(1.0 / scales.specificEnergy).epsilon(2.0e-15));
CHECK(map.ResidualFactors()(layout.residual_offsets()[phaseResidualBlock]) ==
Catch::Approx(1.0 / scales.specificEnergy).epsilon(2.0e-15));
CHECK(
map.StateFactors()(layout.value_offsets()[massValueBlock]) ==
Catch::Approx(1.0 / scales.specificEnergy).epsilon(2.0e-15)
);
CHECK(
map.ResidualFactors()(layout.residual_offsets()[massResidualBlock]) ==
Catch::Approx(1.0 / targetMass).epsilon(2.0e-15)
);
CHECK(
map.StateFactors()(layout.value_offsets()[phaseValueBlock]) ==
Catch::Approx(1.0 / scales.specificEnergy).epsilon(2.0e-15)
);
CHECK(
map.ResidualFactors()(layout.residual_offsets()[phaseResidualBlock]) ==
Catch::Approx(1.0 / scales.specificEnergy).epsilon(2.0e-15)
);
const auto &surfaceRows = problem.GetPressureSurfaceRows().reduced_dofs();
REQUIRE(surfaceRows.Size() > 0);
for (const int row : surfaceRows) {
const int rootRow = layout.residual_offsets()[enthalpyResidualBlock] + row;
CHECK(map.ResidualFactors()(rootRow) ==
Catch::Approx(1.0 / scales.specificEnergy).epsilon(2.0e-15));
CHECK(map.ResidualFactors()(rootRow) == Catch::Approx(1.0 / scales.specificEnergy).epsilon(2.0e-15));
}
mfem::Vector physicalState(problem.StateSize());

View File

@@ -7,13 +7,13 @@
import mean_field;
namespace {
namespace blocks = mean_field::utils::blocks;
namespace blocks = mean_field::utils::blocks;
namespace normalization = mean_field::normalization;
using PhysicalForm = blocks::surface_deformed_stellar_equilibrium_form;
using PhaseForm = blocks::central_density_bordered_stellar_equilibrium_form;
using PhysicalPlan = normalization::PhysicalRieszNormalizationPlanFor<PhysicalForm>;
using PhasePlan = normalization::PhysicalRieszNormalizationPlanFor<PhaseForm>;
using PhysicalForm = blocks::surface_deformed_stellar_equilibrium_form;
using PhaseForm = blocks::central_density_bordered_stellar_equilibrium_form;
using PhysicalPlan = normalization::PhysicalRieszNormalizationPlanFor<PhysicalForm>;
using PhasePlan = normalization::PhysicalRieszNormalizationPlanFor<PhaseForm>;
struct ValueA final : blocks::value_block_base { };
struct ValueB final : blocks::value_block_base { };
@@ -22,9 +22,7 @@ namespace {
struct ForeignValue final : blocks::value_block_base { };
struct ForeignResidual final : blocks::residual_block_base { };
using SmallForm = blocks::block_form<
blocks::type_list<ValueA, ValueB>,
blocks::type_list<ResidualA, ResidualB>>;
using SmallForm = blocks::block_form<blocks::type_list<ValueA, ValueB>, blocks::type_list<ResidualA, ResidualB>>;
using ValueAIdentity = normalization::CoordinateComponent<
normalization::CoordinateKind::value,
blocks::type_list<ValueA>,
@@ -50,21 +48,11 @@ namespace {
blocks::type_list<ForeignResidual>,
normalization::IdentityCoordinate>;
using CompleteSmallPlan = normalization::NormalizationPlan<
ValueAIdentity,
ValueBIdentity,
ResidualAIdentity,
ResidualBIdentity>;
using MissingSmallPlan = normalization::NormalizationPlan<
ValueAIdentity,
ResidualAIdentity,
ResidualBIdentity>;
using DuplicateSmallPlan = normalization::NormalizationPlan<
ValueAIdentity,
ValueAIdentity,
ValueBIdentity,
ResidualAIdentity,
ResidualBIdentity>;
using CompleteSmallPlan =
normalization::NormalizationPlan<ValueAIdentity, ValueBIdentity, ResidualAIdentity, ResidualBIdentity>;
using MissingSmallPlan = normalization::NormalizationPlan<ValueAIdentity, ResidualAIdentity, ResidualBIdentity>;
using DuplicateSmallPlan = normalization::
NormalizationPlan<ValueAIdentity, ValueAIdentity, ValueBIdentity, ResidualAIdentity, ResidualBIdentity>;
using ForeignSmallPlan = normalization::NormalizationPlan<
ValueAIdentity,
ValueBIdentity,
@@ -79,11 +67,11 @@ namespace {
};
struct IncoherentComponent final {
using Blocks = blocks::type_list<ValueA>;
using Method = normalization::IdentityCoordinate;
using ValueBlocks = blocks::type_list<ValueB>;
using ResidualBlocks = blocks::type_list<>;
static constexpr auto kind = normalization::CoordinateKind::value;
using Blocks = blocks::type_list<ValueA>;
using Method = normalization::IdentityCoordinate;
using ValueBlocks = blocks::type_list<ValueB>;
using ResidualBlocks = blocks::type_list<>;
static constexpr auto kind = normalization::CoordinateKind::value;
};
using WrongDensityTopology = normalization::CoordinateComponent<
@@ -95,40 +83,34 @@ namespace {
struct FutureInvariantValue final : blocks::value_block_base { };
struct FutureInvariantResidual final : blocks::residual_block_base { };
using UnregisteredFutureForm = blocks::block_form<
blocks::type_list<FutureInvariantValue>,
blocks::type_list<FutureInvariantResidual>>;
using UnregisteredFutureForm =
blocks::block_form<blocks::type_list<FutureInvariantValue>, blocks::type_list<FutureInvariantResidual>>;
using BaseModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
using BaseModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
mean_field::eos::Polytrope,
mean_field::surface::Isobaric,
mean_field::integral::FixedTotalMass>>;
using RieszPolicy = normalization::PhysicalRieszDiagonal<>;
using RieszPolicy = normalization::PhysicalRieszDiagonal<>;
using RieszDiscretization = mean_field::equilibrium::StellarDiscretizationFor<RieszPolicy>;
using BaselineProblem = mean_field::equilibrium::StellarEquilibriumProblem<BaseModel>;
using RieszProblem = mean_field::equilibrium::StellarEquilibriumProblem<BaseModel, RieszDiscretization>;
using AngularModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
using BaselineProblem = mean_field::equilibrium::StellarEquilibriumProblem<BaseModel>;
using RieszProblem = mean_field::equilibrium::StellarEquilibriumProblem<BaseModel, RieszDiscretization>;
using AngularModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
mean_field::eos::Polytrope,
mean_field::surface::Isobaric,
mean_field::integral::FixedTotalMass,
mean_field::integral::FixedAngularMomentum>>;
using AngularForm = mean_field::operators::CompiledStellarEquilibriumForm<AngularModel>;
using AngularForm = mean_field::operators::CompiledStellarEquilibriumForm<AngularModel>;
template <typename Mapper>
concept CanMakeRieszDiscretization = requires(
mean_field::fem::FEM &finiteElements,
Mapper &&mapper,
RieszPolicy policy
) {
mean_field::equilibrium::makeStellarDiscretization(
finiteElements,
std::forward<Mapper>(mapper),
policy
);
template <typename FiniteElements>
concept CanMakeRieszDiscretization = requires(FiniteElements &&finiteElements, RieszPolicy policy) {
mean_field::equilibrium::makeStellarDiscretization(std::forward<FiniteElements>(finiteElements), policy);
};
} // namespace
TEST_CASE("Normalization Plans Prove Exact Ownership Of Every Compiled Coordinate", "[normalization][type]") {
TEST_CASE(
"Normalization Plans Prove Exact Ownership Of Every Compiled Coordinate",
"[normalization][type]"
) {
STATIC_CHECK(normalization::NormalizationPlanType<PhysicalPlan>);
STATIC_CHECK(normalization::CompleteNormalizationFor<PhysicalPlan, PhysicalForm>);
STATIC_CHECK(normalization::CompleteNormalizationFor<PhasePlan, PhaseForm>);
@@ -136,17 +118,19 @@ TEST_CASE("Normalization Plans Prove Exact Ownership Of Every Compiled Coordinat
STATIC_CHECK(normalization::CompilableNormalizationFor<RieszPolicy, PhysicalForm>);
STATIC_CHECK(normalization::CompilableNormalizationFor<RieszPolicy, PhaseForm>);
STATIC_CHECK(normalization::CompilableNormalizationFor<RieszPolicy, AngularForm>);
STATIC_CHECK(normalization::StellarSpecificationNormalizationContribution<
mean_field::integral::FixedAngularMomentum>::registered);
STATIC_CHECK(
normalization::StellarSpecificationNormalizationContribution<
mean_field::integral::FixedAngularMomentum>::registered
);
STATIC_CHECK(normalization::CompleteNormalizationFor<CompleteSmallPlan, SmallForm>);
STATIC_CHECK_FALSE(normalization::CompleteNormalizationFor<MissingSmallPlan, SmallForm>);
STATIC_CHECK_FALSE(normalization::CompleteNormalizationFor<DuplicateSmallPlan, SmallForm>);
STATIC_CHECK_FALSE(normalization::CompleteNormalizationFor<ForeignSmallPlan, SmallForm>);
using Missing = normalization::NormalizationCoverage<SmallForm, MissingSmallPlan>;
using Missing = normalization::NormalizationCoverage<SmallForm, MissingSmallPlan>;
using Duplicate = normalization::NormalizationCoverage<SmallForm, DuplicateSmallPlan>;
using Foreign = normalization::NormalizationCoverage<SmallForm, ForeignSmallPlan>;
using Foreign = normalization::NormalizationCoverage<SmallForm, ForeignSmallPlan>;
STATIC_CHECK(Missing::MissingValueBlocks::size == 1);
STATIC_CHECK(blocks::contains_type_v<ValueB, typename Missing::MissingValueBlocks>);
STATIC_CHECK(Duplicate::RepeatedValueBlocks::size == 1);
@@ -155,72 +139,54 @@ TEST_CASE("Normalization Plans Prove Exact Ownership Of Every Compiled Coordinat
STATIC_CHECK(Foreign::UnexpectedResidualBlocks::size == 1);
}
TEST_CASE("Physical Riesz Methods Reject Incompatible Or Unregistered Field Topologies", "[normalization][type]") {
TEST_CASE(
"Physical Riesz Methods Reject Incompatible Or Unregistered Field Topologies",
"[normalization][type]"
) {
STATIC_CHECK_FALSE(normalization::NormalizationComponent<MalformedComponent>);
STATIC_CHECK_FALSE(normalization::NormalizationComponent<IncoherentComponent>);
STATIC_CHECK_FALSE(normalization::NormalizationComponent<WrongDensityTopology>);
STATIC_CHECK_FALSE(normalization::CompilableNormalizationFor<RieszPolicy, UnregisteredFutureForm>);
STATIC_CHECK(normalization::CompilableNormalizationFor<normalization::Unnormalized, UnregisteredFutureForm>);
using Density = normalization::PhysicalRieszBlockTraits<blocks::density::mass::value>;
using Gravity = normalization::PhysicalRieszBlockTraits<blocks::gravity::gradient::value>;
using Surface = normalization::PhysicalRieszBlockTraits<blocks::surface_deformation::parameters::value>;
using Density = normalization::PhysicalRieszBlockTraits<blocks::density::mass::value>;
using Gravity = normalization::PhysicalRieszBlockTraits<blocks::gravity::gradient::value>;
using Surface = normalization::PhysicalRieszBlockTraits<blocks::surface_deformation::parameters::value>;
using EnthalpyResidual = normalization::PhysicalRieszBlockTraits<blocks::enthalpy::specific::residual>;
using MassResidual = normalization::PhysicalRieszBlockTraits<
blocks::fixed_total_mass::mass_normalization::residual>;
using MassResidual =
normalization::PhysicalRieszBlockTraits<blocks::fixed_total_mass::mass_normalization::residual>;
STATIC_CHECK(Density::Method::topology == normalization::RieszTopology::scalar_volume_l2);
STATIC_CHECK(Gravity::Method::topology == normalization::RieszTopology::vector_volume_l2);
STATIC_CHECK(Surface::Method::topology == normalization::RieszTopology::scalar_boundary_l2);
STATIC_CHECK(
EnthalpyResidual::Method::topology == normalization::RieszTopology::hybrid_scalar_volume_point_rows
);
STATIC_CHECK(EnthalpyResidual::Method::topology == normalization::RieszTopology::hybrid_scalar_volume_point_rows);
STATIC_CHECK(MassResidual::Method::topology == normalization::RieszTopology::global_scalar);
STATIC_CHECK(MassResidual::Method::scale == normalization::PhysicalScaleKind::mass);
}
TEST_CASE("Normalization Is Part Of The Compile-Time Discretization And Problem Type", "[normalization][type]") {
TEST_CASE(
"Normalization Is Part Of The Compile-Time Discretization And Problem Type",
"[normalization][type]"
) {
STATIC_CHECK(mean_field::equilibrium::StellarDiscretizationType<RieszDiscretization>);
STATIC_CHECK_FALSE(std::same_as<RieszDiscretization, mean_field::equilibrium::StellarDiscretization>);
STATIC_CHECK_FALSE(std::same_as<RieszProblem, BaselineProblem>);
STATIC_CHECK(std::same_as<typename BaselineProblem::NormalizationPrescriptionType, normalization::Unnormalized>);
STATIC_CHECK(std::same_as<typename RieszProblem::NormalizationPrescriptionType, RieszPolicy>);
STATIC_CHECK(mean_field::equilibrium::DiscretizedStellarEquilibriumProblem<RieszProblem>);
STATIC_CHECK(std::constructible_from<
RieszDiscretization,
mean_field::fem::FEM &,
const mean_field::mapping::DomainMapper &,
RieszPolicy>);
STATIC_CHECK_FALSE(std::constructible_from<
RieszDiscretization,
mean_field::fem::FEM &,
mean_field::mapping::DomainMapper &&,
RieszPolicy>);
STATIC_CHECK_FALSE(std::constructible_from<
RieszDiscretization,
mean_field::fem::FEM &,
const mean_field::mapping::DomainMapper &&,
RieszPolicy>);
STATIC_CHECK(std::constructible_from<
mean_field::equilibrium::StellarDiscretization,
mean_field::fem::FEM &,
const mean_field::mapping::DomainMapper &>);
STATIC_CHECK_FALSE(std::constructible_from<
mean_field::equilibrium::StellarDiscretization,
mean_field::fem::FEM &,
mean_field::mapping::DomainMapper &&>);
STATIC_CHECK_FALSE(std::constructible_from<
mean_field::equilibrium::StellarDiscretization,
mean_field::fem::FEM &,
const mean_field::mapping::DomainMapper &&>);
STATIC_CHECK(CanMakeRieszDiscretization<
mean_field::mapping::DomainMapper &>);
STATIC_CHECK_FALSE(CanMakeRieszDiscretization<
mean_field::mapping::DomainMapper>);
STATIC_CHECK_FALSE(CanMakeRieszDiscretization<
const mean_field::mapping::DomainMapper>);
STATIC_CHECK(std::constructible_from<RieszDiscretization, mean_field::fem::FEM &&, RieszPolicy>);
STATIC_CHECK_FALSE(std::constructible_from<RieszDiscretization, mean_field::fem::FEM &, RieszPolicy>);
STATIC_CHECK_FALSE(std::constructible_from<RieszDiscretization, const mean_field::fem::FEM &, RieszPolicy>);
STATIC_CHECK(std::constructible_from<mean_field::equilibrium::StellarDiscretization, mean_field::fem::FEM &&>);
STATIC_CHECK_FALSE(std::constructible_from<mean_field::equilibrium::StellarDiscretization, mean_field::fem::FEM &>);
STATIC_CHECK_FALSE(
std::constructible_from<mean_field::equilibrium::StellarDiscretization, const mean_field::fem::FEM &>
);
STATIC_CHECK(CanMakeRieszDiscretization<mean_field::fem::FEM>);
STATIC_CHECK_FALSE(CanMakeRieszDiscretization<mean_field::fem::FEM &>);
STATIC_CHECK_FALSE(CanMakeRieszDiscretization<const mean_field::fem::FEM &>);
using SmallLayout = blocks::form_layout<SmallForm>;
using SmallLayout = blocks::form_layout<SmallForm>;
using SmallBuilder = normalization::DiagonalNormalizationBuilder<SmallForm>;
STATIC_CHECK(std::constructible_from<SmallBuilder, const SmallLayout &>);
STATIC_CHECK_FALSE(std::constructible_from<SmallBuilder, SmallLayout &&>);

File diff suppressed because it is too large Load Diff

View File

@@ -9,6 +9,28 @@ using namespace mean_field;
namespace prepared_test = gravity_prepared_test_utils;
namespace gravity_context = operators::context::gravity_field;
namespace {
[[nodiscard]] mfem::Vector makeAffineDisplacement(
const fem::FEM &finiteElements,
const double scale
) {
mfem::ParGridFunction field(finiteElements.displacementFes.get());
mfem::VectorFunctionCoefficient coefficient(
finiteElements.mesh->Dimension(), [scale](const mfem::Vector &position, mfem::Vector &value) {
value.SetSize(position.Size());
for (int component = 0; component < position.Size(); ++component) {
value(component) = scale * position(component);
}
}
);
field.ProjectCoefficient(coefficient);
mfem::Vector result;
field.GetTrueDofs(result);
return result;
}
} // namespace
TEST_CASE(
"Gravity Field Linearization Context Applies Selective Invalidation",
tags::gravity_context
@@ -125,6 +147,70 @@ TEST_CASE(
CHECK(context.GetGeometryContext().GetSourceOperator().GetPreparationCount() == 1);
}
TEST_CASE(
"Gravity Field Contexts Invalidate And Recover After Candidate Rejection",
tags::gravity_context &tags::geometry &tags::unit
) {
auto args = test_utils::setup_args();
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
REQUIRE(f.okay());
gravity_context::GravityFieldGeometryContext geometryContext(f, *f.domainMapperStateless);
const mfem::Vector zeroDisplacement = geometryContext.GetDisplacementMap().gather(makeAffineDisplacement(f, 0.0));
const mfem::Vector foldedDisplacement =
geometryContext.GetDisplacementMap().gather(makeAffineDisplacement(f, -2.0));
REQUIRE(geometryContext.TryPrepare(zeroDisplacement, {.value = 0}, {.value = 0}).has_value());
REQUIRE(geometryContext.IsPrepared());
const auto geometryRejection = geometryContext.TryPrepare(foldedDisplacement, {.value = 0}, {.value = 1});
REQUIRE_FALSE(geometryRejection.has_value());
CHECK(geometryRejection.error().reason == gravity_context::GravityFieldPreparationRejectionReason::invalid_mapping);
CHECK(geometryRejection.error().mappingStatus == mapping::MappingStatus::non_positive_determinant);
CHECK_FALSE(geometryContext.IsPrepared());
const auto geometryRecovery = geometryContext.TryPrepare(zeroDisplacement, {.value = 0}, {.value = 1});
REQUIRE(geometryRecovery.has_value());
CHECK(geometryRecovery->reconstructed_operators);
CHECK(geometryContext.IsPrepared());
gravity_context::GravityFieldLinearizationContext linearizationContext(f, *f.domainMapperStateless);
mfem::Vector density =
prepared_test::make_deterministic_vector(linearizationContext.GetDensityMap().reduced_size(), 0.17);
mfem::Vector gravityGradient =
prepared_test::make_deterministic_vector(linearizationContext.GetGravityGradientMap().reduced_size(), 0.41);
mfem::Vector gravityPotential =
prepared_test::make_deterministic_vector(linearizationContext.GetGravityPotentialMap().reduced_size(), 0.73);
gravity_context::GravityFieldRevisions revisions;
const auto makeState = [&](const mfem::Vector &displacement) {
return gravity_context::GravityFieldStateView{
.density = density,
.displacement = displacement,
.gravity_gradient = gravityGradient,
.gravity_potential = gravityPotential
};
};
REQUIRE(linearizationContext.TryPrepare(makeState(zeroDisplacement), revisions).has_value());
REQUIRE(linearizationContext.IsPrepared());
revisions.displacement.value = 1;
const auto linearizationRejection = linearizationContext.TryPrepare(makeState(foldedDisplacement), revisions);
REQUIRE_FALSE(linearizationRejection.has_value());
CHECK(
linearizationRejection.error().reason ==
gravity_context::GravityFieldPreparationRejectionReason::invalid_mapping
);
CHECK(linearizationRejection.error().mappingStatus == mapping::MappingStatus::non_positive_determinant);
CHECK_FALSE(linearizationContext.IsPrepared());
const auto linearizationRecovery = linearizationContext.TryPrepare(makeState(zeroDisplacement), revisions);
REQUIRE(linearizationRecovery.has_value());
CHECK(linearizationRecovery->geometry.reconstructed_operators);
CHECK(linearizationContext.IsPrepared());
}
TEST_CASE(
"Gravity Field Geometry Context Distinguishes Primal And Linearization Preparation",
tags::gravity_context

View File

@@ -2,6 +2,7 @@
#include <array>
#include <cmath>
#include <limits>
#include <stdexcept>
#include <catch2/catch_test_macros.hpp>
@@ -177,6 +178,26 @@ namespace gravity_displacement_force_test_utils {
return direction;
}
[[nodiscard]] mfem::Vector make_affine_displacement(
const mean_field::fem::FEM &f,
const double scale
) {
mfem::ParGridFunction field(f.displacementFes.get());
mfem::VectorFunctionCoefficient coefficient(
f.mesh->Dimension(), [scale](const mfem::Vector &position, mfem::Vector &value) {
value.SetSize(position.Size());
for (int dimension = 0; dimension < position.Size(); ++dimension) {
value(dimension) = scale * position(dimension);
}
}
);
field.ProjectCoefficient(coefficient);
mfem::Vector result;
field.GetTrueDofs(result);
return result;
}
[[nodiscard]] mfem::Vector make_vacuum_only_density(const mean_field::fem::FEM &f) {
mfem::ParGridFunction densityField(f.densityFes.get());
densityField = 0.0;
@@ -422,6 +443,85 @@ TEST_CASE(
CHECK(gravity_prepared_test_utils::global_norm(vacuumResidual, f.mesh->GetComm()) == 0.0);
}
TEST_CASE(
"Gravity Displacement Force Reports Candidate Mapping And Arithmetic Rejections",
tags::gravity_unit
) {
mean_field::utils::Args args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
REQUIRE(f.okay());
mfem::Vector density = gravity_displacement_force_test_utils::make_density(f, 0.21);
mfem::Vector gravityGradient = gravity_displacement_force_test_utils::make_gravity_gradient(f, 0.37);
mfem::Vector displacement = gravity_displacement_force_test_utils::make_affine_displacement(f, -2.0);
mfem::Vector residual;
const auto invalidMapping = mean_field::operators::kernels::try_apply_gravity_displacement_force_residual(
f, *f.domainMapperStateless, density, gravityGradient, displacement, residual
);
REQUIRE_FALSE(invalidMapping.has_value());
CHECK(
invalidMapping.error().reason ==
mean_field::operators::kernels::GravityDisplacementForceRejectionReason::invalid_mapping
);
CHECK(invalidMapping.error().mappingStatus == mean_field::mapping::MappingStatus::non_positive_determinant);
CHECK_THROWS_AS(
mean_field::operators::kernels::apply_gravity_displacement_force_residual(
f, *f.domainMapperStateless, density, gravityGradient, displacement, residual
),
std::domain_error
);
displacement = 0.0;
density = 1.0e200;
gravityGradient = 1.0e200;
const auto nonFiniteArithmetic = mean_field::operators::kernels::try_apply_gravity_displacement_force_residual(
f, *f.domainMapperStateless, density, gravityGradient, displacement, residual
);
REQUIRE_FALSE(nonFiniteArithmetic.has_value());
CHECK(
nonFiniteArithmetic.error().reason ==
mean_field::operators::kernels::GravityDisplacementForceRejectionReason::non_finite_arithmetic
);
mfem::Vector gravityPotential(f.gravityPotentialFes->GetTrueVSize());
gravityPotential = 0.0;
auto revisions = gravity_displacement_force_test_utils::make_revisions();
mean_field::operators::context::gravity_field::GravityFieldLinearizationContext gravityContext(
f, *f.domainMapperStateless
);
gravity_displacement_force_test_utils::prepare_gravity_context(
gravityContext, density, displacement, gravityGradient, gravityPotential, revisions
);
mean_field::operators::PreparedGravityDisplacementForceOperator preparedOperator(
f, *f.domainMapperStateless, gravityContext
);
const auto preparedRejection = preparedOperator.TryPrepare();
REQUIRE_FALSE(preparedRejection.has_value());
CHECK(
preparedRejection.error().reason ==
mean_field::operators::kernels::GravityDisplacementForceRejectionReason::non_finite_arithmetic
);
CHECK_FALSE(preparedOperator.IsPrepared());
CHECK_THROWS_AS(preparedOperator.Prepare(), std::domain_error);
density = gravity_displacement_force_test_utils::make_density(f, 0.21);
gravityGradient = gravity_displacement_force_test_utils::make_gravity_gradient(f, 0.37);
++revisions.density.value;
++revisions.gravity_gradient.value;
gravity_displacement_force_test_utils::prepare_gravity_context(
gravityContext, density, displacement, gravityGradient, gravityPotential, revisions
);
const auto preparedAccepted = preparedOperator.TryPrepare();
REQUIRE(preparedAccepted.has_value());
CHECK(preparedOperator.IsPrepared());
const auto accepted = mean_field::operators::kernels::try_apply_gravity_displacement_force_residual(
f, *f.domainMapperStateless, density, gravityGradient, displacement, residual
);
CHECK(accepted.has_value());
}
TEST_CASE(
"Prepared Gravity Displacement Force Reuses Shared Gravity Revisions",
tags::gravity_prepared

View File

@@ -3,6 +3,7 @@
#include <cmath>
#include <cstdint>
#include <limits>
#include <stdexcept>
#include <type_traits>
#include <catch2/catch_approx.hpp>
@@ -16,22 +17,22 @@ namespace angular_momentum_test_utils {
[[nodiscard]] mean_field::operators::AngularMomentumDependencies makeDependencies() {
return {
.discretization = {.identity = 15013, .revision = 3},
.density = {.identity = 15017, .revision = 5},
.displacement = {.identity = 15031, .revision = 7},
.rotation = {.identity = 15053, .revision = 11}
.density = {.identity = 15017, .revision = 5},
.displacement = {.identity = 15031, .revision = 7},
.rotation = {.identity = 15053, .revision = 11}
};
}
[[nodiscard]] mean_field::operators::context::gravity_field::GravityFieldRevisions makeGravityRevisions(
const mean_field::operators::AngularMomentumDependencies &dependencies,
const std::uint64_t gravityGradientRevision = 13,
const std::uint64_t gravityGradientRevision = 13,
const std::uint64_t gravityPotentialRevision = 17
) {
return {
.discretization = {.value = dependencies.discretization.revision},
.displacement = {.value = dependencies.displacement.revision},
.density = {.value = dependencies.density.revision},
.gravity_gradient = {.value = gravityGradientRevision},
.discretization = {.value = dependencies.discretization.revision},
.displacement = {.value = dependencies.displacement.revision},
.density = {.value = dependencies.density.revision},
.gravity_gradient = {.value = gravityGradientRevision},
.gravity_potential = {.value = gravityPotentialRevision}
};
}
@@ -42,17 +43,17 @@ namespace angular_momentum_test_utils {
const mfem::Vector &density,
const mfem::Vector &displacement,
const mean_field::operators::AngularMomentumDependencies &dependencies,
const std::uint64_t gravityGradientRevision = 13,
const std::uint64_t gravityGradientRevision = 13,
const std::uint64_t gravityPotentialRevision = 17
) {
mfem::Vector gravityGradient(finiteElements.gravityFluxFes->GetTrueVSize());
mfem::Vector gravityPotential(finiteElements.gravityPotentialFes->GetTrueVSize());
gravityGradient = 0.0;
gravityGradient = 0.0;
gravityPotential = 0.0;
context.Prepare(
{.density = context.GetDensityMap().gather(density),
.displacement = context.GetDisplacementMap().gather(displacement),
.gravity_gradient = context.GetGravityGradientMap().gather(gravityGradient),
{.density = context.GetDensityMap().gather(density),
.displacement = context.GetDisplacementMap().gather(displacement),
.gravity_gradient = context.GetGravityGradientMap().gather(gravityGradient),
.gravity_potential = context.GetGravityPotentialMap().gather(gravityPotential)},
makeGravityRevisions(dependencies, gravityGradientRevision, gravityPotentialRevision)
);
@@ -64,8 +65,8 @@ namespace angular_momentum_test_utils {
) {
mfem::ParGridFunction field(finiteElements.densityFes.get());
mfem::FunctionCoefficient coefficient([phase](const mfem::Vector &position) {
return 0.94 + 0.08 * std::sin(0.71 * position(0) + phase) +
0.05 * std::cos(0.63 * position(1) - phase) + 0.03 * position(2) * position(2);
return 0.94 + 0.08 * std::sin(0.71 * position(0) + phase) + 0.05 * std::cos(0.63 * position(1) - phase) +
0.03 * position(2) * position(2);
});
field.ProjectCoefficient(coefficient);
mfem::Vector result;
@@ -79,8 +80,8 @@ namespace angular_momentum_test_utils {
) {
mfem::ParGridFunction field(finiteElements.densityFes.get());
mfem::FunctionCoefficient coefficient([phase](const mfem::Vector &position) {
return 0.17 * std::sin(0.83 * position(0) + phase) -
0.12 * std::cos(0.79 * position(1) - phase) + 0.06 * position(2);
return 0.17 * std::sin(0.83 * position(0) + phase) - 0.12 * std::cos(0.79 * position(1) - phase) +
0.06 * position(2);
});
field.ProjectCoefficient(coefficient);
mfem::Vector result;
@@ -110,8 +111,7 @@ namespace angular_momentum_test_utils {
) {
mfem::ParGridFunction field(finiteElements.displacementFes.get());
mfem::VectorFunctionCoefficient coefficient(
finiteElements.mesh->Dimension(),
[scale](const mfem::Vector &position, mfem::Vector &value) {
finiteElements.mesh->Dimension(), [scale](const mfem::Vector &position, mfem::Vector &value) {
value.SetSize(position.Size());
for (int component = 0; component < position.Size(); ++component) {
value(component) = scale * position(component);
@@ -130,8 +130,7 @@ namespace angular_momentum_test_utils {
) {
mfem::ParGridFunction field(finiteElements.displacementFes.get());
mfem::VectorFunctionCoefficient coefficient(
finiteElements.mesh->Dimension(),
[scale](const mfem::Vector &position, mfem::Vector &value) {
finiteElements.mesh->Dimension(), [scale](const mfem::Vector &position, mfem::Vector &value) {
value.SetSize(3);
value(0) = scale * (0.07 * position(0) + 0.018 * position(1) * position(2));
value(1) = scale * (-0.05 * position(1) + 0.013 * position(0) * position(2));
@@ -151,7 +150,10 @@ namespace angular_momentum_test_utils {
return value(0);
}
[[nodiscard]] double relativeError(const double actual, const double expected) {
[[nodiscard]] double relativeError(
const double actual,
const double expected
) {
return std::abs(actual - expected) /
std::max({std::abs(actual), std::abs(expected), 100.0 * std::numeric_limits<double>::epsilon()});
}
@@ -168,48 +170,34 @@ TEST_CASE(
STATIC_CHECK_FALSE(std::is_copy_constructible_v<Operator>);
STATIC_CHECK_FALSE(std::is_move_constructible_v<Operator>);
utils::Args arguments = test_utils::setup_args();
utils::Args arguments = test_utils::setup_args();
fem::FEM finiteElements = fem::setup_fem(arguments.mesh_file, arguments, 0);
REQUIRE(finiteElements.okay());
constexpr double densityValue = 1.37;
constexpr double angularVelocity = 0.73;
constexpr double densityValue = 1.37;
constexpr double angularVelocity = 0.73;
constexpr double targetAngularMomentum = 0.41;
mfem::ParGridFunction densityField(finiteElements.densityFes.get());
const mfem::Vector density = angular_momentum_test_utils::projectConstantDensity(
finiteElements,
densityValue,
&densityField
);
const mfem::Vector density =
angular_momentum_test_utils::projectConstantDensity(finiteElements, densityValue, &densityField);
mfem::Vector displacement(finiteElements.displacementFes->GetTrueVSize());
displacement = 0.0;
finiteElements.displacement->SetFromTrueDofs(displacement);
auto dependencies = angular_momentum_test_utils::makeDependencies();
operators::context::gravity_field::GravityFieldLinearizationContext gravityContext(
finiteElements,
*finiteElements.domainMapperStateless
finiteElements, *finiteElements.domainMapperStateless
);
angular_momentum_test_utils::prepareGravityContext(
gravityContext,
finiteElements,
density,
displacement,
dependencies
gravityContext, finiteElements, density, displacement, dependencies
);
const models::CompiledFixedAngularMomentum originConstraint = models::compileConstraint(
integral::FixedAngularMomentum({
.Jtotal = dimensions::AngularMomentumValue{targetAngularMomentum},
.axis = {0.0, 0.0, 4.0}
})
);
Operator origin(
finiteElements,
*finiteElements.domainMapperStateless,
gravityContext,
originConstraint
integral::FixedAngularMomentum(
{.Jtotal = dimensions::AngularMomentumValue{targetAngularMomentum}, .axis = {0.0, 0.0, 4.0}}
)
);
Operator origin(finiteElements, *finiteElements.domainMapperStateless, gravityContext, originConstraint);
const auto initial = origin.Prepare(angularVelocity, dependencies);
CHECK(initial.rebuiltStaticPlan);
CHECK(initial.refreshedGeometry);
@@ -219,10 +207,11 @@ TEST_CASE(
const double independentMoment = analysis::get_moment_of_inertia(finiteElements, densityField);
CHECK(angular_momentum_test_utils::relativeError(origin.GetMomentOfInertia(), independentMoment) < 2.0e-13);
CHECK(origin.GetCurrentAngularMomentum() ==
Approx(angularVelocity * origin.GetMomentOfInertia()).epsilon(2.0e-15));
CHECK(angular_momentum_test_utils::residual(origin) ==
Approx(angularVelocity * origin.GetMomentOfInertia() - targetAngularMomentum).epsilon(2.0e-15));
CHECK(origin.GetCurrentAngularMomentum() == Approx(angularVelocity * origin.GetMomentOfInertia()).epsilon(2.0e-15));
CHECK(
angular_momentum_test_utils::residual(origin) ==
Approx(angularVelocity * origin.GetMomentOfInertia() - targetAngularMomentum).epsilon(2.0e-15)
);
const auto report = origin.GetConstraintReport();
CHECK(report.targetAngularMomentum == targetAngularMomentum);
@@ -239,11 +228,7 @@ TEST_CASE(
angular_momentum_test_utils::projectAffineDisplacement(finiteElements, affineScale);
++dependencies.displacement.revision;
angular_momentum_test_utils::prepareGravityContext(
gravityContext,
finiteElements,
density,
affineDisplacement,
dependencies
gravityContext, finiteElements, density, affineDisplacement, dependencies
);
const auto affine = origin.Prepare(angularVelocity, dependencies);
CHECK(affine.refreshedGeometry);
@@ -257,38 +242,30 @@ TEST_CASE(
displacement = 0.0;
++dependencies.displacement.revision;
angular_momentum_test_utils::prepareGravityContext(
gravityContext,
finiteElements,
density,
displacement,
dependencies
gravityContext, finiteElements, density, displacement, dependencies
);
origin.Prepare(angularVelocity, dependencies);
constexpr std::array<double, 3> shiftedCenter{0.27, -0.19, 0.31};
Operator shifted(
finiteElements,
*finiteElements.domainMapperStateless,
gravityContext,
models::compileConstraint(integral::FixedAngularMomentum({
.Jtotal = dimensions::AngularMomentumValue{targetAngularMomentum},
.axis = {0.0, 0.0, 1.0},
.center = shiftedCenter
}))
finiteElements, *finiteElements.domainMapperStateless, gravityContext,
models::compileConstraint(
integral::FixedAngularMomentum(
{.Jtotal = dimensions::AngularMomentumValue{targetAngularMomentum},
.axis = {0.0, 0.0, 1.0},
.center = shiftedCenter}
)
)
);
shifted.Prepare(angularVelocity, dependencies);
const double mass = analysis::domain_integrate_grid_function(
finiteElements,
densityField,
utils::DOMAINS::STELLAR,
mapping::COORDINATE_SPACE::PHYSICAL
finiteElements, densityField, utils::DOMAINS::STELLAR, mapping::COORDINATE_SPACE::PHYSICAL
);
const mfem::Vector centerOfMass = analysis::get_com(finiteElements, densityField);
const double expectedShiftedMoment = origin.GetMomentOfInertia() +
mass * (shiftedCenter[0] * shiftedCenter[0] +
shiftedCenter[1] * shiftedCenter[1]) -
2.0 * mass * (shiftedCenter[0] * centerOfMass(0) +
shiftedCenter[1] * centerOfMass(1));
const double expectedShiftedMoment =
origin.GetMomentOfInertia() +
mass * (shiftedCenter[0] * shiftedCenter[0] + shiftedCenter[1] * shiftedCenter[1]) -
2.0 * mass * (shiftedCenter[0] * centerOfMass(0) + shiftedCenter[1] * centerOfMass(1));
CHECK(angular_momentum_test_utils::relativeError(shifted.GetMomentOfInertia(), expectedShiftedMoment) < 3.0e-13);
}
@@ -298,45 +275,33 @@ TEST_CASE(
) {
using namespace mean_field;
utils::Args arguments = test_utils::setup_args();
utils::Args arguments = test_utils::setup_args();
fem::FEM finiteElements = fem::setup_fem(arguments.mesh_file, arguments, 0);
REQUIRE(finiteElements.okay());
const mfem::Vector density = angular_momentum_test_utils::projectDensity(finiteElements, 0.31);
const mfem::Vector densityDirection =
angular_momentum_test_utils::projectDensityDirection(finiteElements, 0.67);
const mfem::Vector displacement =
angular_momentum_test_utils::projectDisplacementDirection(finiteElements, 0.43);
const mfem::Vector density = angular_momentum_test_utils::projectDensity(finiteElements, 0.31);
const mfem::Vector densityDirection = angular_momentum_test_utils::projectDensityDirection(finiteElements, 0.67);
const mfem::Vector displacement = angular_momentum_test_utils::projectDisplacementDirection(finiteElements, 0.43);
const mfem::Vector displacementDirection =
angular_momentum_test_utils::projectDisplacementDirection(finiteElements, -0.79);
constexpr double angularVelocity = 0.63;
constexpr double angularVelocity = 0.63;
constexpr double angularVelocityDirection = -0.37;
auto dependencies = angular_momentum_test_utils::makeDependencies();
auto dependencies = angular_momentum_test_utils::makeDependencies();
operators::context::gravity_field::GravityFieldLinearizationContext gravityContext(
finiteElements,
*finiteElements.domainMapperStateless
finiteElements, *finiteElements.domainMapperStateless
);
angular_momentum_test_utils::prepareGravityContext(
gravityContext,
finiteElements,
density,
displacement,
dependencies
gravityContext, finiteElements, density, displacement, dependencies
);
operators::PreparedAngularMomentumOperator operation(
finiteElements,
*finiteElements.domainMapperStateless,
gravityContext,
models::compileConstraint(
integral::FixedAngularMomentum({.Jtotal = dimensions::AngularMomentumValue{0.81}})
)
finiteElements, *finiteElements.domainMapperStateless, gravityContext,
models::compileConstraint(integral::FixedAngularMomentum({.Jtotal = dimensions::AngularMomentumValue{0.81}}))
);
operation.Prepare(angularVelocity, dependencies);
const mfem::Vector reducedDensityDirection = gravityContext.GetDensityMap().gather(densityDirection);
const mfem::Vector reducedDisplacementDirection =
gravityContext.GetDisplacementMap().gather(displacementDirection);
const mfem::Vector reducedDensityDirection = gravityContext.GetDensityMap().gather(densityDirection);
const mfem::Vector reducedDisplacementDirection = gravityContext.GetDisplacementMap().gather(displacementDirection);
mfem::Vector densityAction;
mfem::Vector geometryAction;
mfem::Vector angularVelocityAction;
@@ -345,17 +310,17 @@ TEST_CASE(
operation.ApplyDisplacementJacobianAction(reducedDisplacementDirection, geometryAction);
operation.ApplyAngularVelocityJacobianAction(angularVelocityDirection, angularVelocityAction);
operation.ApplyCompleteJacobianAction(
reducedDensityDirection,
reducedDisplacementDirection,
angularVelocityDirection,
completeAction
reducedDensityDirection, reducedDisplacementDirection, angularVelocityDirection, completeAction
);
CHECK(
angular_momentum_test_utils::relativeError(
completeAction(0), densityAction(0) + geometryAction(0) + angularVelocityAction(0)
) < 3.0e-15
);
CHECK(
angularVelocityAction(0) ==
Catch::Approx(operation.GetMomentOfInertia() * angularVelocityDirection).epsilon(2.0e-15)
);
CHECK(angular_momentum_test_utils::relativeError(
completeAction(0),
densityAction(0) + geometryAction(0) + angularVelocityAction(0)
) < 3.0e-15);
CHECK(angularVelocityAction(0) ==
Catch::Approx(operation.GetMomentOfInertia() * angularVelocityDirection).epsilon(2.0e-15));
constexpr double angularStep = 1.0e-6;
++dependencies.rotation.revision;
@@ -363,7 +328,7 @@ TEST_CASE(
const double angularPlus = angular_momentum_test_utils::residual(operation);
++dependencies.rotation.revision;
operation.Prepare(angularVelocity - angularStep * angularVelocityDirection, dependencies);
const double angularMinus = angular_momentum_test_utils::residual(operation);
const double angularMinus = angular_momentum_test_utils::residual(operation);
const double angularDifference = (angularPlus - angularMinus) / (2.0 * angularStep);
CHECK(angular_momentum_test_utils::relativeError(angularVelocityAction(0), angularDifference) < 2.0e-10);
@@ -372,11 +337,7 @@ TEST_CASE(
densityPlus.Add(densityStep, densityDirection);
++dependencies.density.revision;
angular_momentum_test_utils::prepareGravityContext(
gravityContext,
finiteElements,
densityPlus,
displacement,
dependencies
gravityContext, finiteElements, densityPlus, displacement, dependencies
);
operation.Prepare(angularVelocity, dependencies);
const double densityPlusResidual = angular_momentum_test_utils::residual(operation);
@@ -384,15 +345,11 @@ TEST_CASE(
densityMinus.Add(-densityStep, densityDirection);
++dependencies.density.revision;
angular_momentum_test_utils::prepareGravityContext(
gravityContext,
finiteElements,
densityMinus,
displacement,
dependencies
gravityContext, finiteElements, densityMinus, displacement, dependencies
);
operation.Prepare(angularVelocity, dependencies);
const double densityMinusResidual = angular_momentum_test_utils::residual(operation);
const double densityDifference = (densityPlusResidual - densityMinusResidual) / (2.0 * densityStep);
const double densityDifference = (densityPlusResidual - densityMinusResidual) / (2.0 * densityStep);
CHECK(angular_momentum_test_utils::relativeError(densityAction(0), densityDifference) < 4.0e-8);
constexpr double geometryStep = 1.0e-6;
@@ -401,11 +358,7 @@ TEST_CASE(
++dependencies.density.revision;
++dependencies.displacement.revision;
angular_momentum_test_utils::prepareGravityContext(
gravityContext,
finiteElements,
density,
displacementPlus,
dependencies
gravityContext, finiteElements, density, displacementPlus, dependencies
);
operation.Prepare(angularVelocity, dependencies);
const double geometryPlusResidual = angular_momentum_test_utils::residual(operation);
@@ -413,19 +366,19 @@ TEST_CASE(
displacementMinus.Add(-geometryStep, displacementDirection);
++dependencies.displacement.revision;
angular_momentum_test_utils::prepareGravityContext(
gravityContext,
finiteElements,
density,
displacementMinus,
dependencies
gravityContext, finiteElements, density, displacementMinus, dependencies
);
operation.Prepare(angularVelocity, dependencies);
const double geometryMinusResidual = angular_momentum_test_utils::residual(operation);
const double geometryDifference = (geometryPlusResidual - geometryMinusResidual) / (2.0 * geometryStep);
INFO("Density angular-momentum derivative error = " <<
angular_momentum_test_utils::relativeError(densityAction(0), densityDifference));
INFO("Geometry angular-momentum derivative error = " <<
angular_momentum_test_utils::relativeError(geometryAction(0), geometryDifference));
const double geometryDifference = (geometryPlusResidual - geometryMinusResidual) / (2.0 * geometryStep);
INFO(
"Density angular-momentum derivative error = "
<< angular_momentum_test_utils::relativeError(densityAction(0), densityDifference)
);
INFO(
"Geometry angular-momentum derivative error = "
<< angular_momentum_test_utils::relativeError(geometryAction(0), geometryDifference)
);
CHECK(angular_momentum_test_utils::relativeError(geometryAction(0), geometryDifference) < 4.0e-7);
}
@@ -435,53 +388,35 @@ TEST_CASE(
) {
using namespace mean_field;
utils::Args arguments = test_utils::setup_args();
utils::Args arguments = test_utils::setup_args();
fem::FEM finiteElements = fem::setup_fem(arguments.mesh_file, arguments, 0);
REQUIRE(finiteElements.okay());
mfem::Vector density = angular_momentum_test_utils::projectDensity(finiteElements, 0.29);
mfem::Vector displacement =
angular_momentum_test_utils::projectDisplacementDirection(finiteElements, 0.41);
auto dependencies = angular_momentum_test_utils::makeDependencies();
mfem::Vector density = angular_momentum_test_utils::projectDensity(finiteElements, 0.29);
mfem::Vector displacement = angular_momentum_test_utils::projectDisplacementDirection(finiteElements, 0.41);
auto dependencies = angular_momentum_test_utils::makeDependencies();
std::uint64_t gravityPotentialRevision = 17;
operators::context::gravity_field::GravityFieldLinearizationContext gravityContext(
finiteElements,
*finiteElements.domainMapperStateless
finiteElements, *finiteElements.domainMapperStateless
);
angular_momentum_test_utils::prepareGravityContext(
gravityContext,
finiteElements,
density,
displacement,
dependencies,
13,
gravityPotentialRevision
gravityContext, finiteElements, density, displacement, dependencies, 13, gravityPotentialRevision
);
operators::PreparedAngularMomentumOperator operation(
finiteElements,
*finiteElements.domainMapperStateless,
gravityContext,
models::compileConstraint(
integral::FixedAngularMomentum({.Jtotal = dimensions::AngularMomentumValue{0.71}})
)
finiteElements, *finiteElements.domainMapperStateless, gravityContext,
models::compileConstraint(integral::FixedAngularMomentum({.Jtotal = dimensions::AngularMomentumValue{0.71}}))
);
operation.Prepare(0.52, dependencies);
const auto preparationCount = operation.GetPreparationCount();
const double moment = operation.GetMomentOfInertia();
const double moment = operation.GetMomentOfInertia();
const auto repeated = operation.Prepare(0.52, dependencies);
const auto repeated = operation.Prepare(0.52, dependencies);
CHECK_FALSE(repeated.DidAnyWork());
CHECK(operation.GetPreparationCount() == preparationCount);
++gravityPotentialRevision;
angular_momentum_test_utils::prepareGravityContext(
gravityContext,
finiteElements,
density,
displacement,
dependencies,
13,
gravityPotentialRevision
gravityContext, finiteElements, density, displacement, dependencies, 13, gravityPotentialRevision
);
const auto unrelatedPotential = operation.Prepare(0.52, dependencies);
CHECK_FALSE(unrelatedPotential.DidAnyWork());
@@ -494,19 +429,15 @@ TEST_CASE(
CHECK_FALSE(rotationOnly.refreshedDensity);
CHECK_FALSE(rotationOnly.refreshedGeometry);
CHECK(operation.GetMomentOfInertia() == moment);
CHECK(angular_momentum_test_utils::residual(operation) - residualBeforeRotation ==
Catch::Approx((0.81 - 0.52) * moment).epsilon(3.0e-15));
CHECK(
angular_momentum_test_utils::residual(operation) - residualBeforeRotation ==
Catch::Approx((0.81 - 0.52) * moment).epsilon(3.0e-15)
);
density = angular_momentum_test_utils::projectDensity(finiteElements, 0.83);
++dependencies.density.revision;
angular_momentum_test_utils::prepareGravityContext(
gravityContext,
finiteElements,
density,
displacement,
dependencies,
13,
gravityPotentialRevision
gravityContext, finiteElements, density, displacement, dependencies, 13, gravityPotentialRevision
);
const auto densityOnly = operation.Prepare(0.81, dependencies);
CHECK(densityOnly.refreshedDensity);
@@ -516,16 +447,84 @@ TEST_CASE(
displacement = angular_momentum_test_utils::projectDisplacementDirection(finiteElements, 0.87);
++dependencies.displacement.revision;
angular_momentum_test_utils::prepareGravityContext(
gravityContext,
finiteElements,
density,
displacement,
dependencies,
13,
gravityPotentialRevision
gravityContext, finiteElements, density, displacement, dependencies, 13, gravityPotentialRevision
);
const auto geometryOnly = operation.Prepare(0.81, dependencies);
CHECK(geometryOnly.refreshedGeometry);
CHECK_FALSE(geometryOnly.refreshedDensity);
CHECK_FALSE(geometryOnly.updatedAngularVelocity);
}
TEST_CASE(
"Prepared Angular Momentum Returns Explicit Candidate Rejections And Recovers",
"[fixed-angular-momentum][prepared][trial-outcome]"
) {
using namespace mean_field;
STATIC_CHECK(std::is_trivially_copyable_v<operators::AngularMomentumPreparationRejection>);
utils::Args arguments = test_utils::setup_args();
fem::FEM finiteElements = fem::setup_fem(arguments.mesh_file, arguments, 0);
REQUIRE(finiteElements.okay());
constexpr double angularVelocity = 0.52;
const mfem::Vector positiveDensity = angular_momentum_test_utils::projectConstantDensity(finiteElements, 0.91);
const mfem::Vector negativeDensity = angular_momentum_test_utils::projectConstantDensity(finiteElements, -0.91);
mfem::Vector displacement(finiteElements.displacementFes->GetTrueVSize());
displacement = 0.0;
auto dependencies = angular_momentum_test_utils::makeDependencies();
operators::context::gravity_field::GravityFieldLinearizationContext gravityContext(
finiteElements, *finiteElements.domainMapperStateless
);
angular_momentum_test_utils::prepareGravityContext(
gravityContext, finiteElements, positiveDensity, displacement, dependencies
);
operators::PreparedAngularMomentumOperator operation(
finiteElements, *finiteElements.domainMapperStateless, gravityContext,
models::compileConstraint(integral::FixedAngularMomentum({.Jtotal = dimensions::AngularMomentumValue{0.71}}))
);
const auto initial = operation.TryPrepare(angularVelocity, dependencies);
REQUIRE(initial.has_value());
const std::uint64_t initialPreparationCount = operation.GetPreparationCount();
const std::uint64_t successfulPreparations = initialPreparationCount;
++dependencies.density.revision;
angular_momentum_test_utils::prepareGravityContext(
gravityContext, finiteElements, negativeDensity, displacement, dependencies
);
const auto negativeMoment = operation.TryPrepare(angularVelocity, dependencies);
REQUIRE_FALSE(negativeMoment.has_value());
CHECK(
negativeMoment.error().reason ==
operators::AngularMomentumPreparationRejectionReason::negative_moment_of_inertia
);
CHECK(negativeMoment.error().momentOfInertia < 0.0);
CHECK(operation.GetPreparationCount() == successfulPreparations);
CHECK_FALSE(operation.IsPrepared());
REQUIRE_THROWS_AS(operation.Prepare(angularVelocity, dependencies), std::domain_error);
++dependencies.density.revision;
angular_momentum_test_utils::prepareGravityContext(
gravityContext, finiteElements, positiveDensity, displacement, dependencies
);
const auto recovered = operation.TryPrepare(angularVelocity, dependencies);
REQUIRE(recovered.has_value());
CHECK(operation.IsPrepared());
++dependencies.rotation.revision;
const auto nonFiniteAngularVelocity = operation.TryPrepare(std::numeric_limits<double>::quiet_NaN(), dependencies);
REQUIRE_FALSE(nonFiniteAngularVelocity.has_value());
CHECK(
nonFiniteAngularVelocity.error().reason ==
operators::AngularMomentumPreparationRejectionReason::non_finite_angular_velocity
);
CHECK_FALSE(operation.IsPrepared());
REQUIRE_THROWS_AS(operation.Prepare(std::numeric_limits<double>::quiet_NaN(), dependencies), std::domain_error);
++dependencies.rotation.revision;
REQUIRE(operation.TryPrepare(angularVelocity, dependencies).has_value());
CHECK(operation.IsPrepared());
}

View File

@@ -6,6 +6,7 @@
#include <cstdint>
#include <limits>
#include <mfem.hpp>
#include <stdexcept>
#include <type_traits>
import mean_field;
@@ -101,6 +102,22 @@ namespace prepared_barotropic_closure_test_utils {
return project_scalar(finiteElementSpace, coefficient);
}
[[nodiscard]] mfem::Vector make_folding_displacement(const mean_field::fem::FEM &f) {
mfem::ParGridFunction fieldValue(f.displacementFes.get());
mfem::VectorFunctionCoefficient coefficient(
f.mesh->Dimension(), [](const mfem::Vector &position, mfem::Vector &value) {
value.SetSize(position.Size());
value = 0.0;
value(0) = -2.0 * position(0);
}
);
fieldValue.ProjectCoefficient(coefficient);
mfem::Vector result;
fieldValue.GetTrueDofs(result);
return result;
}
[[nodiscard]] mfem::Vector reduce(
const field::FieldDofMap &map,
const mfem::Vector &full
@@ -263,6 +280,7 @@ namespace prepared_barotropic_closure_test_utils {
STATIC_REQUIRE_FALSE(std::is_copy_assignable_v<Operator>);
STATIC_REQUIRE_FALSE(std::is_move_constructible_v<Operator>);
STATIC_REQUIRE_FALSE(std::is_move_assignable_v<Operator>);
STATIC_REQUIRE(std::is_trivially_copyable_v<mean_field::operators::BarotropicClosurePreparationRejection>);
auto args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
@@ -303,6 +321,115 @@ namespace prepared_barotropic_closure_test_utils {
CHECK(globalEnthalpyReduced < globalEnthalpyFull);
}
TEST_CASE(
"Prepared Barotropic Closure Reports Expected EOS Rejections Without Unwinding",
tags::barotrope &tags::closure &tags::prepared &tags::unit
) {
auto args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
REQUIRE(f.okay());
const Maps maps(f);
const mean_field::eos::Polytrope equationOfState(3.0, 1.5);
mean_field::operators::PreparedBarotropicClosureOperator preparedOperator(
f, *f.domainMapperStateless, equationOfState
);
mfem::Vector density(maps.density.reduced_size());
mfem::Vector enthalpy(maps.enthalpy.reduced_size());
mfem::Vector displacement(maps.displacement.reduced_size());
density = 0.0;
enthalpy = -1.0;
displacement = 0.0;
auto dependencies = make_dependencies();
const auto outsideDomain =
preparedOperator.TryPrepare(make_state_view(density, enthalpy, displacement), dependencies);
REQUIRE_FALSE(outsideDomain.has_value());
CHECK(
outsideDomain.error().reason ==
mean_field::operators::BarotropicClosurePreparationRejectionReason::equation_of_state
);
CHECK(outsideDomain.error().equationOfStateError == mean_field::eos::EvaluationErrorCode::outside_domain);
CHECK_FALSE(preparedOperator.IsPrepared());
try {
(void)preparedOperator.Prepare(make_state_view(density, enthalpy, displacement), dependencies);
FAIL("The compatibility Prepare overload accepted an out-of-domain EOS input.");
} catch (const mean_field::eos::EvaluationError &error) {
CHECK(error.code() == mean_field::eos::EvaluationErrorCode::outside_domain);
}
// Keep the interpolated input finite while forcing the n = 3
// polytropic density evaluation to overflow.
enthalpy = 1.0e150;
++dependencies.enthalpy.revision;
const auto rejected =
preparedOperator.TryPrepare(make_state_view(density, enthalpy, displacement), dependencies);
REQUIRE_FALSE(rejected.has_value());
CHECK(
rejected.error().reason ==
mean_field::operators::BarotropicClosurePreparationRejectionReason::equation_of_state
);
CHECK(rejected.error().equationOfStateError == mean_field::eos::EvaluationErrorCode::nonfinite_result);
CHECK_FALSE(preparedOperator.IsPrepared());
try {
(void)preparedOperator.Prepare(make_state_view(density, enthalpy, displacement), dependencies);
FAIL("The compatibility Prepare overload accepted a non-finite EOS result.");
} catch (const mean_field::eos::EvaluationError &error) {
CHECK(error.code() == mean_field::eos::EvaluationErrorCode::nonfinite_result);
}
enthalpy = 1.0;
++dependencies.enthalpy.revision;
const auto accepted =
preparedOperator.TryPrepare(make_state_view(density, enthalpy, displacement), dependencies);
REQUIRE(accepted.has_value());
CHECK(preparedOperator.IsPrepared());
}
TEST_CASE(
"Prepared Barotropic Closure Reports Invalid Candidate Geometry Without Unwinding",
tags::barotrope &tags::closure &tags::prepared &tags::geometry &tags::unit
) {
auto args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
REQUIRE(f.okay());
const Maps maps(f);
const mean_field::eos::Polytrope equationOfState(3.0, 1.5);
mean_field::operators::PreparedBarotropicClosureOperator preparedOperator(
f, *f.domainMapperStateless, equationOfState
);
mfem::Vector density(maps.density.reduced_size());
mfem::Vector enthalpy(maps.enthalpy.reduced_size());
density = 1.0;
enthalpy = 1.0;
mfem::Vector displacement = reduce(maps.displacement, make_folding_displacement(f));
auto dependencies = make_dependencies();
const auto rejected =
preparedOperator.TryPrepare(make_state_view(density, enthalpy, displacement), dependencies);
REQUIRE_FALSE(rejected.has_value());
CHECK(
rejected.error().reason ==
mean_field::operators::BarotropicClosurePreparationRejectionReason::mapping_failure
);
CHECK(rejected.error().mappingStatus == mean_field::mapping::MappingStatus::non_positive_determinant);
CHECK_FALSE(preparedOperator.IsPrepared());
CHECK_THROWS_AS(
preparedOperator.Prepare(make_state_view(density, enthalpy, displacement), dependencies), std::domain_error
);
displacement = 0.0;
++dependencies.displacement.revision;
const auto accepted =
preparedOperator.TryPrepare(make_state_view(density, enthalpy, displacement), dependencies);
REQUIRE(accepted.has_value());
CHECK(preparedOperator.IsPrepared());
}
TEST_CASE(
"Prepared Barotropic Closure Matches Full Stateless Kernels Through FieldDof Restriction",
tags::barotrope &tags::closure &tags::hydro &tags::prepared &tags::field &tags::integration

View File

@@ -2,7 +2,10 @@
#include <cmath>
#include <concepts>
#include <cstdint>
#include <limits>
#include <stdexcept>
#include <type_traits>
#include <utility>
#include <catch2/catch_test_macros.hpp>
#include <mfem.hpp>
@@ -49,24 +52,30 @@ TEST_CASE(
) {
using namespace mean_field;
utils::Args args = test_utils::setup_args();
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
REQUIRE(f.okay());
utils::Args args = test_utils::setup_args();
fem::FEM physicalFiniteElements = fem::setup_fem(args.mesh_file, args, 0);
REQUIRE(physicalFiniteElements.okay());
fem::FEM borderedFiniteElements = fem::setup_fem(args.mesh_file, args, 0);
REQUIRE(borderedFiniteElements.okay());
models::StellarModel stellarModel{
models::structure::PolytropicStructure{eos::Polytrope{3.0, 0.25}, 1.0},
surface::ConstantPressureSurface{dimensions::PressureValue{0.0}}
};
operators::PreparedStellarEquilibriumOperator physicalOperator(f, *f.domainMapperStateless, stellarModel);
operators::PreparedStellarEquilibriumOperator physicalOperator(
physicalFiniteElements, *physicalFiniteElements.domainMapperStateless, stellarModel
);
auto equilibriumProblem = equilibrium::discretize(
model::StellarModel(
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{1.0}}),
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{1.0}}),
surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}), eos::Polytrope({.n = 3.0, .K = 0.25})
),
equilibrium::StellarDiscretization{f, *f.domainMapperStateless}
std::move(borderedFiniteElements)
);
auto &borderedOperator = equilibriumProblem.GetPreparedOperator();
auto &borderedOperator = equilibriumProblem.GetPreparedOperator();
const MPI_Comm communicator = equilibriumProblem.GetCommunicator();
STATIC_CHECK(
std::same_as<
@@ -87,8 +96,7 @@ TEST_CASE(
CHECK(borderedOperator.GetRootManifest().constraints().size() == 3);
CHECK(borderedOperator.GetRootManifest().specificationDescriptors().size() == 4);
const auto &centralDescriptor =
borderedOperator.GetRootManifest().specification<constraint::FixedCentralDensity>();
const auto &centralDescriptor = borderedOperator.GetRootManifest().specification<constraint::FixedCentralDensity>();
CHECK(centralDescriptor.stableId == "FixedCentralDensity");
CHECK(centralDescriptor.role == models::SpecificationRole::phase_condition);
CHECK(centralDescriptor.columnPolicy == operators::RootColumnPolicy::solver_border);
@@ -99,10 +107,9 @@ TEST_CASE(
CHECK(centralDescriptor.residualUnits == "specific_enthalpy");
mfem::Vector physicalState(physicalOperator.Width());
physicalState = 0.0;
const auto physicalStateView =
physicalOperator.GetRootManifest().stateView(physicalState);
physicalStateView.block(utils::blocks::density_field.mass_term) = 1.0;
physicalState = 0.0;
const auto physicalStateView = physicalOperator.GetRootManifest().stateView(physicalState);
physicalStateView.block(utils::blocks::density_field.mass_term) = 1.0;
physicalStateView.block(utils::blocks::enthalpy_field.specific_term) = 1.0;
mfem::Vector borderedState(borderedOperator.Width());
@@ -152,7 +159,7 @@ TEST_CASE(
localCenterDirection += enthalpyDirection(centerDof);
}
double globalCenterDirection = 0.0;
MPI_Allreduce(&localCenterDirection, &globalCenterDirection, 1, MPI_DOUBLE, MPI_SUM, f.mesh->GetComm());
MPI_Allreduce(&localCenterDirection, &globalCenterDirection, 1, MPI_DOUBLE, MPI_SUM, communicator);
CHECK(borderedAction(borderedAction.Size() - 1) == globalCenterDirection);
const auto repeatedReport = borderedOperator.Prepare(borderedState, dependencies, rotation);
@@ -181,6 +188,60 @@ TEST_CASE(
localBorderEntry += enthalpyAction(centerDof);
}
double globalBorderEntry = 0.0;
MPI_Allreduce(&localBorderEntry, &globalBorderEntry, 1, MPI_DOUBLE, MPI_SUM, f.mesh->GetComm());
MPI_Allreduce(&localBorderEntry, &globalBorderEntry, 1, MPI_DOUBLE, MPI_SUM, communicator);
CHECK(globalBorderEntry == -0.625);
}
TEST_CASE(
"Central Density Variadic Preparation Rejects A Non-Finite Phase Coordinate Without Unwinding",
tags::central_density_phase_integration
) {
using namespace mean_field;
utils::Args args = test_utils::setup_args();
fem::FEM finiteElements = fem::setup_fem(args.mesh_file, args, 0);
REQUIRE(finiteElements.okay());
auto equilibriumProblem = equilibrium::discretize(
model::StellarModel(
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{1.0}}),
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{1.0}}),
surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}), eos::Polytrope({.n = 3.0, .K = 0.25})
),
std::move(finiteElements)
);
auto &preparedOperator = equilibriumProblem.GetPreparedOperator();
mfem::Vector state(preparedOperator.Width());
state = 0.0;
const auto stateView = preparedOperator.GetRootManifest().stateView(state);
stateView.block(utils::blocks::density_field.mass_term) = 1.0;
stateView.block(utils::blocks::enthalpy_field.specific_term) = 1.0;
const operators::StellarEquilibriumDependencies dependencies = make_dependencies();
const physics::RigidRotation rotation = make_zero_rotation();
REQUIRE(equilibriumProblem.TryPrepare(state, dependencies, rotation).has_value());
int rank = 0;
REQUIRE(MPI_Comm_rank(equilibriumProblem.GetCommunicator(), &rank) == MPI_SUCCESS);
auto phaseCoordinate = preparedOperator.GetRootManifest().stateView(state).block(
utils::blocks::fixed_central_density_phase.central_value_term
);
REQUIRE(phaseCoordinate.Size() == 1);
if (rank == 0) {
phaseCoordinate(0) = std::numeric_limits<double>::quiet_NaN();
phaseCoordinate.SyncAliasMemory(state);
}
const auto rejected = equilibriumProblem.TryPrepare(state, dependencies, rotation);
REQUIRE_FALSE(rejected.has_value());
CHECK(rejected.error().reason == operators::StellarEquilibriumPreparationRejectionReason::non_finite_physics);
CHECK(rejected.error().stage == operators::StellarEquilibriumPreparationStage::model_specification);
CHECK_FALSE(equilibriumProblem.IsPrepared());
CHECK_THROWS_AS(equilibriumProblem.Prepare(state, dependencies, rotation), std::domain_error);
phaseCoordinate(0) = 0.0;
phaseCoordinate.SyncAliasMemory(state);
REQUIRE(equilibriumProblem.TryPrepare(state, dependencies, rotation).has_value());
CHECK(equilibriumProblem.IsPrepared());
}

View File

@@ -415,6 +415,57 @@ TEST_CASE(
CHECK(preparedOperator.IsPrepared());
}
TEST_CASE(
"Prepared Displacement Residual Preserves Pressure Rejection Details",
tags::barotrope_prepared
) {
mean_field::utils::Args args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
REQUIRE(f.okay());
const mfem::Vector density = prepared_displacement_residual_test_utils::make_density(f, 0.31);
const mfem::Vector displacement = gravity_prepared_test_utils::make_displacement(f, 0.67);
const mfem::Vector gravityGradient = prepared_displacement_residual_test_utils::make_gravity_gradient(f, 0.47);
mfem::Vector gravityPotential(f.gravityPotentialFes->GetTrueVSize());
gravityPotential = 0.0;
auto dependencies = prepared_displacement_residual_test_utils::make_dependencies();
mean_field::operators::context::gravity_field::GravityFieldLinearizationContext gravityContext(
f, *f.domainMapperStateless
);
prepared_displacement_residual_test_utils::prepare_gravity_context(
gravityContext, density, displacement, gravityGradient, gravityPotential, dependencies, 19
);
const mean_field::eos::Polytrope equationOfState(3.0, 0.25);
const mean_field::physics::RigidRotation rotation = prepared_displacement_residual_test_utils::make_rotation(0.83);
mean_field::operators::PreparedDisplacementResidualOperator preparedOperator(
f, *f.domainMapperStateless, equationOfState, gravityContext
);
mfem::Vector enthalpy(prepared_displacement_residual_test_utils::make_enthalpy_map(f).reduced_size());
enthalpy = std::numeric_limits<double>::max();
const auto rejected = preparedOperator.TryPrepare({.enthalpy = enthalpy}, dependencies, rotation);
REQUIRE_FALSE(rejected.has_value());
CHECK(rejected.error().source == mean_field::operators::DisplacementResidualPreparationRejectionSource::pressure);
CHECK(
rejected.error().reason ==
mean_field::operators::DisplacementResidualPreparationRejectionReason::equation_of_state
);
CHECK(rejected.error().equationOfStateCode == mean_field::eos::EvaluationErrorCode::nonfinite_result);
CHECK_FALSE(preparedOperator.IsPrepared());
CHECK_THROWS_AS(
preparedOperator.Prepare({.enthalpy = enthalpy}, dependencies, rotation), mean_field::eos::EvaluationError
);
enthalpy = 1.0;
++dependencies.enthalpy.revision;
const auto accepted = preparedOperator.TryPrepare({.enthalpy = enthalpy}, dependencies, rotation);
REQUIRE(accepted.has_value());
CHECK(preparedOperator.IsPrepared());
}
TEST_CASE(
"Prepared Displacement Residual Selectively Orchestrates Its Children",
tags::barotrope_context_integration

View File

@@ -1,6 +1,7 @@
#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_floating_point.hpp>
#include <mfem.hpp>
#include <stdexcept>
import mean_field;
import test_helpers;
@@ -9,6 +10,56 @@ using namespace mean_field;
using Catch::Matchers::WithinAbs;
namespace prepared_test = gravity_prepared_test_utils;
namespace {
[[nodiscard]] mfem::Vector make_folding_displacement(const mean_field::fem::FEM &f) {
mfem::ParGridFunction field(f.displacementFes.get());
mfem::VectorFunctionCoefficient coefficient(
f.mesh->Dimension(), [](const mfem::Vector &position, mfem::Vector &value) {
value.SetSize(position.Size());
for (int dimension = 0; dimension < position.Size(); ++dimension) {
value(dimension) = -2.0 * position(dimension);
}
}
);
field.ProjectCoefficient(coefficient);
mfem::Vector displacementTrue;
field.GetTrueDofs(displacementTrue);
return displacementTrue;
}
} // namespace
TEST_CASE(
"Prepared Mapped Gravity Source Reports Invalid Candidate Geometry Without Unwinding",
tags::gravity_prepared_unit &tags::geometry
) {
auto args = test_utils::setup_args();
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
REQUIRE(f.okay());
operators::PreparedMappedGravitySourceOperator preparedOperator(f, *f.domainMapperStateless);
mfem::Vector displacement = preparedOperator.GetDisplacementMap().gather(make_folding_displacement(f));
const auto rejected = preparedOperator.TryPrepare(displacement);
REQUIRE_FALSE(rejected.has_value());
CHECK(rejected.error().reason == operators::GravitySourcePreparationRejectionReason::invalid_mapping);
CHECK(rejected.error().mappingStatus == mapping::MappingStatus::non_positive_determinant);
CHECK_FALSE(preparedOperator.IsPrepared());
CHECK_FALSE(preparedOperator.HasVariationData());
CHECK(preparedOperator.GetPreparationCount() == 0);
CHECK_THROWS_AS(preparedOperator.Prepare(displacement), std::domain_error);
CHECK_FALSE(preparedOperator.IsPrepared());
CHECK(preparedOperator.GetPreparationCount() == 0);
displacement = 0.0;
const auto recovered = preparedOperator.TryPrepare(displacement);
REQUIRE(recovered.has_value());
CHECK(preparedOperator.IsPrepared());
CHECK(preparedOperator.HasVariationData());
CHECK(preparedOperator.GetPreparationCount() == 1);
}
TEST_CASE(
"Prepared Mapped Gravity Source Matches Stateless Kernel",
tags::gravity_prepared

View File

@@ -2,6 +2,7 @@
#include <catch2/matchers/catch_matchers_floating_point.hpp>
#include <cmath>
#include <mfem.hpp>
#include <stdexcept>
import mean_field;
import test_helpers;
@@ -10,6 +11,56 @@ using namespace mean_field;
using Catch::Matchers::WithinAbs;
namespace prepared_test = gravity_prepared_test_utils;
namespace {
[[nodiscard]] mfem::Vector make_folding_displacement(const mean_field::fem::FEM &f) {
mfem::ParGridFunction field(f.displacementFes.get());
mfem::VectorFunctionCoefficient coefficient(
f.mesh->Dimension(), [](const mfem::Vector &position, mfem::Vector &value) {
value.SetSize(position.Size());
for (int dimension = 0; dimension < position.Size(); ++dimension) {
value(dimension) = -2.0 * position(dimension);
}
}
);
field.ProjectCoefficient(coefficient);
mfem::Vector displacementTrue;
field.GetTrueDofs(displacementTrue);
return displacementTrue;
}
} // namespace
TEST_CASE(
"Prepared Mapped Hdiv Mass Reports Invalid Candidate Geometry Without Unwinding",
tags::gravity_prepared_unit &tags::geometry
) {
auto args = test_utils::setup_args();
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
REQUIRE(f.okay());
operators::PreparedMappedHDivMassOperator preparedOperator(f, *f.domainMapperStateless);
mfem::Vector displacement = preparedOperator.GetDisplacementMap().gather(make_folding_displacement(f));
const auto rejected = preparedOperator.TryPrepare(displacement);
REQUIRE_FALSE(rejected.has_value());
CHECK(rejected.error().reason == operators::HDivMassPreparationRejectionReason::invalid_mapping);
CHECK(rejected.error().mappingStatus == mapping::MappingStatus::non_positive_determinant);
CHECK_FALSE(preparedOperator.IsPrepared());
CHECK_FALSE(preparedOperator.HasVariationData());
CHECK(preparedOperator.GetPreparationCount() == 0);
CHECK_THROWS_AS(preparedOperator.Prepare(displacement), std::domain_error);
CHECK_FALSE(preparedOperator.IsPrepared());
CHECK(preparedOperator.GetPreparationCount() == 0);
displacement = 0.0;
const auto recovered = preparedOperator.TryPrepare(displacement);
REQUIRE(recovered.has_value());
CHECK(preparedOperator.IsPrepared());
CHECK(preparedOperator.HasVariationData());
CHECK(preparedOperator.GetPreparationCount() == 1);
}
TEST_CASE(
"Prepared Mapped Hdiv Mass Matches Stateless Kernel",
tags::gravity_prepared

View File

@@ -1,5 +1,6 @@
#include <catch2/catch_test_macros.hpp>
#include <mfem.hpp>
#include <stdexcept>
import mean_field;
import test_helpers;
@@ -288,3 +289,75 @@ TEST_CASE(
CHECK(displacementEffect > 1.0e-8);
}
TEST_CASE(
"Prepared Hydrostatic Equilibrium Reports Candidate Mapping And Arithmetic Failures Without Unwinding",
tags::barotrope_hydrostatic_prepared_residual &tags::unit
) {
auto args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
REQUIRE(f.okay());
mean_field::operators::PreparedHydrostaticEquilibriumOperator preparedOperator(f, *f.domainMapperStateless);
mfem::Vector enthalpy = prepared_hydrostatic_test_utils::make_enthalpy(f);
mfem::Vector gravityPotential = prepared_hydrostatic_test_utils::make_gravity_potential(f);
mfem::Vector displacement = field_dof_test_utils::make_supported_displacement(f, 0.73);
displacement *= 1.0e200;
auto dependencies = prepared_hydrostatic_test_utils::make_dependencies();
const auto rotation = prepared_hydrostatic_test_utils::make_rotation();
const auto mappingRejection = preparedOperator.TryPrepare(
prepared_hydrostatic_test_utils::make_state(enthalpy, gravityPotential, displacement, 0.41), dependencies,
rotation
);
REQUIRE_FALSE(mappingRejection.has_value());
CHECK(
(mappingRejection.error().reason ==
mean_field::operators::HydrostaticEquilibriumPreparationRejectionReason::inverted_geometry ||
mappingRejection.error().reason ==
mean_field::operators::HydrostaticEquilibriumPreparationRejectionReason::non_finite_geometry)
);
CHECK(mappingRejection.error().mappingStatus != mean_field::mapping::MappingStatus::valid);
CHECK_FALSE(preparedOperator.IsPrepared());
CHECK_THROWS_AS(
preparedOperator.Prepare(
prepared_hydrostatic_test_utils::make_state(enthalpy, gravityPotential, displacement, 0.41), dependencies,
rotation
),
std::domain_error
);
displacement = 0.0;
++dependencies.displacement.revision;
++dependencies.rotation.revision;
const auto enormousRotation = prepared_hydrostatic_test_utils::make_rotation(1.0e200);
const auto arithmeticRejection = preparedOperator.TryPrepare(
prepared_hydrostatic_test_utils::make_state(enthalpy, gravityPotential, displacement, 0.41), dependencies,
enormousRotation
);
REQUIRE_FALSE(arithmeticRejection.has_value());
CHECK(
arithmeticRejection.error().reason ==
mean_field::operators::HydrostaticEquilibriumPreparationRejectionReason::non_finite_residual
);
CHECK_FALSE(preparedOperator.IsPrepared());
CHECK_THROWS_AS(
preparedOperator.Prepare(
prepared_hydrostatic_test_utils::make_state(enthalpy, gravityPotential, displacement, 0.41), dependencies,
enormousRotation
),
std::domain_error
);
++dependencies.rotation.revision;
const auto recovered = preparedOperator.TryPrepare(
prepared_hydrostatic_test_utils::make_state(enthalpy, gravityPotential, displacement, 0.41), dependencies,
rotation
);
REQUIRE(recovered.has_value());
CHECK(recovered->preparedResidual);
CHECK(preparedOperator.IsPrepared());
}

View File

@@ -3,6 +3,7 @@
#include <cmath>
#include <cstdint>
#include <limits>
#include <stdexcept>
#include <type_traits>
#include <catch2/catch_test_macros.hpp>
@@ -205,6 +206,7 @@ TEST_CASE(
STATIC_REQUIRE_FALSE(std::is_copy_assignable_v<Operator>);
STATIC_REQUIRE_FALSE(std::is_move_constructible_v<Operator>);
STATIC_REQUIRE_FALSE(std::is_move_assignable_v<Operator>);
STATIC_REQUIRE(std::is_trivially_copyable_v<mean_field::operators::MassNormalizationPreparationRejection>);
mean_field::utils::Args args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
@@ -271,6 +273,49 @@ TEST_CASE(
CHECK(mass_normalization_test_utils::relative_error(measuredScale, expectedScale) < 5e-7);
}
TEST_CASE(
"Prepared Mass Normalization Reports Non-Finite Density Interpolation Without Unwinding",
tags::barotrope_mass_normalization_context &tags::unit
) {
using namespace mass_normalization_test_utils;
mean_field::utils::Args args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
REQUIRE(f.okay());
mfem::Vector density = make_constant_density(f, -0.5 * std::numeric_limits<double>::max());
mfem::Vector displacement(f.displacementFes->GetTrueVSize());
displacement = 0.0;
auto dependencies = make_dependencies();
mean_field::operators::context::gravity_field::GravityFieldLinearizationContext gravityContext(
f, *f.domainMapperStateless
);
prepare_gravity_context(gravityContext, f, density, displacement, dependencies);
mean_field::operators::PreparedMassNormalizationOperator massOperator(f, *f.domainMapperStateless, gravityContext);
const auto rejected = massOperator.TryPrepare({.targetMass = std::numeric_limits<double>::max()}, dependencies);
REQUIRE_FALSE(rejected.has_value());
CHECK(
rejected.error().reason ==
mean_field::operators::MassNormalizationPreparationRejectionReason::non_finite_density_interpolation
);
CHECK_FALSE(massOperator.IsPrepared());
CHECK_THROWS_AS(
massOperator.Prepare({.targetMass = std::numeric_limits<double>::max()}, dependencies), std::domain_error
);
density = make_constant_density(f, 1.0);
++dependencies.density.revision;
++dependencies.targetMass.revision;
prepare_gravity_context(gravityContext, f, density, displacement, dependencies);
const auto accepted = massOperator.TryPrepare({.targetMass = 1.0}, dependencies);
REQUIRE(accepted.has_value());
CHECK(massOperator.IsPrepared());
}
TEST_CASE(
"Prepared Mass Normalization Density Jacobian Matches Centered Difference",
tags::barotrope_mass_normalization_jacobian &tags::accuracy

View File

@@ -3,6 +3,7 @@
#include <cmath>
#include <limits>
#include <memory>
#include <stdexcept>
#include <catch2/catch_test_macros.hpp>
#include <mfem.hpp>
@@ -51,6 +52,26 @@ namespace prepared_pressure_force_test_utils {
}
};
[[nodiscard]] mfem::Vector make_affine_displacement(
const mean_field::fem::FEM &f,
const double scale
) {
mfem::ParGridFunction field(f.displacementFes.get());
mfem::VectorFunctionCoefficient coefficient(
f.mesh->Dimension(), [scale](const mfem::Vector &position, mfem::Vector &value) {
value.SetSize(position.Size());
for (int dimension = 0; dimension < position.Size(); ++dimension) {
value(dimension) = scale * position(dimension);
}
}
);
field.ProjectCoefficient(coefficient);
mfem::Vector result;
field.GetTrueDofs(result);
return result;
}
[[nodiscard]]
mfem::Vector make_positive_enthalpy_true(
const mean_field::fem::FEM &f,
@@ -265,6 +286,97 @@ TEST_CASE(
);
}
TEST_CASE(
"Prepared Pressure Force Reports Expected EOS Rejections Without Unwinding",
tags::barotrope &tags::pressure &tags::prepared &tags::unit
) {
mean_field::utils::Args args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
REQUIRE(f.okay());
const prepared_pressure_force_test_utils::Maps maps(f);
const mean_field::eos::Polytrope equationOfState(3.0, 0.25);
mean_field::operators::PreparedPressureForceOperator preparedOperator(f, *f.domainMapperStateless, equationOfState);
mfem::Vector enthalpy(maps.enthalpy.reduced_size());
mfem::Vector displacement(maps.displacement.reduced_size());
enthalpy = -1.0;
displacement = 0.0;
auto dependencies = prepared_pressure_force_test_utils::make_dependencies();
const auto outsideDomain =
preparedOperator.TryPrepare({.enthalpy = enthalpy, .displacement = displacement}, dependencies);
REQUIRE_FALSE(outsideDomain.has_value());
CHECK(
outsideDomain.error().reason ==
mean_field::operators::PressureForcePreparationRejectionReason::equation_of_state
);
CHECK(outsideDomain.error().equationOfStateCode == mean_field::eos::EvaluationErrorCode::outside_domain);
CHECK_FALSE(preparedOperator.IsPrepared());
try {
(void)preparedOperator.Prepare({.enthalpy = enthalpy, .displacement = displacement}, dependencies);
FAIL("The compatibility Prepare overload accepted an out-of-domain EOS input.");
} catch (const mean_field::eos::EvaluationError &error) {
CHECK(error.code() == mean_field::eos::EvaluationErrorCode::outside_domain);
}
enthalpy = std::numeric_limits<double>::max();
++dependencies.enthalpy.revision;
const auto rejected =
preparedOperator.TryPrepare({.enthalpy = enthalpy, .displacement = displacement}, dependencies);
REQUIRE_FALSE(rejected.has_value());
CHECK(rejected.error().reason == mean_field::operators::PressureForcePreparationRejectionReason::equation_of_state);
CHECK(rejected.error().equationOfStateCode == mean_field::eos::EvaluationErrorCode::nonfinite_result);
CHECK_FALSE(preparedOperator.IsPrepared());
CHECK_THROWS_AS(
preparedOperator.Prepare({.enthalpy = enthalpy, .displacement = displacement}, dependencies),
mean_field::eos::EvaluationError
);
enthalpy = 1.0;
++dependencies.enthalpy.revision;
const auto accepted =
preparedOperator.TryPrepare({.enthalpy = enthalpy, .displacement = displacement}, dependencies);
REQUIRE(accepted.has_value());
CHECK(preparedOperator.IsPrepared());
}
TEST_CASE(
"Prepared Pressure Force Reports Invalid Candidate Geometry Without Unwinding",
tags::barotrope &tags::pressure &tags::prepared &tags::geometry &tags::unit
) {
mean_field::utils::Args args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
REQUIRE(f.okay());
const prepared_pressure_force_test_utils::Maps maps(f);
const mean_field::eos::Polytrope equationOfState(3.0, 0.25);
mean_field::operators::PreparedPressureForceOperator preparedOperator(f, *f.domainMapperStateless, equationOfState);
mfem::Vector enthalpy(maps.enthalpy.reduced_size());
enthalpy = 1.0;
mfem::Vector displacement =
maps.displacement.gather(prepared_pressure_force_test_utils::make_affine_displacement(f, -2.0));
auto dependencies = prepared_pressure_force_test_utils::make_dependencies();
const auto rejected =
preparedOperator.TryPrepare({.enthalpy = enthalpy, .displacement = displacement}, dependencies);
REQUIRE_FALSE(rejected.has_value());
CHECK(rejected.error().reason == mean_field::operators::PressureForcePreparationRejectionReason::invalid_mapping);
CHECK(rejected.error().mappingStatus == mean_field::mapping::MappingStatus::non_positive_determinant);
CHECK_FALSE(preparedOperator.IsPrepared());
CHECK_THROWS_AS(
preparedOperator.Prepare({.enthalpy = enthalpy, .displacement = displacement}, dependencies), std::domain_error
);
displacement = 0.0;
++dependencies.displacement.revision;
const auto accepted =
preparedOperator.TryPrepare({.enthalpy = enthalpy, .displacement = displacement}, dependencies);
REQUIRE(accepted.has_value());
CHECK(preparedOperator.IsPrepared());
}
TEST_CASE(
"Prepared Pressure Force Jacobian Matches Full Stateless Columns "
"Through FieldDof Restriction",

View File

@@ -2,6 +2,7 @@
#include <array>
#include <cmath>
#include <limits>
#include <stdexcept>
#include <catch2/catch_test_macros.hpp>
@@ -127,6 +128,26 @@ namespace rotational_displacement_force_test_utils {
return direction;
}
[[nodiscard]] mfem::Vector make_affine_displacement(
const mean_field::fem::FEM &f,
const double scale
) {
mfem::ParGridFunction field(f.displacementFes.get());
mfem::VectorFunctionCoefficient coefficient(
f.mesh->Dimension(), [scale](const mfem::Vector &position, mfem::Vector &value) {
value.SetSize(position.Size());
for (int dimension = 0; dimension < position.Size(); ++dimension) {
value(dimension) = scale * position(dimension);
}
}
);
field.ProjectCoefficient(coefficient);
mfem::Vector result;
field.GetTrueDofs(result);
return result;
}
[[nodiscard]] mean_field::physics::RigidRotation make_rotation(const double scale = 1.0) {
mfem::Vector angularVelocity(3);
angularVelocity(0) = scale * 0.17;
@@ -377,6 +398,85 @@ TEST_CASE(
CHECK(rotational_displacement_force_test_utils::global_norm(zeroRotationResidual, f.mesh->GetComm()) == 0.0);
}
TEST_CASE(
"Rotational Displacement Force Reports Candidate Mapping And Arithmetic Rejections",
tags::rotation_prepared_unit
) {
mean_field::utils::Args args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
REQUIRE(f.okay());
mfem::Vector density = rotational_displacement_force_test_utils::make_density(f, 0.29);
mfem::Vector displacement = rotational_displacement_force_test_utils::make_affine_displacement(f, -2.0);
const mean_field::physics::RigidRotation rotation = rotational_displacement_force_test_utils::make_rotation(0.83);
mfem::Vector residual;
const auto invalidMapping = mean_field::operators::kernels::try_apply_rotational_displacement_force_residual(
f, *f.domainMapperStateless, rotation, density, displacement, residual
);
REQUIRE_FALSE(invalidMapping.has_value());
CHECK(
invalidMapping.error().reason ==
mean_field::operators::kernels::RotationalDisplacementForceRejectionReason::invalid_mapping
);
CHECK(invalidMapping.error().mappingStatus == mean_field::mapping::MappingStatus::non_positive_determinant);
CHECK_THROWS_AS(
mean_field::operators::kernels::apply_rotational_displacement_force_residual(
f, *f.domainMapperStateless, rotation, density, displacement, residual
),
std::domain_error
);
displacement = 0.0;
density = 1.0e200;
const mean_field::physics::RigidRotation extremeRotation =
rotational_displacement_force_test_utils::make_rotation(1.0e100);
const auto nonFiniteArithmetic = mean_field::operators::kernels::try_apply_rotational_displacement_force_residual(
f, *f.domainMapperStateless, extremeRotation, density, displacement, residual
);
REQUIRE_FALSE(nonFiniteArithmetic.has_value());
CHECK(
nonFiniteArithmetic.error().reason ==
mean_field::operators::kernels::RotationalDisplacementForceRejectionReason::non_finite_arithmetic
);
mean_field::operators::PreparedRotationalDisplacementForceOperator preparedOperator(f, *f.domainMapperStateless);
const auto &context = preparedOperator.GetContext();
auto dependencies = rotational_displacement_force_test_utils::make_dependencies();
mfem::Vector reducedDensity = context.GetDensityMap().gather(density);
const mfem::Vector reducedDisplacement = context.GetDisplacementMap().gather(displacement);
const auto preparedRejection = preparedOperator.TryPrepare(
{.density = reducedDensity, .displacement = reducedDisplacement}, dependencies, extremeRotation
);
REQUIRE_FALSE(preparedRejection.has_value());
CHECK(
preparedRejection.error().reason ==
mean_field::operators::kernels::RotationalDisplacementForceRejectionReason::non_finite_arithmetic
);
CHECK_FALSE(preparedOperator.IsPrepared());
CHECK_THROWS_AS(
preparedOperator.Prepare(
{.density = reducedDensity, .displacement = reducedDisplacement}, dependencies, extremeRotation
),
std::domain_error
);
density = rotational_displacement_force_test_utils::make_density(f, 0.29);
reducedDensity = context.GetDensityMap().gather(density);
++dependencies.density.revision;
++dependencies.rotation.revision;
const auto preparedAccepted = preparedOperator.TryPrepare(
{.density = reducedDensity, .displacement = reducedDisplacement}, dependencies, rotation
);
REQUIRE(preparedAccepted.has_value());
CHECK(preparedOperator.IsPrepared());
const auto accepted = mean_field::operators::kernels::try_apply_rotational_displacement_force_residual(
f, *f.domainMapperStateless, rotation, density, displacement, residual
);
CHECK(accepted.has_value());
}
TEST_CASE(
"Prepared Rotational Displacement Force Reprepares Selectively",
tags::rotation_prepared

View File

@@ -1505,6 +1505,69 @@ TEST_CASE(
CHECK(targetReport.assembledResidual);
}
TEST_CASE(
"Prepared Stellar Trial Preparation Reports Folded Geometry Without Unwinding",
tags::reduced_stellar_geometry &tags::prepared &tags::geometry &tags::unit
) {
mean_field::utils::Args args = test_utils::setup_args();
mean_field::fem::FEM f = mean_field::fem::setup_fem(args.mesh_file, args, 0);
REQUIRE(f.okay());
const mean_field::eos::Polytrope barotrope(3.0, 0.25);
const auto stellarModel = stellar_equilibrium_test_utils::make_stellar_model(barotrope, 1.15);
const auto parameterGeometry = stellarModel.compileDomainDeformation(f);
const auto &surface = parameterGeometry.surfaceDeformationPrescription();
mean_field::operators::PreparedStellarEquilibriumOperator stellarOperator(
f, *f.domainMapperStateless, stellarModel
);
const auto &layout = stellarOperator.GetLayout();
mfem::Vector state = stellar_equilibrium_test_utils::make_state(f, layout);
auto dependencies = stellar_equilibrium_test_utils::make_dependencies();
const auto rotation = stellar_equilibrium_test_utils::make_zero_rotation();
const double finiteStateValue = state(0);
state(0) = std::numeric_limits<double>::quiet_NaN();
const auto nonFiniteState = stellarOperator.TryPrepare(state, dependencies, rotation);
REQUIRE_FALSE(nonFiniteState.has_value());
CHECK(
nonFiniteState.error().reason ==
mean_field::operators::StellarEquilibriumPreparationRejectionReason::non_finite_physics
);
CHECK_FALSE(stellarOperator.IsPrepared());
CHECK_THROWS_AS(stellarOperator.Prepare(state, dependencies, rotation), std::domain_error);
state(0) = finiteStateValue;
mfem::Vector foldingParameters(surface.parameterCount());
for (int parameter = 0; parameter < foldingParameters.Size(); ++parameter) {
foldingParameters(parameter) = -1.5 * surface.referenceRadius(parameter);
}
stellar_equilibrium_test_utils::assign_value_block(
state, layout, stellar_equilibrium_test_utils::displacementValue, foldingParameters
);
const auto rejected = stellarOperator.TryPrepare(state, dependencies, rotation);
REQUIRE_FALSE(rejected.has_value());
CHECK(
rejected.error().reason ==
mean_field::operators::StellarEquilibriumPreparationRejectionReason::inverted_geometry
);
CHECK(rejected.error().stage == mean_field::operators::StellarEquilibriumPreparationStage::generated_geometry);
CHECK(std::isfinite(rejected.error().minimumJacobianDeterminant));
CHECK(rejected.error().minimumJacobianDeterminant <= 0.0);
CHECK_FALSE(stellarOperator.IsPrepared());
CHECK_THROWS_AS(stellarOperator.Prepare(state, dependencies, rotation), std::domain_error);
foldingParameters = 0.0;
stellar_equilibrium_test_utils::assign_value_block(
state, layout, stellar_equilibrium_test_utils::displacementValue, foldingParameters
);
const auto accepted = stellarOperator.TryPrepare(state, dependencies, rotation);
REQUIRE(accepted.has_value());
CHECK(accepted->generatedGeometry.isOrientationPreserving());
CHECK(stellarOperator.IsPrepared());
}
TEST_CASE(
"Accepted Reduced Geometries Remain Valid Across Prepared Stellar Physics Quadrature Rules",
tags::reduced_stellar_geometry &tags::prepared &tags::geometry &tags::self_consistency

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,7 @@
#include <algorithm>
#include <cmath>
#include <concepts>
#include <limits>
#include <numbers>
#include <type_traits>
#include <utility>
@@ -12,8 +13,7 @@ import mean_field;
import test_helpers;
namespace outer_manifest_report_test {
template <mean_field::model::StellarModelType Model>
class PreparedEarlierMultiplier;
template <mean_field::model::StellarModelType Model> class PreparedEarlierMultiplier;
class EarlierMultiplier final {
public:
@@ -36,12 +36,9 @@ namespace outer_manifest_report_test {
"R_a",
"specific_energy",
"specific_energy">>;
using EquilibriumPhysics =
mean_field::operators::SpecificationEquilibriumPhysics<
PreparedEarlierMultiplier>;
using EquilibriumPhysics = mean_field::operators::SpecificationEquilibriumPhysics<PreparedEarlierMultiplier>;
explicit EarlierMultiplier(const Parameters parameters) noexcept
: m_target(parameters.target) {
explicit EarlierMultiplier(const Parameters parameters) noexcept : m_target(parameters.target) {
}
[[nodiscard]] mean_field::dimensions::SpecificEnergyValue target() const noexcept {
@@ -52,20 +49,20 @@ namespace outer_manifest_report_test {
mean_field::dimensions::SpecificEnergyValue m_target;
};
template <mean_field::model::StellarModelType Model>
class PreparedEarlierMultiplier final {
template <mean_field::model::StellarModelType Model> class PreparedEarlierMultiplier final {
public:
using Report = mean_field::operators::EmptySpecificationPreparationReport;
explicit PreparedEarlierMultiplier(const EarlierMultiplier &) noexcept {
}
template <typename StateView>
[[nodiscard]] Report PrepareAfterPhysical(const StateView &) noexcept {
template <typename StateView> [[nodiscard]] Report PrepareAfterPhysical(const StateView &) noexcept {
return {};
}
template <typename Equation, typename Row>
template <
typename Equation,
typename Row>
[[nodiscard]] mean_field::stellar::StructuralZero AddResidual(
Equation,
Row &
@@ -73,9 +70,15 @@ namespace outer_manifest_report_test {
return mean_field::stellar::structuralZero;
}
template <typename Equation, typename State, typename Direction, typename Row>
template <
typename Equation,
typename State,
typename Direction,
typename Row>
[[nodiscard]] mean_field::stellar::StructuralZero AddJacobianAction(
mean_field::stellar::Derivative<Equation, State>,
mean_field::stellar::Derivative<
Equation,
State>,
const Direction &,
Row &
) const noexcept {
@@ -89,40 +92,38 @@ namespace outer_manifest_report_test {
} // namespace outer_manifest_report_test
namespace {
using BaseModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
using BaseModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
mean_field::eos::Polytrope,
mean_field::surface::Isobaric,
mean_field::integral::FixedTotalMass>>;
using CentralDensityModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
using CentralDensityModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
mean_field::eos::Polytrope,
mean_field::surface::Isobaric,
mean_field::integral::FixedTotalMass,
mean_field::constraint::FixedCentralDensity>>;
using AngularMomentumModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
using AngularMomentumModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
mean_field::eos::Polytrope,
mean_field::surface::Isobaric,
mean_field::integral::FixedTotalMass,
mean_field::integral::FixedAngularMomentum>>;
using AngularMomentumCentralDensityModel =
mean_field::model::StellarModel<mean_field::models::SpecificationSet<
mean_field::eos::Polytrope,
mean_field::surface::Isobaric,
mean_field::integral::FixedTotalMass,
mean_field::integral::FixedAngularMomentum,
mean_field::constraint::FixedCentralDensity>>;
using AngularMomentumCentralDensityModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
mean_field::eos::Polytrope,
mean_field::surface::Isobaric,
mean_field::integral::FixedTotalMass,
mean_field::integral::FixedAngularMomentum,
mean_field::constraint::FixedCentralDensity>>;
using IncompleteModel =
mean_field::model::StellarModel<mean_field::models::SpecificationSet<mean_field::eos::Polytrope>>;
using EarlierMultiplierModel =
mean_field::model::StellarModel<mean_field::models::SpecificationSet<
mean_field::eos::Polytrope,
mean_field::surface::Isobaric,
outer_manifest_report_test::EarlierMultiplier,
mean_field::integral::FixedTotalMass>>;
using EarlierMultiplierModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
mean_field::eos::Polytrope,
mean_field::surface::Isobaric,
outer_manifest_report_test::EarlierMultiplier,
mean_field::integral::FixedTotalMass>>;
template <typename Candidate>
concept HasLegacyNumericalModelAdapter = requires { typename Candidate::NumericalModelAdapter; };
@@ -166,8 +167,8 @@ TEST_CASE(
) {
using namespace mean_field;
using BaseProblem = equilibrium::StellarEquilibriumProblem<BaseModel>;
using CentralDensityProblem = equilibrium::StellarEquilibriumProblem<CentralDensityModel>;
using BaseProblem = equilibrium::StellarEquilibriumProblem<BaseModel>;
using CentralDensityProblem = equilibrium::StellarEquilibriumProblem<CentralDensityModel>;
using AngularMomentumProblem = equilibrium::StellarEquilibriumProblem<AngularMomentumModel>;
using AngularMomentumCentralDensityProblem =
equilibrium::StellarEquilibriumProblem<AngularMomentumCentralDensityModel>;
@@ -178,14 +179,17 @@ TEST_CASE(
STATIC_CHECK(equilibrium::StellarEquilibriumModel<AngularMomentumCentralDensityModel>);
STATIC_CHECK(equilibrium::StellarEquilibriumModel<EarlierMultiplierModel>);
STATIC_CHECK_FALSE(equilibrium::StellarEquilibriumModel<IncompleteModel>);
STATIC_CHECK_FALSE(operators::StellarEquilibriumRuntimeContribution<
outer_manifest_report_test::EarlierMultiplier>::registered);
STATIC_CHECK_FALSE(operators::stellarEquilibriumBackendRuntimeAuthorized<
outer_manifest_report_test::EarlierMultiplier,
EarlierMultiplierModel>);
STATIC_CHECK(operators::StellarEquilibriumPhysicsAvailableFor<
outer_manifest_report_test::EarlierMultiplier,
EarlierMultiplierModel>);
STATIC_CHECK_FALSE(
operators::StellarEquilibriumRuntimeContribution<outer_manifest_report_test::EarlierMultiplier>::registered
);
STATIC_CHECK_FALSE(
operators::stellarEquilibriumBackendRuntimeAuthorized<
outer_manifest_report_test::EarlierMultiplier, EarlierMultiplierModel>
);
STATIC_CHECK(
operators::StellarEquilibriumPhysicsAvailableFor<
outer_manifest_report_test::EarlierMultiplier, EarlierMultiplierModel>
);
STATIC_CHECK_FALSE(std::same_as<BaseProblem, CentralDensityProblem>);
STATIC_CHECK(BaseProblem::symbolicallySquare);
STATIC_CHECK(CentralDensityProblem::symbolicallySquare);
@@ -213,20 +217,23 @@ TEST_CASE(
typename AngularMomentumProblem::PreparedOperatorType,
operators::PreparedVariadicStellarEquilibriumOperator<AngularMomentumModel>>
);
STATIC_CHECK_FALSE(std::same_as<
typename BaseProblem::PreparedOperatorType,
typename CentralDensityProblem::PreparedOperatorType>);
STATIC_CHECK_FALSE(std::same_as<
typename AngularMomentumProblem::PreparedOperatorType,
typename AngularMomentumCentralDensityProblem::PreparedOperatorType>);
STATIC_CHECK_FALSE(
std::same_as<typename BaseProblem::PreparedOperatorType, typename CentralDensityProblem::PreparedOperatorType>
);
STATIC_CHECK_FALSE(
std::same_as<
typename AngularMomentumProblem::PreparedOperatorType,
typename AngularMomentumCentralDensityProblem::PreparedOperatorType>
);
STATIC_CHECK(AngularMomentumProblem::FormType::value_block_count == 7);
STATIC_CHECK(AngularMomentumCentralDensityProblem::FormType::value_block_count == 8);
STATIC_CHECK(std::same_as<
typename BaseProblem::FormType,
utils::blocks::surface_deformed_stellar_equilibrium_form>);
STATIC_CHECK(std::same_as<
typename CentralDensityProblem::FormType,
utils::blocks::central_density_bordered_stellar_equilibrium_form>);
STATIC_CHECK(
std::same_as<typename BaseProblem::FormType, utils::blocks::surface_deformed_stellar_equilibrium_form>
);
STATIC_CHECK(
std::same_as<
typename CentralDensityProblem::FormType, utils::blocks::central_density_bordered_stellar_equilibrium_form>
);
STATIC_CHECK(
std::same_as<
typename BaseProblem::CompiledSurfaceConstraintType,
@@ -242,34 +249,29 @@ TEST_CASE(
) {
using namespace mean_field;
utils::Args arguments = test_utils::setup_args();
utils::Args arguments = test_utils::setup_args();
fem::FEM finiteElements = fem::setup_fem(arguments.mesh_file, arguments, 0);
REQUIRE(finiteElements.okay());
constexpr double radius = utils::RADIUS;
constexpr double mass = utils::MASS;
constexpr double radius = utils::RADIUS;
constexpr double mass = utils::MASS;
constexpr double targetAngularMomentum = 0.1;
const double polytropicConstant = 2.0 * utils::G * radius * radius / std::numbers::pi_v<double>;
const double seedCentralDensity =
std::numbers::pi_v<double> * mass / (4.0 * radius * radius * radius);
auto model = model::StellarModel(
const double polytropicConstant = 2.0 * utils::G * radius * radius / std::numbers::pi_v<double>;
const double seedCentralDensity = std::numbers::pi_v<double> * mass / (4.0 * radius * radius * radius);
auto model = model::StellarModel(
eos::Polytrope({.n = 1.0, .K = polytropicConstant}),
surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}),
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{mass}}),
integral::FixedAngularMomentum({
.Jtotal = dimensions::AngularMomentumValue{targetAngularMomentum},
.axis = {0.0, 0.0, 3.0}
})
integral::FixedAngularMomentum(
{.Jtotal = dimensions::AngularMomentumValue{targetAngularMomentum}, .axis = {0.0, 0.0, 3.0}}
)
);
auto problem = equilibrium::discretize(model, finiteElements);
auto problem = equilibrium::discretize(model, std::move(finiteElements));
auto projected = seed::makeProjectedEquilibriumState(
problem,
seed::LaneEmden({
.centralDensity = dimensions::DensityValue{seedCentralDensity},
.radialSampleCount = 1024
})
seed::LaneEmden({.centralDensity = dimensions::DensityValue{seedCentralDensity}, .radialSampleCount = 1024})
);
auto dependencies = make_dependencies();
auto dependencies = make_dependencies();
const auto preparation = problem.Prepare(projected.values, dependencies);
CHECK(preparation.generatedPhysicalControl);
@@ -284,7 +286,7 @@ TEST_CASE(
CHECK(std::abs(angularReport.scaledResidual) < 7.0e-4);
mfem::Vector direction(problem.StateSize());
direction = 0.0;
direction = 0.0;
mfem::Vector angularVelocityDirection = problem.GetManifest().stateView(direction).block(
utils::blocks::fixed_angular_momentum_constraint.angular_velocity_term
);
@@ -309,15 +311,15 @@ TEST_CASE(
plusResidual -= minusResidual;
plusResidual /= 2.0 * step;
auto analyticView = problem.GetManifest().residualView(analyticAction);
auto differenceView = problem.GetManifest().residualView(plusResidual);
auto analyticView = problem.GetManifest().residualView(analyticAction);
auto differenceView = problem.GetManifest().residualView(plusResidual);
const auto blockError = [&](const auto &term) {
const mfem::Vector analytic = analyticView.block(term);
const mfem::Vector analytic = analyticView.block(term);
const mfem::Vector difference = differenceView.block(term);
return relative_difference(analytic, difference);
};
const double surfaceError = blockError(utils::blocks::surface_deformation_field.shape_equilibrium_term);
const double surfaceError = blockError(utils::blocks::surface_deformation_field.shape_equilibrium_term);
const double enthalpyError = blockError(utils::blocks::enthalpy_field.specific_term);
const double angularMomentumError =
blockError(utils::blocks::fixed_angular_momentum_constraint.angular_velocity_term);
@@ -335,13 +337,48 @@ TEST_CASE(
CHECK(analyticView.block(utils::blocks::density_field.mass_term).Norml2() == 0.0);
CHECK(analyticView.block(utils::blocks::fixed_total_mass_constraint.mass_normalization_term).Norml2() == 0.0);
mfem::Vector nonFiniteAngularVelocityState(projected.values);
auto nonFiniteAngularVelocity = problem.GetManifest()
.stateView(nonFiniteAngularVelocityState)
.block(utils::blocks::fixed_angular_momentum_constraint.angular_velocity_term);
REQUIRE(nonFiniteAngularVelocity.Size() == 1);
nonFiniteAngularVelocity(0) = std::numeric_limits<double>::quiet_NaN();
nonFiniteAngularVelocity.SyncAliasMemory(nonFiniteAngularVelocityState);
const auto nonFiniteControl = problem.TryPrepare(nonFiniteAngularVelocityState, dependencies);
REQUIRE_FALSE(nonFiniteControl.has_value());
CHECK(
nonFiniteControl.error().reason == operators::StellarEquilibriumPreparationRejectionReason::non_finite_physics
);
CHECK_FALSE(problem.IsPrepared());
REQUIRE(problem.TryPrepare(projected.values, dependencies).has_value());
mfem::Vector negativeDensityState(projected.values);
auto negativeDensity =
problem.GetManifest().stateView(negativeDensityState).block(utils::blocks::density_field.mass_term);
negativeDensity *= -1.0;
negativeDensity.SyncAliasMemory(negativeDensityState);
auto negativeDensityDependencies = dependencies;
++negativeDensityDependencies.density.revision;
const auto inadmissibleMoment = problem.TryPrepare(negativeDensityState, negativeDensityDependencies);
REQUIRE_FALSE(inadmissibleMoment.has_value());
CHECK(
inadmissibleMoment.error().reason ==
operators::StellarEquilibriumPreparationRejectionReason::inadmissible_physics
);
CHECK_FALSE(problem.IsPrepared());
++negativeDensityDependencies.density.revision;
REQUIRE(problem.TryPrepare(projected.values, negativeDensityDependencies).has_value());
CHECK(problem.IsPrepared());
auto zeroModel = model::StellarModel(
eos::Polytrope({.n = 1.0, .K = polytropicConstant}),
surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}),
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{mass}}),
integral::FixedAngularMomentum({.Jtotal = dimensions::AngularMomentumValue{0.0}})
);
auto zeroProblem = equilibrium::discretize(zeroModel, finiteElements);
fem::FEM zeroFiniteElements = fem::setup_fem(arguments.mesh_file, arguments, 0);
REQUIRE(zeroFiniteElements.okay());
auto zeroProblem = equilibrium::discretize(zeroModel, std::move(zeroFiniteElements));
mfem::Vector zeroState(projected.values);
zeroProblem.GetManifest().stateView(zeroState).block(
utils::blocks::fixed_angular_momentum_constraint.angular_velocity_term
@@ -361,47 +398,37 @@ TEST_CASE(
) {
using namespace mean_field;
using Form = operators::CompiledStellarEquilibriumForm<EarlierMultiplierModel>;
using EarlierValue = utils::blocks::generated_value_block<
models::MultiplierFor<outer_manifest_report_test::EarlierMultiplier>>;
using EarlierValue =
utils::blocks::generated_value_block<models::MultiplierFor<outer_manifest_report_test::EarlierMultiplier>>;
using MassValue = utils::blocks::fixed_total_mass::mass_normalization::value;
STATIC_CHECK(utils::blocks::type_index_v<
EarlierValue,
typename Form::value_blocks> == 5);
STATIC_CHECK(utils::blocks::type_index_v<
MassValue,
typename Form::value_blocks> == 6);
STATIC_CHECK(utils::blocks::type_index_v<EarlierValue, typename Form::value_blocks> == 5);
STATIC_CHECK(utils::blocks::type_index_v<MassValue, typename Form::value_blocks> == 6);
utils::Args arguments = test_utils::setup_args();
utils::Args arguments = test_utils::setup_args();
fem::FEM finiteElements = fem::setup_fem(arguments.mesh_file, arguments, 0);
REQUIRE(finiteElements.okay());
auto model = model::StellarModel(
eos::Polytrope({.n = 1.0, .K = 0.25}),
surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}),
outer_manifest_report_test::EarlierMultiplier({
.target = dimensions::SpecificEnergyValue{0.75}}),
eos::Polytrope({.n = 1.0, .K = 0.25}), surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}),
outer_manifest_report_test::EarlierMultiplier({.target = dimensions::SpecificEnergyValue{0.75}}),
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{1.25}})
);
auto problem = equilibrium::discretize(model, finiteElements);
auto problem = equilibrium::discretize(model, std::move(finiteElements));
mfem::Vector state(problem.StateSize());
state = 0.0;
const auto stateView = problem.GetManifest().stateView(state);
stateView.block(utils::blocks::density_field.mass_term) = 1.0;
state = 0.0;
const auto stateView = problem.GetManifest().stateView(state);
stateView.block(utils::blocks::density_field.mass_term) = 1.0;
stateView.block(utils::blocks::enthalpy_field.specific_term) = 1.0;
stateView.block(utils::blocks::fixed_total_mass_constraint.mass_normalization_term) = 0.25;
const auto preparation = problem.Prepare(
state,
make_dependencies(),
make_zero_rotation()
);
REQUIRE(preparation.physical.DidAnyWork());
const auto preparation = problem.TryPrepare(state, make_dependencies(), make_zero_rotation());
REQUIRE(preparation.has_value());
REQUIRE(preparation->physical.DidAnyWork());
const auto report = problem.GetPreparedOperator().GetFixedMassReport();
const auto &outerDescriptor =
problem.GetManifest().template specification<models::FixedTotalMass>();
const auto report = problem.GetPreparedOperator().GetFixedMassReport();
const auto &outerDescriptor = problem.GetManifest().template specification<models::FixedTotalMass>();
CHECK(report.descriptor.stableId == outerDescriptor.stableId);
CHECK(report.descriptor.valueBlock == outerDescriptor.valueBlock);
CHECK(report.descriptor.residualBlock == outerDescriptor.residualBlock);
@@ -409,8 +436,7 @@ TEST_CASE(
CHECK(report.descriptor.residualBlock == 6);
CHECK(report.descriptor.target == 1.25);
CHECK(report.dimensionalResidual == report.achieved - report.descriptor.target);
CHECK(report.scaledResidual ==
report.dimensionalResidual / report.descriptor.residualScale);
CHECK(report.scaledResidual == report.dimensionalResidual / report.descriptor.residualScale);
}
TEST_CASE(
@@ -419,32 +445,38 @@ TEST_CASE(
) {
using namespace mean_field;
utils::Args args = test_utils::setup_args();
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
REQUIRE(f.okay());
utils::Args args = test_utils::setup_args();
fem::FEM legacyFiniteElements = fem::setup_fem(args.mesh_file, args, 0);
REQUIRE(legacyFiniteElements.okay());
fem::FEM modelDrivenFiniteElements = fem::setup_fem(args.mesh_file, args, 0);
REQUIRE(modelDrivenFiniteElements.okay());
const MPI_Comm modelDrivenCommunicator = modelDrivenFiniteElements.mesh->GetComm();
const mapping::DomainMapper *modelDrivenMapper = modelDrivenFiniteElements.domainMapperStateless.get();
models::StellarModel legacyModel{
models::structure::PolytropicStructure{eos::Polytrope{3.0, 0.25}, 1.25},
surface::ConstantPressureSurface{eos::PressureValue{0.0}}
};
operators::PreparedStellarEquilibriumOperator legacyOperator(f, *f.domainMapperStateless, legacyModel);
operators::PreparedStellarEquilibriumOperator legacyOperator(
legacyFiniteElements, *legacyFiniteElements.domainMapperStateless, legacyModel
);
const equilibrium::StellarDiscretization discretization{f, *f.domainMapperStateless};
auto equilibriumProblem = equilibrium::discretize(
model::StellarModel(
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{1.25}}),
surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}), eos::Polytrope({.n = 3.0, .K = 0.25})
),
discretization
std::move(modelDrivenFiniteElements)
);
auto &modelDrivenOperator = equilibriumProblem.GetPreparedOperator();
auto &modelDrivenOperator = equilibriumProblem.GetPreparedOperator();
const auto &physicalOperator = equilibriumProblem.GetPhysicalOperator();
CHECK(equilibriumProblem.StateSize() == legacyOperator.Width());
CHECK(equilibriumProblem.EquationSize() == legacyOperator.Height());
CHECK(equilibriumProblem.StateSize() == equilibriumProblem.EquationSize());
CHECK(&equilibriumProblem.GetDiscretization().finiteElementModel() == &f);
CHECK(&equilibriumProblem.GetDiscretization().domainMapper() == f.domainMapperStateless.get());
CHECK(equilibriumProblem.GetCommunicator() == modelDrivenCommunicator);
CHECK(&equilibriumProblem.GetDiscretization().domainMapper() == modelDrivenMapper);
CHECK(equilibriumProblem.GetDiscretization().isCurrent());
CHECK(physicalOperator.GetTargetMass() == 1.25);
CHECK(physicalOperator.GetSurfaceConstraintOperator().GetPhysicalCondition().targetPressure == 0.0);
@@ -455,7 +487,7 @@ TEST_CASE(
mfem::Vector state(legacyOperator.Width());
state = 0.0;
const auto stateView = legacyOperator.GetRootManifest().stateView(state);
const auto stateView = legacyOperator.GetRootManifest().stateView(state);
stateView.block(utils::blocks::density_field.mass_term) = 1.0;
stateView.block(utils::blocks::enthalpy_field.specific_term) = 1.0;
@@ -487,29 +519,28 @@ TEST_CASE(
) {
using namespace mean_field;
utils::Args arguments = test_utils::setup_args();
utils::Args arguments = test_utils::setup_args();
fem::FEM finiteElements = fem::setup_fem(arguments.mesh_file, arguments, 0);
REQUIRE(finiteElements.okay());
auto model = model::StellarModel(
eos::Polytrope({.n = 1.0, .K = 0.25}),
surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}),
eos::Polytrope({.n = 1.0, .K = 0.25}), surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}),
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{1.0}}),
integral::FixedAngularMomentum({.Jtotal = dimensions::AngularMomentumValue{0.2}}),
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{1.0}})
);
auto problem = equilibrium::discretize(model, finiteElements);
auto problem = equilibrium::discretize(model, std::move(finiteElements));
using Problem = std::remove_cvref_t<decltype(problem)>;
STATIC_CHECK(Problem::FormType::value_block_count == 8);
STATIC_CHECK(Problem::FormType::residual_block_count == 8);
mfem::Vector state(problem.StateSize());
state = 0.0;
const auto stateView = problem.GetManifest().stateView(state);
stateView.block(utils::blocks::density_field.mass_term) = 1.0;
state = 0.0;
const auto stateView = problem.GetManifest().stateView(state);
stateView.block(utils::blocks::density_field.mass_term) = 1.0;
stateView.block(utils::blocks::enthalpy_field.specific_term) = 1.0;
stateView.block(utils::blocks::fixed_total_mass_constraint.mass_normalization_term) = 0.25;
stateView.block(utils::blocks::fixed_total_mass_constraint.mass_normalization_term) = 0.25;
stateView.block(utils::blocks::fixed_angular_momentum_constraint.angular_velocity_term) = 0.4;
stateView.block(utils::blocks::fixed_central_density_phase.central_value_term) = 0.03;
stateView.block(utils::blocks::fixed_central_density_phase.central_value_term) = 0.03;
const auto report = problem.Prepare(state, make_dependencies());
CHECK(report.template specification<models::FixedCentralDensity>().constraint.DidAnyWork());
@@ -517,18 +548,18 @@ TEST_CASE(
CHECK(problem.IsPrepared());
CHECK(problem.StateSize() == problem.GetPhysicalOperator().Width() + 2);
REQUIRE(problem.GetManifest().constraints().size() == 4);
CHECK(problem.GetManifest().template specification<models::FixedAngularMomentum>().stableId ==
"FixedAngularMomentum");
CHECK(problem.GetManifest().template specification<models::FixedCentralDensity>().stableId ==
"FixedCentralDensity");
CHECK(
problem.GetManifest().template specification<models::FixedAngularMomentum>().stableId == "FixedAngularMomentum"
);
CHECK(
problem.GetManifest().template specification<models::FixedCentralDensity>().stableId == "FixedCentralDensity"
);
mfem::Vector residual;
problem.BuildResidual(residual);
REQUIRE(residual.Size() == problem.EquationSize());
const auto residualView = problem.GetManifest().residualView(residual);
CHECK(std::isfinite(
residualView.block(utils::blocks::fixed_angular_momentum_constraint.angular_velocity_term)(0)
));
CHECK(std::isfinite(residualView.block(utils::blocks::fixed_angular_momentum_constraint.angular_velocity_term)(0)));
CHECK(std::isfinite(residualView.block(utils::blocks::fixed_central_density_phase.central_value_term)(0)));
mfem::Vector direction(problem.StateSize());

View File

@@ -57,33 +57,26 @@ namespace {
blocks::type_list<>,
preconditioning::IdentityOperatorCharacteristics,
preconditioning::backend::Identity>;
using LifetimeModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
using LifetimeModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
mean_field::eos::Polytrope,
mean_field::surface::Isobaric,
mean_field::models::FixedTotalMass,
mean_field::models::FixedCentralDensity>>;
using LifetimeProblem = mean_field::equilibrium::StellarEquilibriumProblem<LifetimeModel>;
using LifetimeBlock = decltype(preconditioning::makePreconditioner(
std::declval<const LifetimeProblem &>()
));
using LifetimePrepared = preconditioning::PreparedStellarPreconditioner<
LifetimeProblem,
LifetimeBlock>;
using LifetimeProblem = mean_field::equilibrium::StellarEquilibriumProblem<LifetimeModel>;
using LifetimeBlock = decltype(preconditioning::makePreconditioner(std::declval<const LifetimeProblem &>()));
using LifetimePrepared = preconditioning::PreparedStellarPreconditioner<LifetimeProblem, LifetimeBlock>;
template <typename Problem, typename Block>
concept CanPrepareStellarPreconditioner = requires(const Problem &problem, Block block) {
preconditioning::prepare(problem, std::move(block));
};
concept CanPrepareStellarPreconditioner =
requires(const Problem &problem, Block block) { preconditioning::prepare(problem, std::move(block)); };
template <typename Problem, typename Block>
concept CanPrepareStellarPreconditionerFromTemporary = requires(Block block) {
preconditioning::prepare(std::declval<Problem &&>(), std::move(block));
};
concept CanPrepareStellarPreconditionerFromTemporary =
requires(Block block) { preconditioning::prepare(std::declval<Problem &&>(), std::move(block)); };
template <typename Problem, typename Block>
concept CanPrepareStellarPreconditionerFromConstTemporary = requires(Block block) {
preconditioning::prepare(std::declval<const Problem &&>(), std::move(block));
};
concept CanPrepareStellarPreconditionerFromConstTemporary =
requires(Block block) { preconditioning::prepare(std::declval<const Problem &&>(), std::move(block)); };
[[nodiscard]] blocks::form_layout<Form> makeUnevenLayout() {
return {
@@ -221,7 +214,7 @@ TEST_CASE(
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{mass}}),
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{centralDensity}})
);
auto problem = equilibrium::discretize(model, finiteElements);
auto problem = equilibrium::discretize(model, std::move(finiteElements));
auto projected = seed::makeProjectedEquilibriumState(problem, seed::LaneEmden({.radialSampleCount = 512}));
problem.Prepare(projected.values, makeDependencies(), zeroRotation());

View File

@@ -6,6 +6,7 @@
#include <numbers>
#include <stdexcept>
#include <type_traits>
#include <utility>
#include <catch2/catch_approx.hpp>
#include <catch2/catch_test_macros.hpp>
@@ -18,12 +19,12 @@ namespace material_surface_runtime_contract_test {
struct RegisteredAlternateEquationOfState final {
struct Parameters final { };
using ModelDefinition = mean_field::models::ConstitutiveLaw<
RegisteredAlternateEquationOfState,
"RegisteredAlternateMaterialSurfaceEquationOfState">;
using ModelDefinition = mean_field::models::
ConstitutiveLaw<RegisteredAlternateEquationOfState, "RegisteredAlternateMaterialSurfaceEquationOfState">;
using Relations = mean_field::eos::RelationCatalog<mean_field::eos::SpecificEnthalpyFromPressure>;
explicit RegisteredAlternateEquationOfState(Parameters) noexcept { }
explicit RegisteredAlternateEquationOfState(Parameters) noexcept {
}
[[nodiscard]] mean_field::dimensions::SpecificEnthalpyValue evaluate(
mean_field::eos::SpecificEnthalpyFromPressure,
@@ -40,7 +41,10 @@ namespace material_surface_runtime_contract_test {
using mfem::Operator::Operator;
void Mult(const mfem::Vector &, mfem::Vector &) const override;
void Mult(
const mfem::Vector &,
mfem::Vector &
) const override;
[[nodiscard]] const mean_field::operators::StellarEquilibriumLayout &GetLayout() const noexcept;
[[nodiscard]] mean_field::operators::PreparedStellarEquilibriumReport Prepare(
@@ -58,8 +62,7 @@ namespace material_surface_runtime_contract_test {
GetSurfaceConstraintOperator() const;
};
template <mean_field::model::StellarModelType Model>
class AlternateEquationOfStateRuntime final {
template <mean_field::model::StellarModelType Model> class AlternateEquationOfStateRuntime final {
public:
using Report = mean_field::operators::EmptySpecificationPreparationReport;
@@ -68,10 +71,17 @@ namespace material_surface_runtime_contract_test {
const mean_field::mapping::DomainMapper &,
AlternatePhysicalCore &,
const Model &
) noexcept { }
) noexcept {
}
template <typename StateView, typename Controls>
void ReadPhysicalControls(const StateView &, Controls &) noexcept { }
template <
typename StateView,
typename Controls>
void ReadPhysicalControls(
const StateView &,
Controls &
) noexcept {
}
template <typename StateView>
[[nodiscard]] Report PrepareAfterPhysical(
@@ -82,15 +92,18 @@ namespace material_surface_runtime_contract_test {
return {};
}
template <typename ResidualView>
void AddResidual(const ResidualView &) const noexcept { }
template <typename ResidualView> void AddResidual(const ResidualView &) const noexcept {
}
template <typename DirectionView, typename ActionView>
template <
typename DirectionView,
typename ActionView>
void AddJacobianAction(
const DirectionView &,
const ActionView &,
const AlternatePhysicalCore &
) const noexcept { }
) const noexcept {
}
[[nodiscard]] constexpr bool IsPrepared() const noexcept {
return true;
@@ -100,10 +113,9 @@ namespace material_surface_runtime_contract_test {
namespace mean_field::operators {
template <>
struct StellarEquilibriumCoreRuntime<
material_surface_runtime_contract_test::RegisteredAlternateEquationOfState> {
struct StellarEquilibriumCoreRuntime<material_surface_runtime_contract_test::RegisteredAlternateEquationOfState> {
static constexpr bool registered = true;
using CoreType = material_surface_runtime_contract_test::AlternatePhysicalCore;
using CoreType = material_surface_runtime_contract_test::AlternatePhysicalCore;
[[nodiscard]] static std::unique_ptr<CoreType> Make(
fem::FEM &,
@@ -134,7 +146,7 @@ namespace mean_field::preconditioning {
struct MaterialSurfaceEquationOfStateBackend<
material_surface_runtime_contract_test::RegisteredAlternateEquationOfState> {
static constexpr bool registered = true;
using CoreType = material_surface_runtime_contract_test::AlternatePhysicalCore;
using CoreType = material_surface_runtime_contract_test::AlternatePhysicalCore;
};
} // namespace mean_field::preconditioning
@@ -150,12 +162,12 @@ namespace {
mean_field::constraint::FixedCentralDensity>>;
using PolytropicProblem = mean_field::equilibrium::StellarEquilibriumProblem<PolytropicModel>;
using PolytropicMaterialSurfaceDescriptor = preconditioning::MaterialSurfaceDescriptorFor<PolytropicProblem>;
using RegisteredAlternateModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
using RegisteredAlternateModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
material_surface_runtime_contract_test::RegisteredAlternateEquationOfState,
mean_field::surface::Isobaric,
mean_field::integral::FixedTotalMass>>;
using RegisteredAlternateProblem = mean_field::equilibrium::StellarEquilibriumProblem<RegisteredAlternateModel>;
using MaterialSurfaceDiagonal = preconditioning::MaterialSurfaceBlock<
using MaterialSurfaceDiagonal = preconditioning::MaterialSurfaceBlock<
PolytropicMaterialSurfaceDescriptor,
backend::Diagonal,
backend::Diagonal,
@@ -174,10 +186,8 @@ namespace {
PolytropicMaterialSurfaceDescriptor,
preconditioning::ApproximateMaterialSurfaceLDU,
backend::FixedCycles>;
using RegisteredAlternateGravityComponent = preconditioning::GravityFieldBlock<
backend::Diagonal,
FixedCycleAMG,
preconditioning::GravityApproximateLDU>;
using RegisteredAlternateGravityComponent =
preconditioning::GravityFieldBlock<backend::Diagonal, FixedCycleAMG, preconditioning::GravityApproximateLDU>;
using RegisteredAlternateMaterialSurfaceDescriptor =
preconditioning::MaterialSurfaceDescriptorFor<RegisteredAlternateProblem>;
@@ -185,9 +195,8 @@ namespace {
struct DistinctPhysicalCore final { };
template <typename Problem>
concept CanMakeDefaultMaterialSurface = requires(const Problem &problem) {
preconditioning::materialSurfaceBlock(problem);
};
concept CanMakeDefaultMaterialSurface =
requires(const Problem &problem) { preconditioning::materialSurfaceBlock(problem); };
template <typename Problem>
concept CanPrepareDefaultMaterialSurface = requires(const Problem &problem) {
@@ -195,9 +204,8 @@ namespace {
};
template <typename Problem>
concept CanMakeDefaultStellarStructure = requires(const Problem &problem) {
preconditioning::stellarStructureBlock(problem);
};
concept CanMakeDefaultStellarStructure =
requires(const Problem &problem) { preconditioning::stellarStructureBlock(problem); };
class KnownCouplings final {
public:
@@ -315,41 +323,46 @@ TEST_CASE(
STATIC_CHECK(preconditioning::PreconditionerComponent<MaterialSurfaceDiagonal>);
STATIC_CHECK(preconditioning::PreconditionerComponent<MaterialSurfaceH1>);
STATIC_CHECK(preconditioning::MaterialSurfaceDescriptor<PolytropicMaterialSurfaceDescriptor>);
STATIC_CHECK(preconditioning::ImplementedMaterialSurfaceEquationOfState<
mean_field::eos::Polytrope>);
STATIC_CHECK(preconditioning::ImplementedMaterialSurfaceEquationOfState<mean_field::eos::Polytrope>);
STATIC_CHECK_FALSE(preconditioning::ImplementedMaterialSurfaceEquationOfState<int>);
STATIC_CHECK(preconditioning::ImplementedMaterialSurfaceDescriptor<PolytropicMaterialSurfaceDescriptor>);
STATIC_CHECK(
preconditioning::ExecutableMaterialSurfaceRuntimeFor<
mean_field::eos::Polytrope, mean_field::operators::PreparedStellarEquilibriumOperator>
);
STATIC_CHECK(
std::same_as<
typename preconditioning::MaterialSurfaceEquationOfStateBackend<mean_field::eos::Polytrope>::CoreType,
mean_field::operators::PreparedStellarEquilibriumOperator>
);
STATIC_CHECK(
preconditioning::MaterialSurfaceRuntimeFor<
PolytropicMaterialSurfaceDescriptor, mean_field::operators::PreparedStellarEquilibriumOperator>
);
STATIC_CHECK_FALSE(
preconditioning::ImplementedMaterialSurfaceEquationOfState<int>);
STATIC_CHECK(preconditioning::ImplementedMaterialSurfaceDescriptor<
PolytropicMaterialSurfaceDescriptor>);
STATIC_CHECK(preconditioning::ExecutableMaterialSurfaceRuntimeFor<
mean_field::eos::Polytrope,
mean_field::operators::PreparedStellarEquilibriumOperator>);
STATIC_CHECK(std::same_as<
typename preconditioning::MaterialSurfaceEquationOfStateBackend<
mean_field::eos::Polytrope>::CoreType,
mean_field::operators::PreparedStellarEquilibriumOperator>);
STATIC_CHECK(preconditioning::MaterialSurfaceRuntimeFor<
PolytropicMaterialSurfaceDescriptor,
mean_field::operators::PreparedStellarEquilibriumOperator>);
STATIC_CHECK_FALSE(preconditioning::MaterialSurfaceRuntimeFor<
PolytropicMaterialSurfaceDescriptor,
DistinctPhysicalCore>);
STATIC_CHECK(preconditioning::MaterialSurfaceDescriptor<
RegisteredAlternateMaterialSurfaceDescriptor>);
preconditioning::MaterialSurfaceRuntimeFor<PolytropicMaterialSurfaceDescriptor, DistinctPhysicalCore>
);
STATIC_CHECK(preconditioning::MaterialSurfaceDescriptor<RegisteredAlternateMaterialSurfaceDescriptor>);
STATIC_CHECK(mean_field::equilibrium::StellarEquilibriumModel<RegisteredAlternateModel>);
STATIC_CHECK(std::same_as<
typename RegisteredAlternateProblem::PhysicalCoreType,
material_surface_runtime_contract_test::AlternatePhysicalCore>);
STATIC_CHECK(preconditioning::ImplementedMaterialSurfaceEquationOfState<
material_surface_runtime_contract_test::RegisteredAlternateEquationOfState>);
STATIC_CHECK(preconditioning::ImplementedMaterialSurfaceDescriptor<
RegisteredAlternateMaterialSurfaceDescriptor>);
STATIC_CHECK_FALSE(preconditioning::ExecutableMaterialSurfaceRuntimeFor<
material_surface_runtime_contract_test::RegisteredAlternateEquationOfState,
material_surface_runtime_contract_test::AlternatePhysicalCore>);
STATIC_CHECK_FALSE(preconditioning::MaterialSurfaceRuntimeFor<
RegisteredAlternateMaterialSurfaceDescriptor,
material_surface_runtime_contract_test::AlternatePhysicalCore>);
STATIC_CHECK(
std::same_as<
typename RegisteredAlternateProblem::PhysicalCoreType,
material_surface_runtime_contract_test::AlternatePhysicalCore>
);
STATIC_CHECK(
preconditioning::ImplementedMaterialSurfaceEquationOfState<
material_surface_runtime_contract_test::RegisteredAlternateEquationOfState>
);
STATIC_CHECK(preconditioning::ImplementedMaterialSurfaceDescriptor<RegisteredAlternateMaterialSurfaceDescriptor>);
STATIC_CHECK_FALSE(
preconditioning::ExecutableMaterialSurfaceRuntimeFor<
material_surface_runtime_contract_test::RegisteredAlternateEquationOfState,
material_surface_runtime_contract_test::AlternatePhysicalCore>
);
STATIC_CHECK_FALSE(
preconditioning::MaterialSurfaceRuntimeFor<
RegisteredAlternateMaterialSurfaceDescriptor, material_surface_runtime_contract_test::AlternatePhysicalCore>
);
STATIC_CHECK(preconditioning::MaterialSurfacePreconditionerProblem<PolytropicProblem>);
STATIC_CHECK_FALSE(preconditioning::MaterialSurfacePreconditionerProblem<RegisteredAlternateProblem>);
STATIC_CHECK(CanMakeDefaultMaterialSurface<PolytropicProblem>);
@@ -357,10 +370,10 @@ TEST_CASE(
STATIC_CHECK(CanPrepareDefaultMaterialSurface<PolytropicProblem>);
STATIC_CHECK_FALSE(CanPrepareDefaultMaterialSurface<RegisteredAlternateProblem>);
STATIC_CHECK_FALSE(preconditioning::StellarStructurePreconditionerProblem<RegisteredAlternateProblem>);
STATIC_CHECK_FALSE(preconditioning::StellarStructurePreparableFor<
RegisteredAlternateProblem,
MaterialSurfaceDiagonal,
RegisteredAlternateGravityComponent>);
STATIC_CHECK_FALSE(
preconditioning::StellarStructurePreparableFor<
RegisteredAlternateProblem, MaterialSurfaceDiagonal, RegisteredAlternateGravityComponent>
);
STATIC_CHECK_FALSE(CanMakeDefaultStellarStructure<RegisteredAlternateProblem>);
STATIC_CHECK(
mean_field::material::CompiledThermodynamicEquations<typename PolytropicProblem::ThermodynamicEquationsType>
@@ -572,9 +585,10 @@ TEST_CASE(
const utils::Args arguments = test_utils::setup_args();
fem::FEM finiteElements = fem::setup_fem(arguments.mesh_file, arguments, 0);
REQUIRE(finiteElements.okay());
const mfem::ParFiniteElementSpace *surfaceDeformationSpace = finiteElements.surfaceDeformationFes.get();
constexpr double radius = utils::RADIUS;
constexpr double mass = utils::MASS;
constexpr double radius = utils::RADIUS;
constexpr double mass = utils::MASS;
const double polytropicConstant = 2.0 * utils::G * radius * radius / std::numbers::pi_v<double>;
const double centralDensity = std::numbers::pi_v<double> * mass / (4.0 * radius * radius * radius);
const auto stellarModel = model::StellarModel(
@@ -583,7 +597,7 @@ TEST_CASE(
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{mass}}),
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{centralDensity}})
);
auto problem = equilibrium::discretize(stellarModel, finiteElements);
auto problem = equilibrium::discretize(stellarModel, std::move(finiteElements));
auto projected = seed::makeProjectedEquilibriumState(problem, seed::LaneEmden({.radialSampleCount = 512}));
const auto rotation = zeroRotation();
problem.Prepare(projected.values, makeDependencies(), rotation);
@@ -711,7 +725,7 @@ TEST_CASE(
CHECK(surfaceFit.relativeGramDeterminant > 1.0e-12);
CHECK(surfaceFit.normalEquations.targetTarget > 0.0);
CHECK(frequencyAware.GetSurfaceInverse().Height() == physical.GetDomainDeformation().parameterCount());
CHECK(frequencyAware.GetSurfaceSurrogateMatrix().Height() == finiteElements.surfaceDeformationFes->GetTrueVSize());
CHECK(frequencyAware.GetSurfaceSurrogateMatrix().Height() == surfaceDeformationSpace->GetTrueVSize());
CHECK(frequencyAware.GetSurfaceBackend().GetStatistics().setups == 1);
CHECK(frequencyAware.GetStatistics().surfaceJacobianProbes == 4);
CHECK(frequencyAware.GetStatistics().surfaceH1Assemblies == 3);

File diff suppressed because it is too large Load Diff

View File

@@ -122,7 +122,7 @@ namespace unsupported_physical_preconditioner_test {
mean_field::dimensions::SpecificEnthalpyValue target;
};
using TargetValue = mean_field::dimensions::SpecificEnthalpyValue;
using TargetValue = mean_field::dimensions::SpecificEnthalpyValue;
using ScalarDescription = mean_field::stellar::ScalarConstraint<
mean_field::dimensions::quantity::SpecificEnthalpy,
mean_field::dimensions::quantity::Dimensionless,
@@ -134,18 +134,13 @@ namespace unsupported_physical_preconditioner_test {
using ModelDefinition = mean_field::constraint::ScalarPhaseCondition<
Constraint,
"UnsupportedPhysicalPreconditionerEdge",
mean_field::stellar::Reads<
mean_field::stellar::state::SpecificEnthalpy,
mean_field::stellar::state::OwnGeneratedCoordinate>,
mean_field::stellar::Changes<
mean_field::stellar::equation::PoissonEquation>,
mean_field::stellar::
Reads<mean_field::stellar::state::SpecificEnthalpy, mean_field::stellar::state::OwnGeneratedCoordinate>,
mean_field::stellar::Changes<mean_field::stellar::equation::PoissonEquation>,
ScalarDescription>;
using EquilibriumPhysics =
mean_field::operators::LocalSpecificationEquilibriumPhysics<
PreparedConstraint>;
using EquilibriumPhysics = mean_field::operators::LocalSpecificationEquilibriumPhysics<PreparedConstraint>;
explicit constexpr Constraint(const Parameters parameters) noexcept
: m_target(parameters.target) {
explicit constexpr Constraint(const Parameters parameters) noexcept : m_target(parameters.target) {
}
[[nodiscard]] constexpr TargetValue target() const noexcept {
@@ -167,8 +162,7 @@ namespace unsupported_physical_preconditioner_test {
explicit PreparedConstraint(const Constraint &) noexcept {
}
template <typename StateView>
[[nodiscard]] Report PrepareAfterPhysical(const StateView &) noexcept {
template <typename StateView> [[nodiscard]] Report PrepareAfterPhysical(const StateView &) noexcept {
m_isPrepared = true;
return {};
}
@@ -189,7 +183,9 @@ namespace unsupported_physical_preconditioner_test {
return mean_field::stellar::structuralZero;
}
template <typename Direction, typename Row>
template <
typename Direction,
typename Row>
[[nodiscard]] mean_field::stellar::StructuralZero AddJacobianAction(
mean_field::stellar::Derivative<
mean_field::stellar::equation::OwnConstraint,
@@ -200,7 +196,9 @@ namespace unsupported_physical_preconditioner_test {
return mean_field::stellar::zeroDerivative;
}
template <typename Direction, typename Row>
template <
typename Direction,
typename Row>
[[nodiscard]] mean_field::stellar::StructuralZero AddJacobianAction(
mean_field::stellar::Derivative<
mean_field::stellar::equation::OwnConstraint,
@@ -211,7 +209,9 @@ namespace unsupported_physical_preconditioner_test {
return mean_field::stellar::zeroDerivative;
}
template <typename Direction, typename Row>
template <
typename Direction,
typename Row>
[[nodiscard]] mean_field::stellar::StructuralZero AddJacobianAction(
mean_field::stellar::Derivative<
mean_field::stellar::equation::PoissonEquation,
@@ -222,7 +222,9 @@ namespace unsupported_physical_preconditioner_test {
return mean_field::stellar::zeroDerivative;
}
template <typename Direction, typename Row>
template <
typename Direction,
typename Row>
[[nodiscard]] mean_field::stellar::StructuralZero AddJacobianAction(
mean_field::stellar::Derivative<
mean_field::stellar::equation::PoissonEquation,
@@ -277,42 +279,40 @@ template <> struct mean_field::preconditioning::StellarEquilibriumProblemTraits<
};
namespace {
namespace blocks = mean_field::utils::blocks;
namespace preconditioning = mean_field::preconditioning;
namespace blocks = mean_field::utils::blocks;
namespace preconditioning = mean_field::preconditioning;
using ModelWithoutPhase = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
using ModelWithoutPhase = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
mean_field::eos::Polytrope,
mean_field::surface::Isobaric,
mean_field::models::FixedTotalMass>>;
using CentralDensityModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
using CentralDensityModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
mean_field::eos::Polytrope,
mean_field::surface::Isobaric,
mean_field::models::FixedTotalMass,
mean_field::models::FixedCentralDensity>>;
using AngularMomentumModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
using AngularMomentumModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
mean_field::eos::Polytrope,
mean_field::surface::Isobaric,
mean_field::models::FixedTotalMass,
mean_field::models::FixedAngularMomentum>>;
using ProvenZeroPhysicalEdgeModel = mean_field::model::StellarModel<
mean_field::models::SpecificationSet<
mean_field::eos::Polytrope,
mean_field::surface::Isobaric,
mean_field::models::FixedTotalMass,
unsupported_physical_preconditioner_test::Constraint>>;
using ProvenZeroPhysicalEdgeModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
mean_field::eos::Polytrope,
mean_field::surface::Isobaric,
mean_field::models::FixedTotalMass,
unsupported_physical_preconditioner_test::Constraint>>;
using ProblemWithoutPhase = mean_field::equilibrium::StellarEquilibriumProblem<ModelWithoutPhase>;
using CentralDensityProblem = mean_field::equilibrium::StellarEquilibriumProblem<CentralDensityModel>;
using AngularMomentumProblem = mean_field::equilibrium::StellarEquilibriumProblem<AngularMomentumModel>;
using ProblemWithoutPhase = mean_field::equilibrium::StellarEquilibriumProblem<ModelWithoutPhase>;
using CentralDensityProblem = mean_field::equilibrium::StellarEquilibriumProblem<CentralDensityModel>;
using AngularMomentumProblem = mean_field::equilibrium::StellarEquilibriumProblem<AngularMomentumModel>;
using ProvenZeroPhysicalEdgeProblem =
mean_field::equilibrium::StellarEquilibriumProblem<ProvenZeroPhysicalEdgeModel>;
using PlanWithoutPhase = preconditioning::IdentityPreconditionerPlanFor<ProblemWithoutPhase>;
using CentralDensityPlan = preconditioning::IdentityPreconditionerPlanFor<CentralDensityProblem>;
using PlanWithoutPhase = preconditioning::IdentityPreconditionerPlanFor<ProblemWithoutPhase>;
using CentralDensityPlan = preconditioning::IdentityPreconditionerPlanFor<CentralDensityProblem>;
template <typename Problem>
concept CanMakeDefaultStellarStructureBlock = requires(const Problem &problem) {
preconditioning::stellarStructureBlock(problem);
};
concept CanMakeDefaultStellarStructureBlock =
requires(const Problem &problem) { preconditioning::stellarStructureBlock(problem); };
using RefreshingDensityIdentity = preconditioning::ComponentDeclaration<
blocks::type_list<blocks::density::mass::value>,
@@ -367,67 +367,51 @@ TEST_CASE(
"Default Stellar Structure Availability Distinguishes Proven Zeros From Unhandled Physical Edges",
"[preconditioning][stellar_structure][type_contract][compiler]"
) {
using BaseCompilation =
mean_field::operators::CompiledStellarEquilibriumSystem<ModelWithoutPhase>;
using BaseSupport =
preconditioning::DefaultStellarStructurePhysicalTopologySupport<
ModelWithoutPhase>;
using BaseCompilation = mean_field::operators::CompiledStellarEquilibriumSystem<ModelWithoutPhase>;
using BaseSupport = preconditioning::DefaultStellarStructurePhysicalTopologySupport<ModelWithoutPhase>;
using ProvenZeroSupport =
preconditioning::DefaultStellarStructurePhysicalTopologySupport<
ProvenZeroPhysicalEdgeModel>;
using TrustedFixedMassEdge =
mean_field::operators::StellarEquilibriumJacobianCoupling<
blocks::enthalpy::specific::residual,
blocks::density::mass::value>;
using ProvenZeroPoissonEnthalpyEdge =
mean_field::operators::StellarEquilibriumJacobianCoupling<
blocks::gravity::poisson::residual,
blocks::enthalpy::specific::value>;
preconditioning::DefaultStellarStructurePhysicalTopologySupport<ProvenZeroPhysicalEdgeModel>;
using TrustedFixedMassEdge = mean_field::operators::StellarEquilibriumJacobianCoupling<
blocks::enthalpy::specific::residual, blocks::density::mass::value>;
using ProvenZeroPoissonEnthalpyEdge = mean_field::operators::StellarEquilibriumJacobianCoupling<
blocks::gravity::poisson::residual, blocks::enthalpy::specific::value>;
// FixedTotalMass contributes h <- rho outside the generic five-field base
// graph. It remains supported because that specification is explicitly
// embedded in the trusted numerical core, not because of a model-pack
// special case.
STATIC_CHECK_FALSE(mean_field::utils::blocks::contains_type_v<
TrustedFixedMassEdge,
typename BaseCompilation::BaseJacobianCouplings>);
STATIC_CHECK(mean_field::utils::blocks::contains_type_v<
TrustedFixedMassEdge,
typename BaseCompilation::ContributionJacobianCouplings>);
STATIC_CHECK_FALSE(
mean_field::utils::blocks::contains_type_v<
TrustedFixedMassEdge, typename BaseCompilation::BaseJacobianCouplings>
);
STATIC_CHECK(
mean_field::utils::blocks::contains_type_v<
TrustedFixedMassEdge, typename BaseCompilation::ContributionJacobianCouplings>
);
STATIC_CHECK(BaseSupport::UnsupportedCouplings::size == 0);
STATIC_CHECK(preconditioning::DefaultStellarStructurePhysicalTopologySupportedFor<
ModelWithoutPhase>);
STATIC_CHECK(preconditioning::DefaultStellarStructurePhysicalTopologySupportedFor<
CentralDensityModel>);
STATIC_CHECK(preconditioning::DefaultStellarStructurePhysicalTopologySupportedFor<
AngularMomentumModel>);
STATIC_CHECK(preconditioning::StellarStructurePreconditionerProblem<
ProblemWithoutPhase>);
STATIC_CHECK(preconditioning::DefaultStellarStructurePhysicalTopologySupportedFor<ModelWithoutPhase>);
STATIC_CHECK(preconditioning::DefaultStellarStructurePhysicalTopologySupportedFor<CentralDensityModel>);
STATIC_CHECK(preconditioning::DefaultStellarStructurePhysicalTopologySupportedFor<AngularMomentumModel>);
STATIC_CHECK(preconditioning::StellarStructurePreconditionerProblem<ProblemWithoutPhase>);
STATIC_CHECK(CanMakeDefaultStellarStructureBlock<ProblemWithoutPhase>);
STATIC_CHECK(preconditioning::DefaultStellarPreconditionerAvailableFor<
ProblemWithoutPhase>);
STATIC_CHECK(preconditioning::DefaultStellarPreconditionerAvailableFor<ProblemWithoutPhase>);
// The mock's novel Poisson <- enthalpy edge is absent from the structure
// backend, but its exact nested provider returns StructuralZero. That is
// a compile-time proof that no preconditioner term is missing; generated-
// coordinate edges are handled independently by the inferred border.
STATIC_CHECK(mean_field::equilibrium::StellarEquilibriumModel<
ProvenZeroPhysicalEdgeModel>);
STATIC_CHECK(mean_field::equilibrium::DiscretizedStellarEquilibriumProblem<
ProvenZeroPhysicalEdgeProblem>);
STATIC_CHECK(mean_field::equilibrium::StellarEquilibriumModel<ProvenZeroPhysicalEdgeModel>);
STATIC_CHECK(mean_field::equilibrium::DiscretizedStellarEquilibriumProblem<ProvenZeroPhysicalEdgeProblem>);
STATIC_CHECK(ProvenZeroSupport::UnsupportedCouplings::size == 0);
STATIC_CHECK(mean_field::utils::blocks::contains_type_v<
ProvenZeroPoissonEnthalpyEdge,
typename mean_field::operators::CompiledStellarEquilibriumSystem<
ProvenZeroPhysicalEdgeModel>::ContributionJacobianCouplings>);
STATIC_CHECK(preconditioning::DefaultStellarStructurePhysicalTopologySupportedFor<
ProvenZeroPhysicalEdgeModel>);
STATIC_CHECK(preconditioning::StellarStructurePreconditionerProblem<
ProvenZeroPhysicalEdgeProblem>);
STATIC_CHECK(CanMakeDefaultStellarStructureBlock<
ProvenZeroPhysicalEdgeProblem>);
STATIC_CHECK(preconditioning::DefaultStellarPreconditionerAvailableFor<
ProvenZeroPhysicalEdgeProblem>);
STATIC_CHECK(
mean_field::utils::blocks::contains_type_v<
ProvenZeroPoissonEnthalpyEdge, typename mean_field::operators::CompiledStellarEquilibriumSystem<
ProvenZeroPhysicalEdgeModel>::ContributionJacobianCouplings>
);
STATIC_CHECK(preconditioning::DefaultStellarStructurePhysicalTopologySupportedFor<ProvenZeroPhysicalEdgeModel>);
STATIC_CHECK(preconditioning::StellarStructurePreconditionerProblem<ProvenZeroPhysicalEdgeProblem>);
STATIC_CHECK(CanMakeDefaultStellarStructureBlock<ProvenZeroPhysicalEdgeProblem>);
STATIC_CHECK(preconditioning::DefaultStellarPreconditionerAvailableFor<ProvenZeroPhysicalEdgeProblem>);
}
TEST_CASE(

View File

@@ -108,12 +108,12 @@ namespace {
return action;
}
using PolytropicModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
using PolytropicModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
mean_field::eos::Polytrope,
mean_field::surface::Isobaric,
mean_field::integral::FixedTotalMass,
mean_field::constraint::FixedCentralDensity>>;
using PolytropicProblem = mean_field::equilibrium::StellarEquilibriumProblem<PolytropicModel>;
using PolytropicProblem = mean_field::equilibrium::StellarEquilibriumProblem<PolytropicModel>;
using PolytropicMaterialSurfaceDescriptor = preconditioning::MaterialSurfaceDescriptorFor<PolytropicProblem>;
using MaterialComponent =
decltype(preconditioning::materialSurfaceBlock(std::declval<const PolytropicProblem &>()));
@@ -181,20 +181,21 @@ TEST_CASE(
preconditioning::Coupling<blocks::enthalpy::specific::residual, blocks::gravity::poisson::value>>;
STATIC_CHECK(preconditioning::PreconditionerComponent<PolytropicStructure>);
STATIC_CHECK(preconditioning::ExecutableStellarStructureRuntimeFor<
mean_field::operators::PreparedStellarEquilibriumOperator>);
STATIC_CHECK(
preconditioning::ExecutableStellarStructureRuntimeFor<mean_field::operators::PreparedStellarEquilibriumOperator>
);
STATIC_CHECK_FALSE(preconditioning::ExecutableStellarStructureRuntimeFor<DistinctPhysicalCore>);
STATIC_CHECK(preconditioning::StellarStructureRuntimeFor<
PolytropicMaterialSurfaceDescriptor,
mean_field::operators::PreparedStellarEquilibriumOperator>);
STATIC_CHECK_FALSE(preconditioning::StellarStructureRuntimeFor<
PolytropicMaterialSurfaceDescriptor,
DistinctPhysicalCore>);
STATIC_CHECK(
preconditioning::StellarStructureRuntimeFor<
PolytropicMaterialSurfaceDescriptor, mean_field::operators::PreparedStellarEquilibriumOperator>
);
STATIC_CHECK_FALSE(
preconditioning::StellarStructureRuntimeFor<PolytropicMaterialSurfaceDescriptor, DistinctPhysicalCore>
);
STATIC_CHECK(preconditioning::StellarStructurePreconditionerProblem<PolytropicProblem>);
STATIC_CHECK(preconditioning::StellarStructurePreparableFor<
PolytropicProblem,
MaterialComponent,
GravityComponent>);
STATIC_CHECK(
preconditioning::StellarStructurePreparableFor<PolytropicProblem, MaterialComponent, GravityComponent>
);
STATIC_CHECK(CanPrepareDefaultStellarStructure<PolytropicProblem>);
STATIC_CHECK(std::same_as<typename PolytropicStructure::MaterialToGravityCouplings, ExpectedMaterialToGravity>);
STATIC_CHECK(std::same_as<typename PolytropicStructure::GravityToMaterialCouplings, ExpectedGravityToMaterial>);
@@ -268,7 +269,7 @@ TEST_CASE(
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{mass}}),
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{centralDensity}})
);
auto problem = equilibrium::discretize(stellarModel, finiteElements);
auto problem = equilibrium::discretize(stellarModel, std::move(finiteElements));
auto projected = seed::makeProjectedEquilibriumState(problem, seed::LaneEmden({.radialSampleCount = 512}));
problem.Prepare(projected.values, makeDependencies(), zeroRotation());
const auto &physical = problem.GetPreparedOperator().GetPhysicalOperator();

View File

@@ -4,6 +4,7 @@
#include <numbers>
#include <stdexcept>
#include <type_traits>
#include <utility>
#include <catch2/catch_approx.hpp>
#include <catch2/catch_test_macros.hpp>
@@ -14,8 +15,8 @@ import test_helpers;
namespace {
template <typename... Specifications>
using ProjectionModelWith = mean_field::model::StellarModel<
mean_field::models::SpecificationSet<Specifications...>>;
using ProjectionModelWith =
mean_field::model::StellarModel<mean_field::models::SpecificationSet<Specifications...>>;
class UnregisteredProjectionConstraint final {
public:
@@ -45,11 +46,10 @@ namespace {
};
struct IncompleteProjectionPhysics final {
static constexpr bool registered = true;
static constexpr bool providesRadialMass = false;
static constexpr bool registered = true;
static constexpr bool providesRadialMass = false;
template <typename Model>
static constexpr bool supports = true;
template <typename Model> static constexpr bool supports = true;
};
class IncompleteProjectionConstraint final {
@@ -69,9 +69,8 @@ namespace {
class UnregisteredProjectionEquationOfState final {
public:
struct Parameters final { };
using ModelDefinition = mean_field::eos::ConstitutiveLaw<
UnregisteredProjectionEquationOfState,
"UnregisteredProjectionEquationOfState">;
using ModelDefinition = mean_field::eos::
ConstitutiveLaw<UnregisteredProjectionEquationOfState, "UnregisteredProjectionEquationOfState">;
explicit constexpr UnregisteredProjectionEquationOfState(Parameters) noexcept {
}
@@ -80,27 +79,27 @@ namespace {
class UnregisteredProjectionSurface final {
public:
struct Parameters final { };
using ModelDefinition = mean_field::surface::BoundaryCondition<
UnregisteredProjectionSurface,
"UnregisteredProjectionSurface">;
using ModelDefinition =
mean_field::surface::BoundaryCondition<UnregisteredProjectionSurface, "UnregisteredProjectionSurface">;
explicit constexpr UnregisteredProjectionSurface(Parameters) noexcept {
}
};
struct SecondRadialMassProjectionPhysics final {
static constexpr bool registered = true;
static constexpr bool providesRadialMass = true;
static constexpr bool registered = true;
static constexpr bool providesRadialMass = true;
template <typename Model>
static constexpr bool supports = true;
template <typename Model> static constexpr bool supports = true;
template <typename Specification>
[[nodiscard]] static mean_field::dimensions::MassValue targetMass(const Specification &specification) {
return specification.targetMass();
}
template <typename Specification, typename Model>
template <
typename Specification,
typename Model>
static void validate(
const Specification &,
const Model &,
@@ -109,7 +108,9 @@ namespace {
) noexcept {
}
template <typename Specification, typename Model>
template <
typename Specification,
typename Model>
static void initialize(
const Specification &,
const Model &,
@@ -128,9 +129,8 @@ namespace {
struct Parameters final {
mean_field::dimensions::MassValue mass;
};
using ModelDefinition = mean_field::integral::FixedWithMultiplier<
SecondRadialMassConstraint,
"SecondRadialMassConstraint">;
using ModelDefinition =
mean_field::integral::FixedWithMultiplier<SecondRadialMassConstraint, "SecondRadialMassConstraint">;
using RadialProjection = mean_field::seed::projection::Use<SecondRadialMassProjectionPhysics>;
explicit constexpr SecondRadialMassConstraint(Parameters parameters) noexcept : m_mass(parameters.mass) {
@@ -178,46 +178,24 @@ TEST_CASE(
tags::stellar_seed_projection_type_contract
) {
using namespace mean_field;
using BaseModel = ProjectionModelWith<eos::Polytrope, surface::Isobaric, integral::FixedTotalMass>;
using BaseModel = ProjectionModelWith<eos::Polytrope, surface::Isobaric, integral::FixedTotalMass>;
using CentralModel = ProjectionModelWith<
eos::Polytrope,
surface::Isobaric,
integral::FixedTotalMass,
constraint::FixedCentralDensity>;
eos::Polytrope, surface::Isobaric, integral::FixedTotalMass, constraint::FixedCentralDensity>;
using AngularModel = ProjectionModelWith<
eos::Polytrope,
surface::Isobaric,
integral::FixedTotalMass,
integral::FixedAngularMomentum>;
eos::Polytrope, surface::Isobaric, integral::FixedTotalMass, integral::FixedAngularMomentum>;
using ExplicitExtensionModel = ProjectionModelWith<
eos::Polytrope,
surface::Isobaric,
integral::FixedTotalMass,
ExplicitNoChangeProjectionConstraint>;
eos::Polytrope, surface::Isobaric, integral::FixedTotalMass, ExplicitNoChangeProjectionConstraint>;
using MissingConstraintRuleModel = ProjectionModelWith<
eos::Polytrope,
surface::Isobaric,
integral::FixedTotalMass,
UnregisteredProjectionConstraint>;
eos::Polytrope, surface::Isobaric, integral::FixedTotalMass, UnregisteredProjectionConstraint>;
using IncompleteConstraintRuleModel = ProjectionModelWith<
eos::Polytrope,
surface::Isobaric,
integral::FixedTotalMass,
IncompleteProjectionConstraint>;
using MissingEquationOfStateRuleModel = ProjectionModelWith<
UnregisteredProjectionEquationOfState,
surface::Isobaric,
integral::FixedTotalMass>;
using MissingSurfaceRuleModel = ProjectionModelWith<
eos::Polytrope,
UnregisteredProjectionSurface,
integral::FixedTotalMass>;
eos::Polytrope, surface::Isobaric, integral::FixedTotalMass, IncompleteProjectionConstraint>;
using MissingEquationOfStateRuleModel =
ProjectionModelWith<UnregisteredProjectionEquationOfState, surface::Isobaric, integral::FixedTotalMass>;
using MissingSurfaceRuleModel =
ProjectionModelWith<eos::Polytrope, UnregisteredProjectionSurface, integral::FixedTotalMass>;
using MissingMassProviderModel = ProjectionModelWith<eos::Polytrope, surface::Isobaric>;
using AmbiguousMassProviderModel = ProjectionModelWith<
eos::Polytrope,
surface::Isobaric,
integral::FixedTotalMass,
SecondRadialMassConstraint>;
using AmbiguousMassProviderModel =
ProjectionModelWith<eos::Polytrope, surface::Isobaric, integral::FixedTotalMass, SecondRadialMassConstraint>;
STATIC_CHECK(seed::RadialProfileProjectableModel<BaseModel>);
STATIC_CHECK(seed::RadialProfileProjectableModel<CentralModel>);
@@ -272,7 +250,7 @@ TEST_CASE(
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{targetMass}}),
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{centralDensity}})
);
auto problem = equilibrium::discretize(stellarModel, finiteElementModel);
auto problem = equilibrium::discretize(stellarModel, std::move(finiteElementModel));
STATIC_CHECK(seed::RadialSeedStrategyFor<seed::LaneEmden, decltype(stellarModel)>);
STATIC_CHECK(
@@ -346,7 +324,7 @@ TEST_CASE(
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{1.0}}),
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{1.0}})
);
auto problem = equilibrium::discretize(stellarModel, finiteElementModel);
auto problem = equilibrium::discretize(stellarModel, std::move(finiteElementModel));
const seed::RadialProfile mismatchedProfile =
seed::generateRadialProfile(problem.GetStellarModel(), seed::LaneEmden({.radialSampleCount = 64}));
@@ -371,7 +349,7 @@ TEST_CASE(
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{1.0}}),
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{centralDensity}})
);
auto problem = equilibrium::discretize(stellarModel, finiteElementModel);
auto problem = equilibrium::discretize(stellarModel, std::move(finiteElementModel));
const seed::RadialProfile profile =
seed::generateRadialProfile(problem.GetStellarModel(), seed::LaneEmden({.radialSampleCount = 64}));

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,319 @@
module;
#include <algorithm>
#include <cmath>
#include <cstddef>
#include <memory>
#include <numbers>
#include <stdexcept>
#include <utility>
#include <catch2/catch_test_macros.hpp>
#include <mfem.hpp>
#include <mpi.h>
module mean_field;
import :solver.stellar_equilibrium;
namespace solver_internal_architecture_test {
struct LifetimeProbe final {
const void *problemIdentity{nullptr};
bool backendDestroyed{false};
bool dependenciesAliveAtBackendDestruction{false};
};
struct InspectingBackend final : mean_field::solver::LinearBackendConfigurationTag {
static constexpr mean_field::preconditioning::ApplicationContract supportedPreconditionerContract =
mean_field::preconditioning::ApplicationContract::flexible;
std::shared_ptr<LifetimeProbe> probe;
explicit InspectingBackend(std::shared_ptr<LifetimeProbe> lifetimeProbe = nullptr)
: probe(std::move(lifetimeProbe)) {
}
};
struct ThrowingBackend final : mean_field::solver::LinearBackendConfigurationTag {
static constexpr mean_field::preconditioning::ApplicationContract supportedPreconditionerContract =
mean_field::preconditioning::ApplicationContract::flexible;
};
struct ZeroMetric final { };
[[nodiscard]] mean_field::solver::nonlinear::MetricEvaluation getMetric(
const ZeroMetric &,
const mfem::Vector &,
MPI_Comm
) {
return {.residualNorm = 0.0, .merit = 0.0};
}
template <typename Operator, typename Preconditioner> class PreparedBackend final {
public:
PreparedBackend(
const Operator &operation,
Preconditioner &preconditioner,
const MPI_Comm communicator,
std::shared_ptr<LifetimeProbe> probe
)
: m_operation(&operation),
m_preconditioner(&preconditioner),
m_communicator(communicator),
m_rightHandSide(operation.Height()),
m_correction(operation.Width()),
m_probe(std::move(probe)) {
m_rightHandSide = 0.0;
m_correction = 0.0;
if (m_probe != nullptr) {
m_probe->problemIdentity = std::addressof(operation.GetProblem());
}
}
PreparedBackend(const PreparedBackend &) = delete;
PreparedBackend &operator=(const PreparedBackend &) = delete;
PreparedBackend(PreparedBackend &&) = delete;
PreparedBackend &operator=(PreparedBackend &&) = delete;
~PreparedBackend() {
if (m_probe != nullptr) {
m_probe->backendDestroyed = true;
m_probe->dependenciesAliveAtBackendDestruction =
m_operation != nullptr && m_preconditioner != nullptr && m_preconditioner->IsCurrent();
}
}
[[nodiscard]] const Operator &GetOperator() const noexcept {
return *m_operation;
}
[[nodiscard]] const Preconditioner &GetPreconditioner() const noexcept {
return *m_preconditioner;
}
[[nodiscard]] MPI_Comm GetCommunicator() const noexcept {
return m_communicator;
}
[[nodiscard]] bool IsReady() const noexcept {
return m_operation != nullptr && m_preconditioner != nullptr && m_communicator != MPI_COMM_NULL &&
m_rightHandSide.Size() == m_operation->Height() && m_correction.Size() == m_operation->Width();
}
[[nodiscard]] int RightHandSideSize() const noexcept {
return m_rightHandSide.Size();
}
[[nodiscard]] int CorrectionSize() const noexcept {
return m_correction.Size();
}
[[nodiscard]] mean_field::solver::LinearSolveReport Solve(
const mfem::Vector &rightHandSide,
mfem::Vector &correction,
const mean_field::solver::LinearSolveControl &control
) {
control.Validate();
if (rightHandSide.Size() != RightHandSideSize() || correction.Size() != CorrectionSize()) {
throw std::invalid_argument("The internal test backend requires preallocated compatible vectors.");
}
m_rightHandSide = rightHandSide;
m_correction = 0.0;
correction = m_correction;
return {
.status = mean_field::solver::LinearSolveStatus::converged,
.control = control,
.initialResidualNorm = rightHandSide.Norml2(),
.reportedResidualNorm = 0.0,
.trueResidualNorm = 0.0
};
}
private:
const Operator *m_operation;
Preconditioner *m_preconditioner;
MPI_Comm m_communicator;
mfem::Vector m_rightHandSide;
mfem::Vector m_correction;
std::shared_ptr<LifetimeProbe> m_probe;
};
template <
typename Operator,
typename Preconditioner>
[[nodiscard]] auto prepareLinearBackend(
InspectingBackend configuration,
const Operator &operation,
Preconditioner &preconditioner,
const MPI_Comm communicator
) {
return PreparedBackend<Operator, Preconditioner>{
operation, preconditioner, communicator, std::move(configuration.probe)
};
}
template <
typename Operator,
typename Preconditioner>
[[nodiscard]] auto prepareLinearBackend(
ThrowingBackend,
const Operator &,
Preconditioner &,
MPI_Comm
)
-> PreparedBackend<
Operator,
Preconditioner> {
throw std::runtime_error("The internal test backend rejected restart preparation.");
}
[[nodiscard]] mean_field::fem::FEM makeFiniteElements() {
mean_field::utils::Args arguments;
arguments.mesh_file = "sandbox.smesh";
arguments.p.rtol = 1.0e-12;
arguments.p.atol = 1.0e-12;
return mean_field::fem::setup_fem(arguments.mesh_file, arguments, 0);
}
[[nodiscard]] double matchingCentralDensity() {
constexpr double stellarRadius = mean_field::utils::RADIUS;
constexpr double targetMass = mean_field::utils::MASS;
return std::numbers::pi_v<double> * targetMass / (4.0 * stellarRadius * stellarRadius * stellarRadius);
}
[[nodiscard]] auto makeModel() {
using namespace mean_field;
constexpr double stellarRadius = utils::RADIUS;
constexpr double targetMass = utils::MASS;
const double polytropicConstant = 2.0 * utils::G * stellarRadius * stellarRadius / std::numbers::pi_v<double>;
return model::StellarModel(
eos::Polytrope({.n = 1.0, .K = polytropicConstant}),
surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}),
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{targetMass}}),
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{matchingCentralDensity()}})
);
}
[[nodiscard]] auto makeGeneratedRotationModel() {
using namespace mean_field;
constexpr double stellarRadius = utils::RADIUS;
constexpr double targetMass = utils::MASS;
const double polytropicConstant = 2.0 * utils::G * stellarRadius * stellarRadius / std::numbers::pi_v<double>;
return model::StellarModel(
eos::Polytrope({.n = 1.0, .K = polytropicConstant}),
surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}),
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{targetMass}}),
integral::FixedAngularMomentum({.Jtotal = dimensions::AngularMomentumValue{0.05}}),
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{matchingCentralDensity()}})
);
}
void checkZeroRotation(const mean_field::physics::RigidRotation &rotation) {
REQUIRE(rotation.angular_velocity().Size() == 3);
REQUIRE(rotation.center().Size() == 3);
for (int component = 0; component < 3; ++component) {
CHECK(rotation.angular_velocity()(component) == 0.0);
CHECK(rotation.center()(component) == 0.0);
}
}
} // namespace solver_internal_architecture_test
TEST_CASE(
"Report Views Retain Context-Owned Accepted State After Solver Destruction",
"[solver][architecture][ownership][report][internal]"
) {
using namespace mean_field;
using namespace solver_internal_architecture_test;
auto finiteElements = makeFiniteElements();
REQUIRE(finiteElements.okay());
const MPI_Comm expectedCommunicator = finiteElements.mesh->GetComm();
auto probe = std::make_shared<LifetimeProbe>();
{
auto context = solver::makeContext(
makeModel(), equilibrium::StellarDiscretization{std::move(finiteElements)},
preconditioning::makePreconditioner(), InspectingBackend{probe}
);
REQUIRE(context.isReady());
REQUIRE(probe->problemIdentity != nullptr);
auto report = [&] {
auto borrowingSolver =
solver::make(context, solver::nonlinear::Newton(solver::nonlinear::NewtonOptions{}, ZeroMetric{}));
return borrowingSolver.evaluate();
}();
CHECK_FALSE(context.hasActiveSolver());
CHECK_FALSE(probe->backendDestroyed);
REQUIRE(report.converged());
auto structure = report.structureView();
REQUIRE(structure.valid());
CHECK(
structure.model().template specification<constraint::FixedCentralDensity>().targetDensity() ==
dimensions::DensityValue{matchingCentralDensity()}
);
const auto state = structure.state();
REQUIRE_FALSE(state.empty());
CHECK(std::ranges::all_of(state, [](const mfem::real_t value) { return std::isfinite(value); }));
REQUIRE_FALSE(structure.stateDescriptors().empty());
REQUIRE_FALSE(structure.stateBlock(utils::blocks::density_field.mass_term).empty());
int communicatorComparison = MPI_UNEQUAL;
REQUIRE(
MPI_Comm_compare(structure.communicator(), expectedCommunicator, &communicatorComparison) == MPI_SUCCESS
);
CHECK((communicatorComparison == MPI_IDENT || communicatorComparison == MPI_CONGRUENT));
REQUIRE(structure.prescribedRotation().has_value());
checkZeroRotation(*structure.prescribedRotation());
checkZeroRotation(structure.rotation());
CHECK_THROWS_AS(structure.capture(), std::logic_error);
}
CHECK(probe->backendDestroyed);
CHECK(probe->dependenciesAliveAtBackendDestruction);
auto rejectedFiniteElements = makeFiniteElements();
CHECK_THROWS_AS(
solver::makeContext(
makeModel(), equilibrium::StellarDiscretization{std::move(rejectedFiniteElements)},
preconditioning::makePreconditioner(), ThrowingBackend{}
),
std::runtime_error
);
}
TEST_CASE(
"Generated Rotation Is Reported Without A Prescribed Rotation Payload",
"[solver][architecture][ownership][rotation][report][internal]"
) {
using namespace mean_field;
using namespace solver_internal_architecture_test;
auto finiteElements = makeFiniteElements();
REQUIRE(finiteElements.okay());
auto probe = std::make_shared<LifetimeProbe>();
auto context = solver::makeContext(
makeGeneratedRotationModel(), equilibrium::StellarDiscretization{std::move(finiteElements)},
preconditioning::makePreconditioner(), InspectingBackend{probe}
);
REQUIRE(context.isReady());
auto borrowingSolver =
solver::make(context, solver::nonlinear::Newton(solver::nonlinear::NewtonOptions{}, ZeroMetric{}));
auto report = borrowingSolver.evaluate();
auto structure = report.structureView();
CHECK_FALSE(probe->backendDestroyed);
REQUIRE(structure.valid());
CHECK_FALSE(structure.prescribedRotation().has_value());
const auto rotation = structure.rotation();
REQUIRE(rotation.angular_velocity().Size() == 3);
REQUIRE(rotation.center().Size() == 3);
CHECK(rotation.angular_velocity()(0) == 0.0);
CHECK(rotation.angular_velocity()(1) == 0.0);
CHECK(rotation.angular_velocity()(2) > 0.0);
for (int component = 0; component < 3; ++component) {
CHECK(std::isfinite(rotation.angular_velocity()(component)));
CHECK(rotation.center()(component) == 0.0);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,7 @@
#include <concepts>
#include <numbers>
#include <type_traits>
#include <utility>
#include <catch2/catch_test_macros.hpp>
#include <mfem.hpp>
@@ -8,6 +10,16 @@ import mean_field;
import test_helpers;
namespace {
struct UserApiZeroMetric final { };
[[nodiscard]] mean_field::solver::nonlinear::MetricEvaluation getMetric(
const UserApiZeroMetric &,
const mfem::Vector &,
MPI_Comm
) {
return {.residualNorm = 0.0, .merit = 0.0};
}
[[nodiscard]] mean_field::fem::FEM makeFiniteElements() {
const mean_field::utils::Args arguments = test_utils::setup_args();
return mean_field::fem::setup_fem(arguments.mesh_file, arguments, 0);
@@ -18,9 +30,7 @@ namespace {
Problem &problem,
const mfem::Vector &state,
const mean_field::operators::StellarEquilibriumDependencies &dependencies
) {
problem.Prepare(state, dependencies);
};
) { problem.Prepare(state, dependencies); };
template <typename Problem>
concept PreparesWithPrescribedRotation = requires(
@@ -28,9 +38,7 @@ namespace {
const mfem::Vector &state,
const mean_field::operators::StellarEquilibriumDependencies &dependencies,
const mean_field::physics::RigidRotation &rotation
) {
problem.Prepare(state, dependencies, rotation);
};
) { problem.Prepare(state, dependencies, rotation); };
} // namespace
TEST_CASE(
@@ -47,7 +55,7 @@ TEST_CASE(
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{1.0}})
);
auto problem = equilibrium::discretize(model, finiteElements);
auto problem = equilibrium::discretize(model, std::move(finiteElements));
auto preconditioner = preconditioning::makePreconditioner(problem);
const auto &gravity = preconditioner.structureComponent().gravityComponent();
@@ -62,6 +70,52 @@ TEST_CASE(
CHECK(gravity.potentialSchurBackend().application.cycles == 3);
}
TEST_CASE(
"Complete User API Builds A Context And Evaluates A Borrowing Newton Solver",
"[user-api][solver][context][report]"
) {
using namespace mean_field;
auto finiteElements = makeFiniteElements();
REQUIRE(finiteElements.okay());
constexpr double radius = utils::RADIUS;
const double polytropicConstant = 2.0 * utils::G * radius * radius / std::numbers::pi_v<double>;
const double centralDensity = std::numbers::pi_v<double> * utils::MASS / (4.0 * radius * radius * radius);
auto stellarModel = model::StellarModel(
eos::Polytrope({.n = 1.0, .K = polytropicConstant}),
surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}),
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{utils::MASS}}),
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{centralDensity}})
);
auto discretization = equilibrium::makeStellarDiscretization(
std::move(finiteElements), normalization::PhysicalRieszDiagonal{dimensions::LengthValue{radius}, utils::G}
);
auto context = solver::makeContext(
std::move(stellarModel), std::move(discretization), preconditioning::makePreconditioner(),
solver::linear::FGMRES({.restartLength = 20, .printLevel = -1})
);
int beforeCalls = 0;
int afterCalls = 0;
auto observer = solver::nonlinear::makeObserver(
[&beforeCalls](const solver::nonlinear::BeforeIteration &) { ++beforeCalls; },
[&afterCalls](const solver::nonlinear::AfterIteration &) { ++afterCalls; }
);
auto newton = solver::nonlinear::Newton(solver::nonlinear::NewtonOptions{}, UserApiZeroMetric{});
auto equilibriumSolver = solver::make(context, newton, observer);
auto report = equilibriumSolver.evaluate();
REQUIRE(report.converged());
CHECK(report.completedNonlinearIterations() == 0);
CHECK(beforeCalls == 0);
CHECK(afterCalls == 0);
auto structure = report.structureView();
REQUIRE(structure.valid());
CHECK_FALSE(structure.state().empty());
CHECK(context.hasActiveSolver());
}
TEST_CASE(
"Fixed Angular Momentum User API Generates Rotation And Its Composable Solver Border",
"[user-api][fixed-angular-momentum][type]"
@@ -72,18 +126,14 @@ TEST_CASE(
REQUIRE(finiteElements.okay());
auto model = model::StellarModel(
eos::Polytrope({.n = 1.0, .K = 0.25}),
surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}),
eos::Polytrope({.n = 1.0, .K = 0.25}), surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}),
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{1.0}}),
integral::FixedAngularMomentum({
.Jtotal = dimensions::AngularMomentumValue{0.2},
.axis = {0.0, 0.0, 2.0}
})
integral::FixedAngularMomentum({.Jtotal = dimensions::AngularMomentumValue{0.2}, .axis = {0.0, 0.0, 2.0}})
);
auto problem = equilibrium::discretize(model, finiteElements);
auto preconditioner = preconditioning::makePreconditioner(problem);
using Problem = std::remove_cvref_t<decltype(problem)>;
auto problem = equilibrium::discretize(model, std::move(finiteElements));
auto preconditioner = preconditioning::makePreconditioner(problem);
using Problem = std::remove_cvref_t<decltype(problem)>;
using Preconditioner = std::remove_cvref_t<decltype(preconditioner)>;
STATIC_CHECK(Problem::hasFixedAngularMomentum);
@@ -99,8 +149,9 @@ TEST_CASE(
CHECK(problem.StateSize() == problem.EquationSize());
CHECK(problem.StateSize() == problem.GetPhysicalOperator().Width() + 1);
REQUIRE(problem.GetManifest().constraints().size() == 3);
CHECK(problem.GetManifest().template specification<models::FixedAngularMomentum>().stableId ==
"FixedAngularMomentum");
CHECK(
problem.GetManifest().template specification<models::FixedAngularMomentum>().stableId == "FixedAngularMomentum"
);
CHECK(problem.GetManifest().valueBlocks().back().symbol == "Omega");
CHECK(problem.GetManifest().residualBlocks().back().symbol == "R_J");
}
@@ -119,7 +170,7 @@ TEST_CASE(
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{1.0}}),
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{1.0}})
);
auto problem = equilibrium::discretize(model, finiteElements);
auto problem = equilibrium::discretize(model, std::move(finiteElements));
auto material = preconditioning::materialSurfaceBlock(problem);
auto gravity = preconditioning::GravityFieldBlock(
@@ -151,7 +202,7 @@ TEST_CASE(
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{1.0}}),
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{1.0}})
);
auto problem = equilibrium::discretize(model, finiteElements);
auto problem = equilibrium::discretize(model, std::move(finiteElements));
auto material = preconditioning::materialSurfaceBlock(
problem, preconditioning::backend::Diagonal{}, preconditioning::backend::Diagonal{},