feat(libmeanfield): variadic refactor
also added normaliztion operator
This commit is contained in:
2182
tests/extensions/fixed_magnetic_specific_energy.cpp
Normal file
2182
tests/extensions/fixed_magnetic_specific_energy.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -85,6 +85,8 @@ TEST_CASE(
|
||||
|
||||
STATIC_REQUIRE(field::FieldTag<field::BarotropicConstant>);
|
||||
|
||||
STATIC_REQUIRE(field::FieldTag<field::AngularVelocity>);
|
||||
|
||||
STATIC_REQUIRE_FALSE(field::FieldTag<field_registry_test_utils::MissingSupportField>);
|
||||
|
||||
STATIC_REQUIRE_FALSE(field::FieldTag<field_registry_test_utils::InvalidSupportField>);
|
||||
@@ -113,6 +115,8 @@ TEST_CASE(
|
||||
|
||||
STATIC_REQUIRE(field::NonSpatialField<field::BarotropicConstant>);
|
||||
|
||||
STATIC_REQUIRE(field::NonSpatialField<field::AngularVelocity>);
|
||||
|
||||
STATIC_REQUIRE(std::same_as<field::FieldDomainT<field::Density>, domain::Stellar>);
|
||||
|
||||
STATIC_REQUIRE(std::same_as<field::FieldDomainT<field::Enthalpy>, domain::Stellar>);
|
||||
@@ -123,6 +127,8 @@ TEST_CASE(
|
||||
|
||||
STATIC_REQUIRE(std::same_as<field::FieldSupportT<field::BarotropicConstant>, field::NonSpatialSupport>);
|
||||
|
||||
STATIC_REQUIRE(std::same_as<field::FieldSupportT<field::AngularVelocity>, field::NonSpatialSupport>);
|
||||
|
||||
CHECK(true);
|
||||
}
|
||||
|
||||
@@ -424,4 +430,4 @@ TEST_CASE(
|
||||
STATIC_REQUIRE(field::BarotropicConstant::constraintsAreValid);
|
||||
|
||||
CHECK(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <concepts>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
@@ -23,10 +25,18 @@ namespace {
|
||||
mean_field::eos::Polytrope,
|
||||
mean_field::models::FixedTotalMass>;
|
||||
|
||||
using AngularMomentumPolytropicMassSpecifications = mean_field::models::SpecificationSet<
|
||||
mean_field::models::FixedAngularMomentum,
|
||||
mean_field::surface::Isobaric,
|
||||
mean_field::eos::Polytrope,
|
||||
mean_field::models::FixedTotalMass>;
|
||||
|
||||
using PolytropicMassModel = mean_field::model::StellarModel<PolytropicMassSpecifications>;
|
||||
using PermutedPolytropicMassModel = mean_field::model::StellarModel<PermutedPolytropicMassSpecifications>;
|
||||
using CentralDensityPolytropicMassModel =
|
||||
mean_field::model::StellarModel<CentralDensityPolytropicMassSpecifications>;
|
||||
using AngularMomentumPolytropicMassModel =
|
||||
mean_field::model::StellarModel<AngularMomentumPolytropicMassSpecifications>;
|
||||
} // namespace
|
||||
|
||||
TEST_CASE(
|
||||
@@ -36,11 +46,13 @@ TEST_CASE(
|
||||
STATIC_CHECK(mean_field::models::ModelSpecification<mean_field::eos::Polytrope>);
|
||||
STATIC_CHECK(mean_field::models::ModelSpecification<mean_field::surface::ConstantPressureSurface>);
|
||||
STATIC_CHECK(mean_field::models::ModelSpecification<mean_field::models::FixedTotalMass>);
|
||||
STATIC_CHECK(mean_field::models::ModelSpecification<mean_field::models::FixedAngularMomentum>);
|
||||
STATIC_CHECK(mean_field::models::ModelSpecification<mean_field::models::FixedCentralDensity>);
|
||||
STATIC_CHECK_FALSE(mean_field::models::ModelSpecification<NotAModelSpecification>);
|
||||
STATIC_CHECK(mean_field::models::ResolvedModelSpecification<mean_field::eos::Polytrope>);
|
||||
STATIC_CHECK(mean_field::models::ResolvedModelSpecification<mean_field::surface::ConstantPressureSurface>);
|
||||
STATIC_CHECK(mean_field::models::ResolvedModelSpecification<mean_field::models::FixedTotalMass>);
|
||||
STATIC_CHECK(mean_field::models::ResolvedModelSpecification<mean_field::models::FixedAngularMomentum>);
|
||||
STATIC_CHECK(mean_field::models::ResolvedModelSpecification<mean_field::models::FixedCentralDensity>);
|
||||
|
||||
STATIC_CHECK(
|
||||
@@ -61,8 +73,10 @@ TEST_CASE(
|
||||
|
||||
STATIC_CHECK(std::same_as<PolytropicMassModel, PermutedPolytropicMassModel>);
|
||||
STATIC_CHECK_FALSE(std::same_as<PolytropicMassModel, CentralDensityPolytropicMassModel>);
|
||||
STATIC_CHECK_FALSE(std::same_as<PolytropicMassModel, AngularMomentumPolytropicMassModel>);
|
||||
STATIC_CHECK(mean_field::model::StellarModelType<PolytropicMassModel>);
|
||||
STATIC_CHECK(mean_field::model::StellarModelType<CentralDensityPolytropicMassModel>);
|
||||
STATIC_CHECK(mean_field::model::StellarModelType<AngularMomentumPolytropicMassModel>);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
@@ -81,6 +95,22 @@ TEST_CASE(
|
||||
typename MassSignature::GeneratedValues>
|
||||
);
|
||||
|
||||
using AngularMomentumSignature = AngularMomentumPolytropicMassModel::OperatorSignature;
|
||||
|
||||
STATIC_CHECK(AngularMomentumSignature::generatedValueArity == 2);
|
||||
STATIC_CHECK(AngularMomentumSignature::generatedResidualArity == 2);
|
||||
STATIC_CHECK(AngularMomentumSignature::symbolicallySquare);
|
||||
STATIC_CHECK(
|
||||
mean_field::models::modelTypeListContains<
|
||||
mean_field::models::PhysicalCoordinateFor<mean_field::models::FixedAngularMomentum>,
|
||||
typename AngularMomentumSignature::GeneratedValues>
|
||||
);
|
||||
STATIC_CHECK(
|
||||
mean_field::models::modelTypeListContains<
|
||||
mean_field::models::ResidualFor<mean_field::models::FixedAngularMomentum>,
|
||||
typename AngularMomentumSignature::GeneratedResiduals>
|
||||
);
|
||||
|
||||
STATIC_CHECK(
|
||||
mean_field::models::modelTypeListContains<
|
||||
mean_field::models::ResidualFor<mean_field::models::FixedTotalMass>,
|
||||
@@ -106,6 +136,76 @@ TEST_CASE(
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Fixed Angular Momentum Compiles A Physical Angular Velocity And Invariant Row",
|
||||
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 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(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, 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 models::CompiledFixedAngularMomentum compiled = models::compileConstraint(specification);
|
||||
CHECK(compiled.targetAngularMomentum() == dimensions::AngularMomentumValue{2.75});
|
||||
CHECK(std::abs(compiled.specification().axis()[0]) < 1.0e-15);
|
||||
CHECK(std::abs(compiled.specification().axis()[1] - 0.6) < 1.0e-15);
|
||||
CHECK(std::abs(compiled.specification().axis()[2] - 0.8) < 1.0e-15);
|
||||
CHECK(compiled.specification().center() == std::array<double, 3>{0.25, -0.5, 0.75});
|
||||
|
||||
const physics::RigidRotation rotation = compiled.makeRotation(1.5);
|
||||
CHECK(std::abs(rotation.angular_velocity()(0)) < 1.0e-15);
|
||||
CHECK(std::abs(rotation.angular_velocity()(1) - 0.9) < 1.0e-15);
|
||||
CHECK(std::abs(rotation.angular_velocity()(2) - 1.2) < 1.0e-15);
|
||||
CHECK(rotation.center()(0) == 0.25);
|
||||
CHECK(rotation.center()(1) == -0.5);
|
||||
CHECK(rotation.center()(2) == 0.75);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Fixed Total Mass Compiles Its Generated Multiplier And Canonical Residual Row",
|
||||
tags::model_specification_type_contract
|
||||
@@ -146,6 +246,8 @@ TEST_CASE(
|
||||
constexpr auto surface =
|
||||
mean_field::models::specificationDescriptor<mean_field::surface::ConstantPressureSurface>();
|
||||
constexpr auto mass = mean_field::models::specificationDescriptor<mean_field::models::FixedTotalMass>();
|
||||
constexpr auto angularMomentum =
|
||||
mean_field::models::specificationDescriptor<mean_field::models::FixedAngularMomentum>();
|
||||
constexpr auto centralDensity =
|
||||
mean_field::models::specificationDescriptor<mean_field::models::FixedCentralDensity>();
|
||||
|
||||
@@ -162,6 +264,11 @@ TEST_CASE(
|
||||
STATIC_CHECK(mass.generatedValueArity == 1);
|
||||
STATIC_CHECK(mass.generatedResidualArity == 1);
|
||||
|
||||
STATIC_CHECK(angularMomentum.name == "FixedAngularMomentum");
|
||||
STATIC_CHECK(angularMomentum.role == mean_field::models::SpecificationRole::invariant);
|
||||
STATIC_CHECK(angularMomentum.generatedValueArity == 1);
|
||||
STATIC_CHECK(angularMomentum.generatedResidualArity == 1);
|
||||
|
||||
STATIC_CHECK(centralDensity.name == "FixedCentralDensity");
|
||||
STATIC_CHECK(centralDensity.role == mean_field::models::SpecificationRole::phase_condition);
|
||||
STATIC_CHECK(centralDensity.generatedValueArity == 1);
|
||||
@@ -174,9 +281,15 @@ 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}
|
||||
};
|
||||
|
||||
CHECK(mass.targetMass() == mean_field::dimensions::MassValue{1.25});
|
||||
CHECK(centralDensity.targetDensity() == mean_field::eos::DensityValue{2.5});
|
||||
CHECK(angularMomentum.targetAngularMomentum() == mean_field::dimensions::AngularMomentumValue{0.75});
|
||||
CHECK(angularMomentum.axis() == std::array<double, 3>{0.0, 0.0, 1.0});
|
||||
CHECK(angularMomentum.center() == std::array<double, 3>{0.0, 0.0, 0.0});
|
||||
|
||||
CHECK_THROWS_AS(mean_field::models::FixedTotalMass{mean_field::dimensions::MassValue{0.0}}, std::invalid_argument);
|
||||
CHECK_THROWS_AS(mean_field::models::FixedTotalMass{mean_field::dimensions::MassValue{-1.0}}, std::invalid_argument);
|
||||
@@ -184,6 +297,31 @@ TEST_CASE(
|
||||
mean_field::models::FixedTotalMass{mean_field::dimensions::MassValue{std::numeric_limits<double>::infinity()}},
|
||||
std::invalid_argument
|
||||
);
|
||||
CHECK_THROWS_AS(
|
||||
mean_field::models::FixedAngularMomentum{mean_field::dimensions::AngularMomentumValue{-1.0}},
|
||||
std::invalid_argument
|
||||
);
|
||||
CHECK_THROWS_AS(
|
||||
mean_field::models::FixedAngularMomentum{
|
||||
mean_field::dimensions::AngularMomentumValue{std::numeric_limits<double>::infinity()}
|
||||
},
|
||||
std::invalid_argument
|
||||
);
|
||||
CHECK_THROWS_AS(
|
||||
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}
|
||||
}),
|
||||
std::invalid_argument
|
||||
);
|
||||
|
||||
CHECK_THROWS_AS(mean_field::models::FixedCentralDensity{mean_field::eos::DensityValue{0.0}}, std::invalid_argument);
|
||||
CHECK_THROWS_AS(
|
||||
|
||||
1389
tests/models/physics_specification_frontend.cpp
Normal file
1389
tests/models/physics_specification_frontend.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,6 @@
|
||||
#include <concepts>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
|
||||
@@ -19,6 +20,101 @@ namespace {
|
||||
mean_field::eos::Polytrope,
|
||||
mean_field::surface::Isobaric,
|
||||
mean_field::integral::FixedTotalMass>>;
|
||||
|
||||
class MoveOnlyEquationOfState final {
|
||||
public:
|
||||
struct Parameters final {
|
||||
int marker;
|
||||
};
|
||||
|
||||
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 &operator=(MoveOnlyEquationOfState &&) noexcept = default;
|
||||
|
||||
[[nodiscard]] const std::unique_ptr<int> &marker() const noexcept {
|
||||
return m_marker;
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<int> m_marker;
|
||||
};
|
||||
|
||||
class MoveOnlySurfaceCondition final {
|
||||
public:
|
||||
struct Parameters final {
|
||||
int marker;
|
||||
};
|
||||
|
||||
using ModelDefinition =
|
||||
mean_field::surface::BoundaryCondition<MoveOnlySurfaceCondition, "MoveOnlySurfaceCondition">;
|
||||
|
||||
explicit MoveOnlySurfaceCondition(const Parameters parameters)
|
||||
: m_marker(std::make_unique<int>(parameters.marker)) {
|
||||
}
|
||||
|
||||
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 {
|
||||
return m_marker;
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<int> m_marker;
|
||||
};
|
||||
|
||||
class MoveOnlyIntegralConstraint final {
|
||||
public:
|
||||
struct Parameters final {
|
||||
int marker;
|
||||
};
|
||||
|
||||
using ScalarDescription = mean_field::stellar::ScalarConstraint<
|
||||
mean_field::dimensions::quantity::Mass,
|
||||
mean_field::dimensions::quantity::SpecificEnergy,
|
||||
mean_field::dimensions::quantity::Mass,
|
||||
"move_only_integral.multiplier",
|
||||
"C_move",
|
||||
"move_only_integral.residual",
|
||||
"R_move">;
|
||||
using TargetValue = typename ScalarDescription::TargetValue;
|
||||
using ModelDefinition = mean_field::integral::FixedScalarWithMultiplier<
|
||||
MoveOnlyIntegralConstraint,
|
||||
"MoveOnlyIntegralConstraint",
|
||||
mean_field::stellar::Reads<mean_field::stellar::state::Density>,
|
||||
mean_field::stellar::Changes<mean_field::stellar::equation::HydrostaticBalance>,
|
||||
ScalarDescription>;
|
||||
|
||||
explicit MoveOnlyIntegralConstraint(const Parameters parameters)
|
||||
: m_marker(std::make_unique<int>(parameters.marker)) {
|
||||
}
|
||||
|
||||
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 {
|
||||
return m_marker;
|
||||
}
|
||||
|
||||
[[nodiscard]] TargetValue target() const noexcept {
|
||||
return TargetValue{static_cast<double>(*m_marker)};
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<int> m_marker;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
TEST_CASE(
|
||||
@@ -38,8 +134,8 @@ TEST_CASE(
|
||||
STATIC_CHECK(models::SpecifiedModelType<decltype(stellarModel)>);
|
||||
STATIC_CHECK(CanonicalModel::specificationCount == 4);
|
||||
STATIC_CHECK(CanonicalModel::symbolicallySquare);
|
||||
STATIC_CHECK(CanonicalModel::hasCompleteEquilibriumCompiler);
|
||||
STATIC_CHECK(CanonicalModel::compilationClass == models::EquilibriumSystemCompilation::complete_equilibrium_system);
|
||||
STATIC_CHECK(CanonicalModel::hasCompleteEquilibriumDeclaration);
|
||||
STATIC_CHECK(operators::StellarEquilibriumSystemCompilable<CanonicalModel>);
|
||||
|
||||
CHECK(stellarModel.specification<eos::Polytrope>().polytropic_index() == 3.0);
|
||||
CHECK(stellarModel.specification<eos::Polytrope>().polytropic_constant() == 0.25);
|
||||
@@ -50,6 +146,44 @@ TEST_CASE(
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Incomplete Stellar Model Spellings Fail Capability Probes Without Diagnostics",
|
||||
tags::stellar_model_specification_api
|
||||
) {
|
||||
using namespace mean_field;
|
||||
using IncompleteModel = model::StellarModel<int>;
|
||||
|
||||
STATIC_CHECK_FALSE(model::StellarModelType<IncompleteModel>);
|
||||
STATIC_CHECK_FALSE(models::SpecifiedModelType<IncompleteModel>);
|
||||
STATIC_CHECK(model::specificationRoleCount<models::SpecificationRole::constitutive_law, IncompleteModel> == 0);
|
||||
STATIC_CHECK_FALSE(model::HasEquationOfState<IncompleteModel>);
|
||||
STATIC_CHECK_FALSE(model::HasSurfaceCondition<IncompleteModel>);
|
||||
STATIC_CHECK_FALSE(operators::StellarEquilibriumSystemCompilable<IncompleteModel>);
|
||||
STATIC_CHECK_FALSE(equilibrium::StellarEquilibriumModel<IncompleteModel>);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Specification Sets Canonicalize Cvref-Qualified Physics Types",
|
||||
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>;
|
||||
|
||||
STATIC_CHECK(std::same_as<Qualified, Plain>);
|
||||
STATIC_CHECK(models::ValidModelSpecificationPack<
|
||||
const eos::Polytrope &,
|
||||
volatile surface::Isobaric &&,
|
||||
const integral::FixedTotalMass>);
|
||||
STATIC_CHECK(model::StellarModelType<model::StellarModel<Qualified>>);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Stellar Model Deduction Canonicalizes Unordered Specifications",
|
||||
tags::stellar_model_specification_api
|
||||
@@ -85,6 +219,40 @@ TEST_CASE(
|
||||
CHECK(descriptors[3].specification.name == "FixedCentralDensity");
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Canonical Model Construction Preserves Every Move-Only Physics Specification",
|
||||
tags::stellar_model_specification_api
|
||||
) {
|
||||
using namespace mean_field;
|
||||
|
||||
auto stellarModel = model::StellarModel(
|
||||
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>>;
|
||||
|
||||
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 &integralConstraint = stellarModel.specification<MoveOnlyIntegralConstraint>();
|
||||
|
||||
REQUIRE(equationOfState.marker() != nullptr);
|
||||
REQUIRE(surfaceCondition.marker() != nullptr);
|
||||
REQUIRE(integralConstraint.marker() != nullptr);
|
||||
CHECK(*equationOfState.marker() == 101);
|
||||
CHECK(*surfaceCondition.marker() == 211);
|
||||
CHECK(*integralConstraint.marker() == 307);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Stellar Specification Parameter Constructors Preserve Validation",
|
||||
tags::stellar_model_specification_api
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
#include <algorithm>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <mfem.hpp>
|
||||
#include <numbers>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
import mean_field;
|
||||
@@ -30,12 +34,41 @@ namespace {
|
||||
return vector;
|
||||
}
|
||||
|
||||
void require_all_ranks(
|
||||
const bool localCondition,
|
||||
const MPI_Comm communicator,
|
||||
const char *description
|
||||
) {
|
||||
int rank = 0;
|
||||
int size = 0;
|
||||
MPI_Comm_rank(communicator, &rank);
|
||||
MPI_Comm_size(communicator, &size);
|
||||
|
||||
const int localFailure = localCondition ? size : rank;
|
||||
int firstFailure = size;
|
||||
const int result = MPI_Allreduce(
|
||||
&localFailure,
|
||||
&firstFailure,
|
||||
1,
|
||||
MPI_INT,
|
||||
MPI_MIN,
|
||||
communicator
|
||||
);
|
||||
REQUIRE(result == MPI_SUCCESS);
|
||||
CAPTURE(description, localCondition, firstFailure);
|
||||
REQUIRE(firstFailure == size);
|
||||
}
|
||||
|
||||
double global_dot(
|
||||
const mfem::Vector &left,
|
||||
const mfem::Vector &right,
|
||||
const MPI_Comm communicator
|
||||
) {
|
||||
REQUIRE(left.Size() == right.Size());
|
||||
require_all_ranks(
|
||||
left.Size() == right.Size(),
|
||||
communicator,
|
||||
"global dot-product vector sizes"
|
||||
);
|
||||
const double local = left * right;
|
||||
double global = 0.0;
|
||||
REQUIRE(MPI_Allreduce(&local, &global, 1, MPI_DOUBLE, MPI_SUM, communicator) == MPI_SUCCESS);
|
||||
@@ -48,6 +81,39 @@ namespace {
|
||||
) {
|
||||
return std::sqrt(global_dot(vector, vector, communicator));
|
||||
}
|
||||
|
||||
[[nodiscard]] mean_field::operators::StellarEquilibriumDependencies
|
||||
make_stellar_dependencies(const std::uint64_t revision = 1) {
|
||||
return {
|
||||
.discretization = {.identity = 16101, .revision = 1},
|
||||
.density = {.identity = 16103, .revision = revision},
|
||||
.surfaceDeformation = {.identity = 16111, .revision = revision},
|
||||
.gravityGradient = {.identity = 16127, .revision = revision},
|
||||
.gravityPotential = {.identity = 16139, .revision = revision},
|
||||
.enthalpy = {.identity = 16141, .revision = revision},
|
||||
.bernoulliConstant = {.identity = 16183, .revision = revision},
|
||||
.rotation = {.identity = 16187, .revision = revision},
|
||||
.targetMass = {.identity = 16189, .revision = 1}
|
||||
};
|
||||
}
|
||||
|
||||
void check_rank_consistent_scalar(
|
||||
const double value,
|
||||
const MPI_Comm communicator,
|
||||
const double relativeTolerance = 2.0e-13
|
||||
) {
|
||||
double minimum = 0.0;
|
||||
double maximum = 0.0;
|
||||
REQUIRE(MPI_Allreduce(&value, &minimum, 1, MPI_DOUBLE, MPI_MIN, communicator) == MPI_SUCCESS);
|
||||
REQUIRE(MPI_Allreduce(&value, &maximum, 1, MPI_DOUBLE, MPI_MAX, communicator) == MPI_SUCCESS);
|
||||
CAPTURE(value, minimum, maximum);
|
||||
CHECK(std::isfinite(minimum));
|
||||
CHECK(std::isfinite(maximum));
|
||||
CHECK(
|
||||
std::abs(maximum - minimum) <=
|
||||
relativeTolerance * std::max({1.0, std::abs(minimum), std::abs(maximum)})
|
||||
);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE(
|
||||
@@ -97,6 +163,750 @@ TEST_CASE(
|
||||
CHECK(f.logicalReferenceMesh->GetNE() == f.mesh->GetNE());
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"MPI Fixed Angular Momentum Produces One Consistent Global Invariant Row",
|
||||
"[mpi][distributed][fixed-angular-momentum][physics][jacobian]"
|
||||
) {
|
||||
using namespace mean_field;
|
||||
|
||||
const auto args = test_utils::setup_args();
|
||||
fem::FEM finiteElements = fem::setup_fem(args.mesh_file, args, 0);
|
||||
require_all_ranks(
|
||||
finiteElements.okay(),
|
||||
MPI_COMM_WORLD,
|
||||
"fixed-angular-momentum FEM setup"
|
||||
);
|
||||
const MPI_Comm communicator = finiteElements.mesh->GetComm();
|
||||
|
||||
mfem::ParGridFunction densityField(finiteElements.densityFes.get());
|
||||
mfem::ConstantCoefficient densityCoefficient(1.23);
|
||||
densityField.ProjectCoefficient(densityCoefficient);
|
||||
mfem::Vector densityTrue;
|
||||
densityField.GetTrueDofs(densityTrue);
|
||||
|
||||
mfem::Vector displacementTrue(finiteElements.displacementFes->GetTrueVSize());
|
||||
displacementTrue = 0.0;
|
||||
finiteElements.displacement->SetFromTrueDofs(displacementTrue);
|
||||
mfem::Vector gravityGradientTrue(finiteElements.gravityFluxFes->GetTrueVSize());
|
||||
mfem::Vector gravityPotentialTrue(finiteElements.gravityPotentialFes->GetTrueVSize());
|
||||
gravityGradientTrue = 0.0;
|
||||
gravityPotentialTrue = 0.0;
|
||||
|
||||
operators::context::gravity_field::GravityFieldLinearizationContext gravityContext(
|
||||
finiteElements,
|
||||
*finiteElements.domainMapperStateless
|
||||
);
|
||||
gravityContext.Prepare(
|
||||
{.density = gravityContext.GetDensityMap().gather(densityTrue),
|
||||
.displacement = gravityContext.GetDisplacementMap().gather(displacementTrue),
|
||||
.gravity_gradient = gravityContext.GetGravityGradientMap().gather(gravityGradientTrue),
|
||||
.gravity_potential = gravityContext.GetGravityPotentialMap().gather(gravityPotentialTrue)},
|
||||
{.discretization = {.value = 2},
|
||||
.displacement = {.value = 3},
|
||||
.density = {.value = 5},
|
||||
.gravity_gradient = {.value = 7},
|
||||
.gravity_potential = {.value = 11}}
|
||||
);
|
||||
|
||||
constexpr double targetAngularMomentum = 0.37;
|
||||
constexpr double angularVelocity = 0.61;
|
||||
operators::PreparedAngularMomentumOperator invariant(
|
||||
finiteElements,
|
||||
*finiteElements.domainMapperStateless,
|
||||
gravityContext,
|
||||
models::compileConstraint(
|
||||
integral::FixedAngularMomentum({.Jtotal = dimensions::AngularMomentumValue{targetAngularMomentum}})
|
||||
)
|
||||
);
|
||||
const operators::AngularMomentumDependencies dependencies{
|
||||
.discretization = {.identity = 101, .revision = 2},
|
||||
.density = {.identity = 103, .revision = 5},
|
||||
.displacement = {.identity = 107, .revision = 3},
|
||||
.rotation = {.identity = 109, .revision = 13}
|
||||
};
|
||||
const auto preparation = invariant.Prepare(angularVelocity, dependencies);
|
||||
CHECK(preparation.rebuiltStaticPlan);
|
||||
CHECK(preparation.refreshedGeometry);
|
||||
CHECK(preparation.refreshedDensity);
|
||||
CHECK(preparation.updatedAngularVelocity);
|
||||
CHECK(preparation.assembledResidual);
|
||||
|
||||
const double independentMoment = analysis::get_moment_of_inertia(finiteElements, densityField);
|
||||
const double preparedMoment = invariant.GetMomentOfInertia();
|
||||
const double comparisonScale = std::max({std::abs(independentMoment), std::abs(preparedMoment), 1.0e-300});
|
||||
CHECK(std::abs(preparedMoment - independentMoment) / comparisonScale <= 3.0e-13);
|
||||
|
||||
double minimumMoment = 0.0;
|
||||
double maximumMoment = 0.0;
|
||||
MPI_Allreduce(&preparedMoment, &minimumMoment, 1, MPI_DOUBLE, MPI_MIN, finiteElements.mesh->GetComm());
|
||||
MPI_Allreduce(&preparedMoment, &maximumMoment, 1, MPI_DOUBLE, MPI_MAX, finiteElements.mesh->GetComm());
|
||||
CHECK(std::abs(maximumMoment - minimumMoment) / comparisonScale <= 2.0e-15);
|
||||
|
||||
mfem::Vector residual;
|
||||
invariant.BuildResidual(residual);
|
||||
require_all_ranks(
|
||||
residual.Size() == 1,
|
||||
communicator,
|
||||
"fixed-angular-momentum residual size"
|
||||
);
|
||||
CHECK(
|
||||
std::abs(residual(0) - (angularVelocity * independentMoment - targetAngularMomentum)) /
|
||||
std::max({std::abs(residual(0)), std::abs(angularVelocity * independentMoment), 1.0}) <=
|
||||
3.0e-13
|
||||
);
|
||||
|
||||
mfem::Vector densityAction;
|
||||
invariant.ApplyDensityJacobianAction(gravityContext.GetDensityMap().gather(densityTrue), densityAction);
|
||||
require_all_ranks(
|
||||
densityAction.Size() == 1,
|
||||
communicator,
|
||||
"fixed-angular-momentum density action size"
|
||||
);
|
||||
CHECK(std::abs(densityAction(0) - angularVelocity * preparedMoment) / comparisonScale <= 3.0e-13);
|
||||
|
||||
constexpr double angularVelocityVariation = -0.29;
|
||||
mfem::Vector angularVelocityAction;
|
||||
invariant.ApplyAngularVelocityJacobianAction(angularVelocityVariation, angularVelocityAction);
|
||||
require_all_ranks(
|
||||
angularVelocityAction.Size() == 1,
|
||||
communicator,
|
||||
"fixed-angular-momentum rotation action size"
|
||||
);
|
||||
CHECK(
|
||||
std::abs(angularVelocityAction(0) - angularVelocityVariation * preparedMoment) / comparisonScale <=
|
||||
2.0e-15
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"MPI Density Volume Context Forwards The Prepared Global Mass Integral",
|
||||
"[mpi][distributed][integral-context][physics-extension]"
|
||||
) {
|
||||
using namespace mean_field;
|
||||
|
||||
const utils::Args arguments = test_utils::setup_args();
|
||||
fem::FEM finiteElements = fem::setup_fem(arguments.mesh_file, arguments, 0);
|
||||
require_all_ranks(
|
||||
finiteElements.okay(),
|
||||
MPI_COMM_WORLD,
|
||||
"density-volume context FEM setup"
|
||||
);
|
||||
const MPI_Comm communicator = finiteElements.mesh->GetComm();
|
||||
|
||||
constexpr double densityValue = 1.23;
|
||||
mfem::ParGridFunction densityField(finiteElements.densityFes.get());
|
||||
mfem::ConstantCoefficient densityCoefficient(densityValue);
|
||||
densityField.ProjectCoefficient(densityCoefficient);
|
||||
mfem::Vector densityTrue;
|
||||
densityField.GetTrueDofs(densityTrue);
|
||||
|
||||
mfem::Vector displacementTrue(finiteElements.displacementFes->GetTrueVSize());
|
||||
displacementTrue = 0.0;
|
||||
finiteElements.displacement->SetFromTrueDofs(displacementTrue);
|
||||
mfem::Vector gravityGradientTrue(finiteElements.gravityFluxFes->GetTrueVSize());
|
||||
mfem::Vector gravityPotentialTrue(finiteElements.gravityPotentialFes->GetTrueVSize());
|
||||
gravityGradientTrue = 0.0;
|
||||
gravityPotentialTrue = 0.0;
|
||||
|
||||
operators::context::gravity_field::GravityFieldLinearizationContext gravityContext(
|
||||
finiteElements,
|
||||
*finiteElements.domainMapperStateless
|
||||
);
|
||||
const mfem::Vector reducedDensity = gravityContext.GetDensityMap().gather(
|
||||
densityTrue
|
||||
);
|
||||
gravityContext.Prepare(
|
||||
{.density = reducedDensity,
|
||||
.displacement = gravityContext.GetDisplacementMap().gather(displacementTrue),
|
||||
.gravity_gradient = gravityContext.GetGravityGradientMap().gather(gravityGradientTrue),
|
||||
.gravity_potential = gravityContext.GetGravityPotentialMap().gather(gravityPotentialTrue)},
|
||||
{.discretization = {.value = 2},
|
||||
.displacement = {.value = 3},
|
||||
.density = {.value = 5},
|
||||
.gravity_gradient = {.value = 7},
|
||||
.gravity_potential = {.value = 11}}
|
||||
);
|
||||
|
||||
operators::PreparedMassNormalizationOperator massIntegral(
|
||||
finiteElements,
|
||||
*finiteElements.domainMapperStateless,
|
||||
gravityContext
|
||||
);
|
||||
massIntegral.Prepare(
|
||||
models::compileConstraint(
|
||||
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{1.0}})
|
||||
),
|
||||
{.discretization = {.identity = 101, .revision = 2},
|
||||
.density = {.identity = 103, .revision = 5},
|
||||
.displacement = {.identity = 107, .revision = 3},
|
||||
.targetMass = {.identity = 109, .revision = 1}}
|
||||
);
|
||||
|
||||
class DistributedMassIntegralCore final {
|
||||
public:
|
||||
explicit DistributedMassIntegralCore(
|
||||
const operators::PreparedMassNormalizationOperator &mass
|
||||
) noexcept
|
||||
: m_mass(&mass) {
|
||||
}
|
||||
|
||||
[[nodiscard]] double ApplyDensityVolumeIntegralDensityAction(
|
||||
const mfem::Vector &direction
|
||||
) const {
|
||||
mfem::Vector action;
|
||||
m_mass->ApplyDensityJacobianAction(direction, action);
|
||||
return action(0);
|
||||
}
|
||||
|
||||
[[nodiscard]] double ApplyDensityVolumeIntegralSurfaceShapeAction(
|
||||
const mfem::Vector &
|
||||
) const noexcept {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
private:
|
||||
const operators::PreparedMassNormalizationOperator *m_mass;
|
||||
};
|
||||
|
||||
const DistributedMassIntegralCore testCore{massIntegral};
|
||||
|
||||
const stellar::DensityVolumeIntegralContext<integral::FixedTotalMass>
|
||||
densityIntegral{testCore};
|
||||
const double integratedMass =
|
||||
densityIntegral.integrateDensity(reducedDensity).value();
|
||||
const double linearizedDensityMass =
|
||||
densityIntegral.linearizeDensityIntegral(reducedDensity).value();
|
||||
const double preparedMass = massIntegral.GetCurrentMass();
|
||||
const double independentMass =
|
||||
densityValue * analysis::get_mesh_volume(finiteElements);
|
||||
|
||||
check_rank_consistent_scalar(integratedMass, communicator);
|
||||
check_rank_consistent_scalar(linearizedDensityMass, communicator);
|
||||
check_rank_consistent_scalar(preparedMass, communicator);
|
||||
check_rank_consistent_scalar(independentMass, communicator);
|
||||
const double massScale = std::max(
|
||||
{1.0, std::abs(integratedMass), std::abs(independentMass)}
|
||||
);
|
||||
CHECK(std::abs(integratedMass - independentMass) <= 3.0e-12 * massScale);
|
||||
CHECK(std::abs(integratedMass - preparedMass) <= 3.0e-13 * massScale);
|
||||
CHECK(std::abs(linearizedDensityMass - integratedMass) <=
|
||||
3.0e-13 * massScale);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"MPI Assembled Variadic Stellar Root Normalizes And Applies Its Inferred Preconditioner",
|
||||
"[mpi][distributed][stellar-equilibrium][normalization][preconditioning][integration]"
|
||||
) {
|
||||
using namespace mean_field;
|
||||
|
||||
const utils::Args arguments = test_utils::setup_args();
|
||||
fem::FEM finiteElements = fem::setup_fem(arguments.mesh_file, arguments, 0);
|
||||
require_all_ranks(
|
||||
finiteElements.okay(),
|
||||
MPI_COMM_WORLD,
|
||||
"variadic stellar-root FEM setup"
|
||||
);
|
||||
const MPI_Comm communicator = finiteElements.mesh->GetComm();
|
||||
|
||||
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);
|
||||
|
||||
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{0.05},
|
||||
.axis = {0.0, 0.0, 1.0}
|
||||
}),
|
||||
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{centralDensity}})
|
||||
);
|
||||
auto problem = equilibrium::discretize(
|
||||
model,
|
||||
equilibrium::makeStellarDiscretization(
|
||||
finiteElements,
|
||||
normalization::PhysicalRieszDiagonal{
|
||||
dimensions::LengthValue{radius},
|
||||
utils::G
|
||||
}
|
||||
)
|
||||
);
|
||||
auto projected = seed::makeProjectedEquilibriumState(
|
||||
problem,
|
||||
seed::LaneEmden({
|
||||
.centralDensity = dimensions::DensityValue{centralDensity},
|
||||
.radialSampleCount = 512
|
||||
})
|
||||
);
|
||||
auto normalized = normalization::makeNormalizedStellarEquilibriumOperator(problem);
|
||||
using Problem = std::remove_cvref_t<decltype(problem)>;
|
||||
using Form = typename Problem::FormType;
|
||||
constexpr auto massResidualBlock = utils::blocks::get_residual_block<Form>(
|
||||
utils::blocks::fixed_total_mass_constraint.mass_normalization_term
|
||||
);
|
||||
constexpr auto angularMomentumResidualBlock = utils::blocks::get_residual_block<Form>(
|
||||
utils::blocks::fixed_angular_momentum_constraint.angular_velocity_term
|
||||
);
|
||||
constexpr auto centralDensityResidualBlock = utils::blocks::get_residual_block<Form>(
|
||||
utils::blocks::fixed_central_density_phase.central_value_term
|
||||
);
|
||||
require_all_ranks(
|
||||
problem.StateSize() == problem.EquationSize(),
|
||||
communicator,
|
||||
"variadic stellar-root square layout"
|
||||
);
|
||||
|
||||
mfem::Vector normalizedState;
|
||||
normalized.NormalizeState(projected.values, normalizedState);
|
||||
const auto preparation = normalized.Prepare(
|
||||
normalizedState,
|
||||
make_stellar_dependencies(1)
|
||||
);
|
||||
require_all_ranks(
|
||||
preparation.DidAnyWork() &&
|
||||
preparation.generatedPhysicalControl &&
|
||||
preparation.template specification<models::FixedAngularMomentum>().generatedRotation &&
|
||||
problem.IsPrepared() &&
|
||||
normalized.IsPrepared(),
|
||||
communicator,
|
||||
"initial variadic stellar-root preparation"
|
||||
);
|
||||
|
||||
// The astronomy-facing integral handle must delegate to the same mapped
|
||||
// physical-volume quadrature and collective reduction as the prepared
|
||||
// mass equation, without exposing the FEM/core objects to extension
|
||||
// physics. Linearity in density gives an independent check of both
|
||||
// public operations on every rank.
|
||||
const auto physicalState = problem.GetManifest().stateView(projected.values);
|
||||
const auto physicalDensity = physicalState.block(
|
||||
utils::blocks::density_field.mass_term
|
||||
);
|
||||
const stellar::DensityVolumeIntegralContext<integral::FixedTotalMass>
|
||||
densityIntegral{problem.GetPhysicalOperator()};
|
||||
const double integratedMass = densityIntegral.integrateDensity(
|
||||
physicalDensity
|
||||
).value();
|
||||
const double linearizedMass = densityIntegral.linearizeDensityIntegral(
|
||||
physicalDensity
|
||||
).value();
|
||||
const double preparedMass = problem.GetPhysicalOperator()
|
||||
.GetFixedMassReport()
|
||||
.achieved;
|
||||
check_rank_consistent_scalar(integratedMass, communicator);
|
||||
check_rank_consistent_scalar(linearizedMass, communicator);
|
||||
const double massComparisonScale = std::max(
|
||||
{std::abs(integratedMass), std::abs(preparedMass), 1.0e-300}
|
||||
);
|
||||
CHECK(std::abs(integratedMass - preparedMass) / massComparisonScale <=
|
||||
3.0e-13);
|
||||
CHECK(std::abs(linearizedMass - preparedMass) / massComparisonScale <=
|
||||
3.0e-13);
|
||||
|
||||
mfem::Vector normalizedResidual;
|
||||
normalized.BuildResidual(normalizedResidual);
|
||||
require_all_ranks(
|
||||
normalizedResidual.Size() == problem.EquationSize(),
|
||||
communicator,
|
||||
"normalized variadic residual size"
|
||||
);
|
||||
|
||||
auto residualView = problem.GetManifest().residualView(normalizedResidual);
|
||||
const auto &layout = problem.GetManifest().layout();
|
||||
const auto &residualFactors = normalized.GetNormalization().ResidualFactors();
|
||||
const auto massReport = problem.GetPreparedOperator().GetFixedMassReport();
|
||||
const auto angularMomentumReport =
|
||||
problem.GetPreparedOperator().GetAngularMomentumReport();
|
||||
const auto centralDensityReport =
|
||||
problem.GetPreparedOperator().GetCentralDensityReport();
|
||||
const auto checkReportedScalarResidual = [&](const mfem::Vector &block,
|
||||
const double expected) {
|
||||
require_all_ranks(
|
||||
block.Size() == 1,
|
||||
communicator,
|
||||
"reported scalar residual block size"
|
||||
);
|
||||
const double actual = block(0);
|
||||
CAPTURE(actual, expected);
|
||||
CHECK(std::isfinite(expected));
|
||||
CHECK(
|
||||
std::abs(actual - expected) <=
|
||||
5.0e-13 * std::max({1.0, std::abs(actual), std::abs(expected)})
|
||||
);
|
||||
check_rank_consistent_scalar(actual, finiteElements.mesh->GetComm());
|
||||
};
|
||||
checkReportedScalarResidual(
|
||||
residualView.block(
|
||||
utils::blocks::fixed_total_mass_constraint.mass_normalization_term
|
||||
),
|
||||
massReport.dimensionalResidual *
|
||||
residualFactors(layout.offset(massResidualBlock))
|
||||
);
|
||||
checkReportedScalarResidual(
|
||||
residualView.block(
|
||||
utils::blocks::fixed_angular_momentum_constraint.angular_velocity_term
|
||||
),
|
||||
angularMomentumReport.dimensionalResidual *
|
||||
residualFactors(layout.offset(angularMomentumResidualBlock))
|
||||
);
|
||||
checkReportedScalarResidual(
|
||||
residualView.block(
|
||||
utils::blocks::fixed_central_density_phase.central_value_term
|
||||
),
|
||||
centralDensityReport.enthalpyResidual *
|
||||
residualFactors(layout.offset(centralDensityResidualBlock))
|
||||
);
|
||||
|
||||
mfem::Vector normalizedDirection = make_deterministic_vector(problem.StateSize(), 0.37);
|
||||
const auto directionState = problem.GetManifest().stateView(normalizedDirection);
|
||||
mfem::Vector massDirection = directionState.block(
|
||||
utils::blocks::fixed_total_mass_constraint.mass_normalization_term
|
||||
);
|
||||
mfem::Vector angularVelocityDirection = directionState.block(
|
||||
utils::blocks::fixed_angular_momentum_constraint.angular_velocity_term
|
||||
);
|
||||
mfem::Vector phaseDirection = directionState.block(
|
||||
utils::blocks::fixed_central_density_phase.central_value_term
|
||||
);
|
||||
require_all_ranks(
|
||||
massDirection.Size() == 1 &&
|
||||
angularVelocityDirection.Size() == 1 &&
|
||||
phaseDirection.Size() == 1,
|
||||
communicator,
|
||||
"generated scalar direction block sizes"
|
||||
);
|
||||
massDirection(0) = 0.17;
|
||||
angularVelocityDirection(0) = -0.23;
|
||||
phaseDirection(0) = 0.31;
|
||||
massDirection.SyncAliasMemory(normalizedDirection);
|
||||
angularVelocityDirection.SyncAliasMemory(normalizedDirection);
|
||||
phaseDirection.SyncAliasMemory(normalizedDirection);
|
||||
normalizedDirection /= global_norm(normalizedDirection, finiteElements.mesh->GetComm());
|
||||
|
||||
mfem::Vector normalizedAction;
|
||||
normalized.Mult(normalizedDirection, normalizedAction);
|
||||
require_all_ranks(
|
||||
normalizedAction.Size() == problem.EquationSize(),
|
||||
communicator,
|
||||
"normalized variadic Jacobian-action size"
|
||||
);
|
||||
|
||||
/* Different dependency revisions are intentional: the state changes in
|
||||
* each difference evaluation, so the distributed physical contexts must
|
||||
* be rebuilt even though all persistent identities remain the same. */
|
||||
constexpr double differenceStep = 1.0e-5;
|
||||
mfem::Vector plusState(normalizedState);
|
||||
plusState.Add(differenceStep, normalizedDirection);
|
||||
const auto plusPreparation = normalized.Prepare(
|
||||
plusState,
|
||||
make_stellar_dependencies(2)
|
||||
);
|
||||
require_all_ranks(
|
||||
plusPreparation.DidAnyWork(),
|
||||
communicator,
|
||||
"positive finite-difference preparation"
|
||||
);
|
||||
mfem::Vector plusResidual;
|
||||
normalized.BuildResidual(plusResidual);
|
||||
|
||||
mfem::Vector minusState(normalizedState);
|
||||
minusState.Add(-differenceStep, normalizedDirection);
|
||||
const auto minusPreparation = normalized.Prepare(
|
||||
minusState,
|
||||
make_stellar_dependencies(3)
|
||||
);
|
||||
require_all_ranks(
|
||||
minusPreparation.DidAnyWork(),
|
||||
communicator,
|
||||
"negative finite-difference preparation"
|
||||
);
|
||||
mfem::Vector minusResidual;
|
||||
normalized.BuildResidual(minusResidual);
|
||||
|
||||
mfem::Vector finiteDifference(plusResidual);
|
||||
finiteDifference -= minusResidual;
|
||||
finiteDifference /= 2.0 * differenceStep;
|
||||
const auto restoredPreparation = normalized.Prepare(
|
||||
normalizedState,
|
||||
make_stellar_dependencies(4)
|
||||
);
|
||||
require_all_ranks(
|
||||
restoredPreparation.DidAnyWork() && normalized.IsPrepared(),
|
||||
communicator,
|
||||
"restored finite-difference preparation"
|
||||
);
|
||||
mfem::Vector restoredResidual;
|
||||
normalized.BuildResidual(restoredResidual);
|
||||
mfem::Vector restoredResidualDifference(restoredResidual);
|
||||
restoredResidualDifference -= normalizedResidual;
|
||||
const double restoredResidualError = global_norm(
|
||||
restoredResidualDifference,
|
||||
finiteElements.mesh->GetComm()
|
||||
) / std::max({
|
||||
global_norm(restoredResidual, finiteElements.mesh->GetComm()),
|
||||
global_norm(normalizedResidual, finiteElements.mesh->GetComm()),
|
||||
std::numeric_limits<double>::epsilon()
|
||||
});
|
||||
CAPTURE(restoredResidualError);
|
||||
CHECK(restoredResidualError <= 2.0e-12);
|
||||
|
||||
mfem::Vector finiteDifferenceError(normalizedAction);
|
||||
finiteDifferenceError -= finiteDifference;
|
||||
const double actionNorm = global_norm(
|
||||
normalizedAction,
|
||||
finiteElements.mesh->GetComm()
|
||||
);
|
||||
const double finiteDifferenceNorm = global_norm(
|
||||
finiteDifference,
|
||||
finiteElements.mesh->GetComm()
|
||||
);
|
||||
const double completeDifferenceError = global_norm(
|
||||
finiteDifferenceError,
|
||||
finiteElements.mesh->GetComm()
|
||||
) / std::max({
|
||||
actionNorm,
|
||||
finiteDifferenceNorm,
|
||||
std::numeric_limits<double>::epsilon()
|
||||
});
|
||||
CAPTURE(actionNorm, finiteDifferenceNorm, completeDifferenceError);
|
||||
CHECK(actionNorm > std::numeric_limits<double>::min());
|
||||
CHECK(finiteDifferenceNorm > std::numeric_limits<double>::min());
|
||||
CHECK(completeDifferenceError <= 8.0e-5);
|
||||
|
||||
const int locallyFinite =
|
||||
vector_is_finite(normalizedResidual) &&
|
||||
vector_is_finite(normalizedAction) &&
|
||||
vector_is_finite(finiteDifference) ? 1 : 0;
|
||||
int globallyFinite = 0;
|
||||
REQUIRE(MPI_Allreduce(
|
||||
&locallyFinite,
|
||||
&globallyFinite,
|
||||
1,
|
||||
MPI_INT,
|
||||
MPI_MIN,
|
||||
finiteElements.mesh->GetComm()
|
||||
) == MPI_SUCCESS);
|
||||
CHECK(globallyFinite == 1);
|
||||
CHECK(global_norm(normalizedResidual, finiteElements.mesh->GetComm()) > 0.0);
|
||||
CHECK(global_norm(normalizedAction, finiteElements.mesh->GetComm()) > 0.0);
|
||||
|
||||
auto actionView = problem.GetManifest().residualView(normalizedAction);
|
||||
auto finiteDifferenceView = problem.GetManifest().residualView(finiteDifference);
|
||||
const auto checkGlobalRow = [&](const auto &term, const char *rowName) {
|
||||
const mfem::Vector residualBlock = residualView.block(term);
|
||||
const mfem::Vector actionBlock = actionView.block(term);
|
||||
const mfem::Vector differenceBlock = finiteDifferenceView.block(term);
|
||||
require_all_ranks(
|
||||
residualBlock.Size() == 1 &&
|
||||
actionBlock.Size() == 1 &&
|
||||
differenceBlock.Size() == 1,
|
||||
communicator,
|
||||
"global scalar residual/Jacobian block sizes"
|
||||
);
|
||||
check_rank_consistent_scalar(residualBlock(0), finiteElements.mesh->GetComm());
|
||||
check_rank_consistent_scalar(actionBlock(0), finiteElements.mesh->GetComm());
|
||||
check_rank_consistent_scalar(differenceBlock(0), finiteElements.mesh->GetComm());
|
||||
const double rowMagnitude = std::max(
|
||||
std::abs(actionBlock(0)),
|
||||
std::abs(differenceBlock(0))
|
||||
);
|
||||
const double rowDifferenceError =
|
||||
std::abs(actionBlock(0) - differenceBlock(0)) /
|
||||
std::max(rowMagnitude, std::numeric_limits<double>::epsilon());
|
||||
CAPTURE(rowName, actionBlock(0), differenceBlock(0), rowMagnitude,
|
||||
rowDifferenceError);
|
||||
CHECK(rowMagnitude > 1.0e-10);
|
||||
CHECK(rowDifferenceError <= 8.0e-5);
|
||||
};
|
||||
checkGlobalRow(
|
||||
utils::blocks::fixed_total_mass_constraint.mass_normalization_term,
|
||||
"fixed-total-mass"
|
||||
);
|
||||
checkGlobalRow(
|
||||
utils::blocks::fixed_angular_momentum_constraint.angular_velocity_term,
|
||||
"fixed-angular-momentum"
|
||||
);
|
||||
checkGlobalRow(
|
||||
utils::blocks::fixed_central_density_phase.central_value_term,
|
||||
"fixed-central-density phase"
|
||||
);
|
||||
|
||||
auto component = preconditioning::makePreconditioner(problem);
|
||||
STATIC_CHECK(decltype(component)::borderValueArity == 3);
|
||||
STATIC_CHECK(decltype(component)::borderResidualArity == 3);
|
||||
auto physicalInverse = preconditioning::prepare(problem, component);
|
||||
require_all_ranks(
|
||||
physicalInverse.IsCurrent(),
|
||||
communicator,
|
||||
"prepared physical preconditioner currentness"
|
||||
);
|
||||
auto scaledInverse = normalized.MakeScaledPreconditioner(physicalInverse);
|
||||
require_all_ranks(
|
||||
scaledInverse.IsCurrent(),
|
||||
communicator,
|
||||
"prepared normalized preconditioner currentness"
|
||||
);
|
||||
|
||||
mfem::Vector normalizedRightHandSide =
|
||||
make_deterministic_vector(problem.EquationSize(), 0.73);
|
||||
auto rightHandSideView = problem.GetManifest().residualView(normalizedRightHandSide);
|
||||
mfem::Vector massRightHandSide = rightHandSideView.block(
|
||||
utils::blocks::fixed_total_mass_constraint.mass_normalization_term
|
||||
);
|
||||
mfem::Vector angularMomentumRightHandSide = rightHandSideView.block(
|
||||
utils::blocks::fixed_angular_momentum_constraint.angular_velocity_term
|
||||
);
|
||||
mfem::Vector phaseRightHandSide = rightHandSideView.block(
|
||||
utils::blocks::fixed_central_density_phase.central_value_term
|
||||
);
|
||||
massRightHandSide(0) = 0.11;
|
||||
angularMomentumRightHandSide(0) = -0.19;
|
||||
phaseRightHandSide(0) = 0.29;
|
||||
massRightHandSide.SyncAliasMemory(normalizedRightHandSide);
|
||||
angularMomentumRightHandSide.SyncAliasMemory(normalizedRightHandSide);
|
||||
phaseRightHandSide.SyncAliasMemory(normalizedRightHandSide);
|
||||
normalizedRightHandSide /= global_norm(
|
||||
normalizedRightHandSide,
|
||||
finiteElements.mesh->GetComm()
|
||||
);
|
||||
|
||||
mfem::Vector firstCorrection(scaledInverse.Height());
|
||||
mfem::Vector repeatedCorrection(scaledInverse.Height());
|
||||
firstCorrection = 0.0;
|
||||
repeatedCorrection = 0.0;
|
||||
scaledInverse.Mult(normalizedRightHandSide, firstCorrection);
|
||||
scaledInverse.Mult(normalizedRightHandSide, repeatedCorrection);
|
||||
|
||||
mfem::Vector repeatDifference(repeatedCorrection);
|
||||
repeatDifference -= firstCorrection;
|
||||
const double correctionNorm = global_norm(firstCorrection, finiteElements.mesh->GetComm());
|
||||
const double repeatError = global_norm(repeatDifference, finiteElements.mesh->GetComm()) /
|
||||
std::max(correctionNorm, std::numeric_limits<double>::epsilon());
|
||||
CAPTURE(correctionNorm, repeatError);
|
||||
const int locallyFiniteCorrections =
|
||||
vector_is_finite(firstCorrection) && vector_is_finite(repeatedCorrection) ? 1 : 0;
|
||||
int globallyFiniteCorrections = 0;
|
||||
REQUIRE(MPI_Allreduce(
|
||||
&locallyFiniteCorrections,
|
||||
&globallyFiniteCorrections,
|
||||
1,
|
||||
MPI_INT,
|
||||
MPI_MIN,
|
||||
finiteElements.mesh->GetComm()
|
||||
) == MPI_SUCCESS);
|
||||
CHECK(globallyFiniteCorrections == 1);
|
||||
CHECK(std::isfinite(correctionNorm));
|
||||
CHECK(correctionNorm > 0.0);
|
||||
CHECK(repeatError <= 2.0e-12);
|
||||
require_all_ranks(
|
||||
physicalInverse.IsCurrent() && scaledInverse.IsCurrent(),
|
||||
communicator,
|
||||
"preconditioner currentness after repeated application"
|
||||
);
|
||||
|
||||
const mfem::Vector &readOnlyCorrection = firstCorrection;
|
||||
const auto correctionView = problem.GetManifest().stateView(readOnlyCorrection);
|
||||
const double massCorrection = correctionView.block(
|
||||
utils::blocks::fixed_total_mass_constraint.mass_normalization_term
|
||||
)(0);
|
||||
const double angularVelocityCorrection = correctionView.block(
|
||||
utils::blocks::fixed_angular_momentum_constraint.angular_velocity_term
|
||||
)(0);
|
||||
const double phaseCorrection = correctionView.block(
|
||||
utils::blocks::fixed_central_density_phase.central_value_term
|
||||
)(0);
|
||||
CAPTURE(massCorrection, angularVelocityCorrection, phaseCorrection);
|
||||
check_rank_consistent_scalar(
|
||||
massCorrection,
|
||||
finiteElements.mesh->GetComm()
|
||||
);
|
||||
check_rank_consistent_scalar(
|
||||
angularVelocityCorrection,
|
||||
finiteElements.mesh->GetComm()
|
||||
);
|
||||
check_rank_consistent_scalar(
|
||||
phaseCorrection,
|
||||
finiteElements.mesh->GetComm()
|
||||
);
|
||||
|
||||
/* A Newton iteration reparses the same normalized state under fresh
|
||||
* dependency revisions. Every rank must observe the stale inverse, and
|
||||
* refresh must reconstruct the inferred border actions and Schur data. */
|
||||
const auto secondPreparation = normalized.Prepare(
|
||||
normalizedState,
|
||||
make_stellar_dependencies(5)
|
||||
);
|
||||
require_all_ranks(
|
||||
secondPreparation.DidAnyWork() && normalized.IsPrepared(),
|
||||
communicator,
|
||||
"second variadic stellar-root preparation"
|
||||
);
|
||||
mfem::Vector secondPreparedResidual;
|
||||
normalized.BuildResidual(secondPreparedResidual);
|
||||
mfem::Vector secondPreparedResidualDifference(secondPreparedResidual);
|
||||
secondPreparedResidualDifference -= normalizedResidual;
|
||||
const double secondPreparedResidualError = global_norm(
|
||||
secondPreparedResidualDifference,
|
||||
finiteElements.mesh->GetComm()
|
||||
) / std::max({
|
||||
global_norm(secondPreparedResidual, finiteElements.mesh->GetComm()),
|
||||
global_norm(normalizedResidual, finiteElements.mesh->GetComm()),
|
||||
std::numeric_limits<double>::epsilon()
|
||||
});
|
||||
CAPTURE(secondPreparedResidualError);
|
||||
CHECK(secondPreparedResidualError <= 2.0e-12);
|
||||
require_all_ranks(
|
||||
!physicalInverse.IsCurrent() && !scaledInverse.IsCurrent(),
|
||||
communicator,
|
||||
"preconditioners become stale together"
|
||||
);
|
||||
CHECK_THROWS_AS(
|
||||
scaledInverse.Mult(normalizedRightHandSide, repeatedCorrection),
|
||||
std::logic_error
|
||||
);
|
||||
|
||||
const auto refresh = physicalInverse.Refresh();
|
||||
CHECK(refresh.specificationActionsRefreshed);
|
||||
CHECK(refresh.rebuiltSchurComplement);
|
||||
CHECK(refresh.DidAnyWork());
|
||||
require_all_ranks(
|
||||
physicalInverse.IsCurrent() && scaledInverse.IsCurrent(),
|
||||
communicator,
|
||||
"refreshed preconditioner currentness"
|
||||
);
|
||||
const auto noOpRefresh = physicalInverse.Refresh();
|
||||
CHECK_FALSE(noOpRefresh.DidAnyWork());
|
||||
|
||||
mfem::Vector refreshedCorrection(scaledInverse.Height());
|
||||
refreshedCorrection = 0.0;
|
||||
scaledInverse.Mult(normalizedRightHandSide, refreshedCorrection);
|
||||
mfem::Vector refreshDifference(refreshedCorrection);
|
||||
refreshDifference -= firstCorrection;
|
||||
const double refreshError = global_norm(
|
||||
refreshDifference,
|
||||
finiteElements.mesh->GetComm()
|
||||
) / std::max(
|
||||
correctionNorm,
|
||||
std::numeric_limits<double>::epsilon()
|
||||
);
|
||||
const int locallyFiniteRefresh = vector_is_finite(refreshedCorrection) ? 1 : 0;
|
||||
int globallyFiniteRefresh = 0;
|
||||
REQUIRE(MPI_Allreduce(
|
||||
&locallyFiniteRefresh,
|
||||
&globallyFiniteRefresh,
|
||||
1,
|
||||
MPI_INT,
|
||||
MPI_MIN,
|
||||
finiteElements.mesh->GetComm()
|
||||
) == MPI_SUCCESS);
|
||||
CAPTURE(refreshError);
|
||||
CHECK(globallyFiniteRefresh == 1);
|
||||
CHECK(refreshError <= 2.0e-10);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"MPI Prepared Gravity Operators Preserve Global Algebraic Identities",
|
||||
"[mpi][distributed][gravity][operators][unit]"
|
||||
|
||||
828
tests/normalization/physical_riesz.cpp
Normal file
828
tests/normalization/physical_riesz.cpp
Normal file
@@ -0,0 +1,828 @@
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <concepts>
|
||||
#include <limits>
|
||||
#include <span>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
#include <catch2/catch_approx.hpp>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <mfem.hpp>
|
||||
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
|
||||
namespace {
|
||||
namespace blocks = mean_field::utils::blocks;
|
||||
namespace normalization = mean_field::normalization;
|
||||
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>;
|
||||
};
|
||||
|
||||
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 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:
|
||||
struct Parameters final {
|
||||
double target;
|
||||
};
|
||||
|
||||
using ModelDefinition = mean_field::integral::FixedWithPhysicalCoordinate<
|
||||
SelfDescribingMagneticSpecificEnergy,
|
||||
"NormalizationMockMagneticSpecificEnergy",
|
||||
models::DependsOn<blocks::density::mass::value>,
|
||||
models::Affects<blocks::enthalpy::specific::residual>,
|
||||
models::GlobalScalarNormalization<
|
||||
models::PhysicalScaleLaw::dimensionless,
|
||||
models::PhysicalScaleLaw::specific_energy>>;
|
||||
|
||||
explicit constexpr SelfDescribingMagneticSpecificEnergy(const Parameters parameters) noexcept
|
||||
: m_target(parameters.target) {
|
||||
}
|
||||
|
||||
private:
|
||||
double m_target;
|
||||
};
|
||||
|
||||
class MissingGeneratedNormalization final {
|
||||
public:
|
||||
struct Parameters final {
|
||||
double target;
|
||||
};
|
||||
|
||||
using ModelDefinition = mean_field::integral::FixedWithMultiplier<
|
||||
MissingGeneratedNormalization,
|
||||
"NormalizationMockMissing",
|
||||
models::DependsOn<blocks::density::mass::value>,
|
||||
models::Affects<blocks::enthalpy::specific::residual>>;
|
||||
|
||||
explicit constexpr MissingGeneratedNormalization(const Parameters parameters) noexcept
|
||||
: m_target(parameters.target) {
|
||||
}
|
||||
|
||||
private:
|
||||
double m_target;
|
||||
};
|
||||
|
||||
class GeneratedVolumeCoordinateWithoutMetricSource final {
|
||||
public:
|
||||
struct Parameters final {
|
||||
double target;
|
||||
};
|
||||
|
||||
using ModelDefinition = mean_field::integral::FixedWithPhysicalCoordinate<
|
||||
GeneratedVolumeCoordinateWithoutMetricSource,
|
||||
"NormalizationMockVolumeCoordinate",
|
||||
models::DependsOn<blocks::density::mass::value>,
|
||||
models::Affects<blocks::enthalpy::specific::residual>,
|
||||
models::GeneratedNormalization<
|
||||
VolumeSpecificEnergyNormalization,
|
||||
GlobalSpecificEnergyNormalization>>;
|
||||
|
||||
explicit constexpr GeneratedVolumeCoordinateWithoutMetricSource(const Parameters parameters) noexcept
|
||||
: m_target(parameters.target) {
|
||||
}
|
||||
|
||||
private:
|
||||
double m_target;
|
||||
};
|
||||
|
||||
struct MalformedGeneratedNormalization final { };
|
||||
|
||||
class MalformedGeneratedNormalizationConstraint final {
|
||||
public:
|
||||
struct Parameters final {
|
||||
double target;
|
||||
};
|
||||
|
||||
using ModelDefinition = mean_field::integral::FixedWithMultiplier<
|
||||
MalformedGeneratedNormalizationConstraint,
|
||||
"NormalizationMockMalformed",
|
||||
models::DependsOn<blocks::density::mass::value>,
|
||||
models::Affects<blocks::enthalpy::specific::residual>,
|
||||
MalformedGeneratedNormalization>;
|
||||
|
||||
explicit constexpr MalformedGeneratedNormalizationConstraint(const Parameters parameters) noexcept
|
||||
: m_target(parameters.target) {
|
||||
}
|
||||
|
||||
private:
|
||||
double m_target;
|
||||
};
|
||||
|
||||
template <typename Specification>
|
||||
using GeneratedValueBlock = blocks::generated_value_block<
|
||||
models::PhysicalCoordinateFor<Specification>>;
|
||||
|
||||
template <typename Specification>
|
||||
using GeneratedMultiplierBlock = blocks::generated_value_block<
|
||||
models::MultiplierFor<Specification>>;
|
||||
|
||||
template <typename Specification>
|
||||
using GeneratedResidualBlock = blocks::generated_residual_block<
|
||||
models::ResidualFor<Specification>>;
|
||||
|
||||
using SelfDescribingValue = GeneratedValueBlock<SelfDescribingMagneticSpecificEnergy>;
|
||||
using SelfDescribingResidual = GeneratedResidualBlock<SelfDescribingMagneticSpecificEnergy>;
|
||||
using SelfDescribingForm = blocks::block_form<
|
||||
blocks::type_list<SelfDescribingValue>,
|
||||
blocks::type_list<SelfDescribingResidual>>;
|
||||
|
||||
using MissingValue = GeneratedMultiplierBlock<MissingGeneratedNormalization>;
|
||||
using MissingResidual = GeneratedResidualBlock<MissingGeneratedNormalization>;
|
||||
using MissingNormalizationForm = blocks::block_form<
|
||||
blocks::type_list<MissingValue>,
|
||||
blocks::type_list<MissingResidual>>;
|
||||
|
||||
using UnpreparedVolumeValue = GeneratedValueBlock<GeneratedVolumeCoordinateWithoutMetricSource>;
|
||||
using UnpreparedVolumeResidual = GeneratedResidualBlock<GeneratedVolumeCoordinateWithoutMetricSource>;
|
||||
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()),
|
||||
m_matrix(matrix) {
|
||||
}
|
||||
|
||||
void Mult(
|
||||
const mfem::Vector &input,
|
||||
mfem::Vector &output
|
||||
) const override {
|
||||
m_matrix.Mult(input, output);
|
||||
}
|
||||
|
||||
private:
|
||||
mfem::DenseMatrix m_matrix;
|
||||
};
|
||||
|
||||
class DenseInverseSolver final : public mfem::Solver {
|
||||
public:
|
||||
explicit DenseInverseSolver(const mfem::DenseMatrix &inverse)
|
||||
: mfem::Solver(inverse.Height(), inverse.Width()),
|
||||
m_inverse(inverse) {
|
||||
}
|
||||
|
||||
void SetOperator(const mfem::Operator &operation) override {
|
||||
if (operation.Height() != Height() || operation.Width() != Width()) {
|
||||
throw std::invalid_argument("The dense inverse received an incompatible operator.");
|
||||
}
|
||||
m_boundOperator = &operation;
|
||||
++m_bindings;
|
||||
}
|
||||
|
||||
void Mult(
|
||||
const mfem::Vector &input,
|
||||
mfem::Vector &output
|
||||
) const override {
|
||||
if (m_boundOperator == nullptr) {
|
||||
throw std::logic_error("The dense inverse must be bound before application.");
|
||||
}
|
||||
m_inverse.Mult(input, output);
|
||||
}
|
||||
|
||||
[[nodiscard]] const mfem::Operator *BoundOperator() const noexcept {
|
||||
return m_boundOperator;
|
||||
}
|
||||
|
||||
[[nodiscard]] int Bindings() const noexcept {
|
||||
return m_bindings;
|
||||
}
|
||||
|
||||
private:
|
||||
mfem::DenseMatrix m_inverse;
|
||||
const mfem::Operator *m_boundOperator{nullptr};
|
||||
int m_bindings{0};
|
||||
};
|
||||
|
||||
[[nodiscard]] mfem::Vector vector(std::initializer_list<double> values) {
|
||||
mfem::Vector result(static_cast<int>(values.size()));
|
||||
int index = 0;
|
||||
for (const double value : values) {
|
||||
result(index++) = value;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void checkVector(
|
||||
const mfem::Vector &actual,
|
||||
const mfem::Vector &expected,
|
||||
const double epsilon = 2.0e-13
|
||||
) {
|
||||
REQUIRE(actual.Size() == expected.Size());
|
||||
for (int index = 0; index < actual.Size(); ++index) {
|
||||
CHECK(actual(index) == Catch::Approx(expected(index)).epsilon(epsilon).margin(1.0e-300));
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE(
|
||||
"Pointer-Retaining Normalization Operators Reject Temporary Dependencies",
|
||||
"[normalization][type][lifetime]"
|
||||
) {
|
||||
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::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 &&>);
|
||||
}
|
||||
|
||||
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 gravity = 5.0;
|
||||
const auto scales = normalization::deriveStellarCharacteristicScales(
|
||||
dimensions::MassValue{mass}, dimensions::LengthValue{radius}, gravity
|
||||
);
|
||||
|
||||
CHECK(scales.density == Catch::Approx(mass / std::pow(radius, 3)));
|
||||
CHECK(scales.acceleration == Catch::Approx(gravity * mass / std::pow(radius, 2)));
|
||||
CHECK(scales.specificEnergy == Catch::Approx(gravity * mass / radius));
|
||||
CHECK(scales.pressure == Catch::Approx(gravity * mass * mass / std::pow(radius, 4)));
|
||||
CHECK(scales.angularVelocity == Catch::Approx(std::sqrt(gravity * mass / std::pow(radius, 3))));
|
||||
CHECK(scales.angularMomentum == Catch::Approx(mass * std::sqrt(gravity * mass * radius)));
|
||||
|
||||
// Hydrostatic/virial energy scales agree: P R^3 = M Phi = F R.
|
||||
const double virial = scales.pressure * std::pow(radius, 3);
|
||||
CHECK(virial == Catch::Approx(mass * scales.specificEnergy).epsilon(2.0e-15));
|
||||
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));
|
||||
}
|
||||
|
||||
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;
|
||||
constexpr double gravityFactor = 7.0;
|
||||
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));
|
||||
}
|
||||
|
||||
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
|
||||
);
|
||||
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);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Generated Physical Riesz Laws Come From A Self-Describing Physics Specification",
|
||||
"[normalization][type][extension]"
|
||||
) {
|
||||
using namespace mean_field;
|
||||
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(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 blocks::form_layout<SelfDescribingForm> layout({1}, {1});
|
||||
normalization::DiagonalNormalizationBuilder<SelfDescribingForm> builder(layout);
|
||||
normalization::StellarSpecificationNormalizationContribution<
|
||||
SelfDescribingMagneticSpecificEnergy>::Apply(builder, scales);
|
||||
const normalization::DiagonalNormalization map = std::move(builder).Build();
|
||||
|
||||
REQUIRE(map.StateFactors().Size() == 1);
|
||||
REQUIRE(map.ResidualFactors().Size() == 1);
|
||||
CHECK(map.StateFactors()(0) == Catch::Approx(1.0).epsilon(2.0e-15));
|
||||
CHECK(map.ResidualFactors()(0) == Catch::Approx(1.0 / scales.specificEnergy).epsilon(2.0e-15));
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Generated Normalization Completeness Is SFINAE Safe And Rejects Missing Runtime Metrics",
|
||||
"[normalization][type][validation]"
|
||||
) {
|
||||
using namespace mean_field;
|
||||
|
||||
STATIC_CHECK_FALSE(normalization::CompleteGeneratedPhysicalRieszNormalizationFor<int>);
|
||||
STATIC_CHECK_FALSE(normalization::RegisteredStellarSpecificationNormalization<int>);
|
||||
STATIC_CHECK_FALSE(normalization::CompleteStellarNormalizationFor<int, SelfDescribingForm>);
|
||||
|
||||
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(models::ModelSpecification<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>);
|
||||
}
|
||||
|
||||
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
|
||||
),
|
||||
std::invalid_argument
|
||||
);
|
||||
CHECK_THROWS_AS(
|
||||
normalization::deriveStellarCharacteristicScales(
|
||||
dimensions::MassValue{1.0}, dimensions::LengthValue{-1.0}, 1.0
|
||||
),
|
||||
std::invalid_argument
|
||||
);
|
||||
CHECK_THROWS_AS(
|
||||
(normalization::PhysicalRieszDiagonal{dimensions::LengthValue{1.0},
|
||||
std::numeric_limits<double>::quiet_NaN()}),
|
||||
std::invalid_argument
|
||||
);
|
||||
CHECK_THROWS_AS(
|
||||
normalization::deriveStellarCharacteristicScales(
|
||||
dimensions::MassValue{1.0e300}, dimensions::LengthValue{1.0e-200}, 1.0e100
|
||||
),
|
||||
std::overflow_error
|
||||
);
|
||||
}
|
||||
|
||||
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;
|
||||
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;
|
||||
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);
|
||||
}
|
||||
CHECK(map.LocalStateNormSquared(state) == Catch::Approx(expectedPrimalNormSquared).epsilon(3.0e-15));
|
||||
CHECK(map.LocalResidualNormSquared(residual) == Catch::Approx(expectedDualNormSquared).epsilon(3.0e-15));
|
||||
|
||||
mfem::Vector normalizedState;
|
||||
mfem::Vector recoveredState;
|
||||
mfem::Vector normalizedResidual;
|
||||
mfem::Vector recoveredResidual;
|
||||
map.NormalizeState(state, normalizedState);
|
||||
map.DenormalizeState(normalizedState, recoveredState);
|
||||
map.NormalizeResidual(residual, normalizedResidual);
|
||||
map.DenormalizeResidual(normalizedResidual, recoveredResidual);
|
||||
checkVector(recoveredState, state, 3.0e-15);
|
||||
checkVector(recoveredResidual, residual, 3.0e-15);
|
||||
}
|
||||
|
||||
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>>;
|
||||
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}));
|
||||
|
||||
// Replaced isobaric rows may have zero bulk mass because they are no longer volume weak rows.
|
||||
const mfem::Vector bulkMetric = vector({4.0, 0.0, 16.0, 0.0});
|
||||
const std::array<int, 2> pointRows{1, 3};
|
||||
builder.SetHybridResidualBlock<blocks::enthalpy::specific::residual>(
|
||||
5.0, bulkMetric, std::span<const int>{pointRows}, 1.0
|
||||
);
|
||||
const auto map = std::move(builder).Build();
|
||||
CHECK(map.ResidualFactors()(0) == Catch::Approx(1.0 / 10.0));
|
||||
CHECK(map.ResidualFactors()(1) == Catch::Approx(1.0 / 5.0));
|
||||
CHECK(map.ResidualFactors()(2) == Catch::Approx(1.0 / 20.0));
|
||||
CHECK(map.ResidualFactors()(3) == Catch::Approx(1.0 / 5.0));
|
||||
|
||||
normalization::DiagonalNormalizationBuilder<HybridForm> duplicateRows(layout);
|
||||
duplicateRows.SetValueGlobal<blocks::enthalpy::specific::value>(1.0);
|
||||
const std::array<int, 2> duplicates{1, 1};
|
||||
CHECK_THROWS_AS(
|
||||
duplicateRows.SetHybridResidualBlock<blocks::enthalpy::specific::residual>(
|
||||
1.0, vector({1.0, 1.0, 1.0, 1.0}), std::span<const int>{duplicates}
|
||||
),
|
||||
std::invalid_argument
|
||||
);
|
||||
}
|
||||
|
||||
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);
|
||||
missing.SetValueBlock<TestValue>(1.0, vector({1.0, 1.0}));
|
||||
CHECK_THROWS_AS(std::move(missing).Build(), std::logic_error);
|
||||
|
||||
normalization::DiagonalNormalizationBuilder<TestForm> duplicate(layout);
|
||||
duplicate.SetValueBlock<TestValue>(1.0, vector({1.0, 1.0}));
|
||||
CHECK_THROWS_AS(duplicate.SetValueBlock<TestValue>(1.0, vector({1.0, 1.0})), std::logic_error);
|
||||
|
||||
normalization::DiagonalNormalizationBuilder<TestForm> zeroMetric(layout);
|
||||
CHECK_THROWS_AS(zeroMetric.SetValueBlock<TestValue>(1.0, vector({1.0, 0.0})), std::invalid_argument);
|
||||
|
||||
normalization::DiagonalNormalizationBuilder<TestForm> wrongSize(layout);
|
||||
CHECK_THROWS_AS(wrongSize.SetResidualBlock<TestResidual>(1.0, vector({1.0})), std::invalid_argument);
|
||||
|
||||
CHECK_THROWS_AS(
|
||||
normalization::DiagonalNormalization(vector({1.0, std::numeric_limits<double>::infinity()}), vector({1.0})),
|
||||
std::invalid_argument
|
||||
);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// Start from a well-conditioned normalized Jacobian A_hat and form the dimensional
|
||||
// J = L^{-1} A_hat R^{-1}. Its entries span the physical unit ranges, while L J R
|
||||
// must recover A_hat rather than an artificially ill-conditioned dense matrix.
|
||||
constexpr double normalizedMatrix[2][2]{{4.0, 1.0}, {2.0, 3.0}};
|
||||
constexpr double normalizedInverse[2][2]{{0.3, -0.1}, {-0.2, 0.4}};
|
||||
mfem::DenseMatrix matrix(2);
|
||||
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);
|
||||
}
|
||||
}
|
||||
const DenseOperator physicalJacobian(matrix);
|
||||
const DenseOperator physicalInverse(inverse);
|
||||
const normalization::ScaledJacobianOperator scaledJacobian(physicalJacobian, map);
|
||||
const normalization::ScaledInverseOperator scaledInverse(physicalInverse, map);
|
||||
|
||||
const mfem::Vector direction = vector({0.75, -1.25});
|
||||
mfem::Vector action;
|
||||
scaledJacobian.Mult(direction, action);
|
||||
mfem::Vector expected(2);
|
||||
expected(0) = 4.0 * direction(0) + direction(1);
|
||||
expected(1) = 2.0 * direction(0) + 3.0 * direction(1);
|
||||
checkVector(action, expected, 4.0e-15);
|
||||
|
||||
mfem::Vector recovered;
|
||||
scaledInverse.Mult(action, recovered);
|
||||
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});
|
||||
const mfem::Vector residualFactors = vector({5.0e8, 3.0e-6});
|
||||
const normalization::DiagonalNormalization map(stateFactors, residualFactors);
|
||||
|
||||
constexpr double normalizedMatrix[2][2]{{4.0, 1.0}, {2.0, 3.0}};
|
||||
constexpr double normalizedInverse[2][2]{{0.3, -0.1}, {-0.2, 0.4}};
|
||||
mfem::DenseMatrix physicalMatrix(2);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
const DenseOperator physicalJacobian(physicalMatrix);
|
||||
const normalization::ScaledJacobianOperator scaledJacobian(physicalJacobian, map);
|
||||
DenseInverseSolver physicalInverse(physicalInverseMatrix);
|
||||
normalization::ScaledPreconditioner scaledPreconditioner(
|
||||
physicalInverse, physicalJacobian, scaledJacobian, map
|
||||
);
|
||||
|
||||
CHECK(physicalInverse.BoundOperator() == &physicalJacobian);
|
||||
CHECK(&scaledPreconditioner.GetPhysicalJacobian() == &physicalJacobian);
|
||||
CHECK(&scaledPreconditioner.GetNormalizedJacobian() == &scaledJacobian);
|
||||
|
||||
const mfem::Vector rightHandSide = vector({1.5, -0.75});
|
||||
mfem::Vector directCorrection(2);
|
||||
scaledPreconditioner.Mult(rightHandSide, directCorrection);
|
||||
const mfem::Vector expected = vector({0.525, -0.6});
|
||||
checkVector(directCorrection, expected, 3.0e-13);
|
||||
|
||||
mfem::FGMRESSolver krylov(MPI_COMM_WORLD);
|
||||
krylov.SetRelTol(1.0e-13);
|
||||
krylov.SetAbsTol(1.0e-15);
|
||||
krylov.SetMaxIter(4);
|
||||
krylov.SetKDim(2);
|
||||
krylov.SetPrintLevel(0);
|
||||
krylov.SetPreconditioner(scaledPreconditioner);
|
||||
krylov.SetOperator(scaledJacobian);
|
||||
|
||||
mfem::Vector solution(2);
|
||||
solution = 0.0;
|
||||
krylov.Mult(rightHandSide, solution);
|
||||
CHECK(krylov.GetConverged());
|
||||
CHECK(krylov.GetNumIterations() <= 1);
|
||||
checkVector(solution, expected, 3.0e-13);
|
||||
CHECK(physicalInverse.BoundOperator() == &physicalJacobian);
|
||||
CHECK(physicalInverse.Bindings() >= 2);
|
||||
CHECK(scaledPreconditioner.GetStatistics().operatorBindings >= 2);
|
||||
CHECK(scaledPreconditioner.GetStatistics().applications >= 2);
|
||||
|
||||
mfem::IdentityOperator differentNormalizedJacobian(2);
|
||||
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});
|
||||
const mfem::Vector residualFactors = vector({1.0e12, 1.0, 1.0e-12});
|
||||
const normalization::DiagonalNormalization map(stateFactors, residualFactors);
|
||||
|
||||
mfem::DenseMatrix physicalMatrix(3);
|
||||
physicalMatrix = 0.0;
|
||||
for (int index = 0; index < 3; ++index) {
|
||||
physicalMatrix(index, index) = stateFactors(index) / residualFactors(index);
|
||||
}
|
||||
CHECK(physicalMatrix(2, 2) / physicalMatrix(0, 0) == Catch::Approx(1.0e48));
|
||||
|
||||
const DenseOperator physicalJacobian(physicalMatrix);
|
||||
const normalization::ScaledJacobianOperator scaledJacobian(physicalJacobian, map);
|
||||
const mfem::Vector direction = vector({-2.0, 3.5, 0.125});
|
||||
mfem::Vector action;
|
||||
scaledJacobian.Mult(direction, action);
|
||||
checkVector(action, direction, 4.0e-15);
|
||||
}
|
||||
|
||||
TEST_CASE("A Compiled Stellar Problem Prepares Reference Physical Riesz Coordinates", "[normalization][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());
|
||||
|
||||
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(
|
||||
model::StellarModel(
|
||||
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
|
||||
);
|
||||
|
||||
using ProblemType = std::remove_cvref_t<decltype(problem)>;
|
||||
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)>>);
|
||||
CHECK(problem.GetNormalizationPrescription().referenceRadius() == dimensions::LengthValue{referenceRadius});
|
||||
|
||||
const normalization::DiagonalNormalization map = normalization::prepareNormalization(problem);
|
||||
REQUIRE(map.StateSize() == problem.StateSize());
|
||||
REQUIRE(map.ResidualSize() == problem.EquationSize());
|
||||
for (int index = 0; index < map.StateSize(); ++index) {
|
||||
CHECK(std::isfinite(map.StateFactors()(index)));
|
||||
CHECK(map.StateFactors()(index) > 0.0);
|
||||
}
|
||||
for (int index = 0; index < map.ResidualSize(); ++index) {
|
||||
CHECK(std::isfinite(map.ResidualFactors()(index)));
|
||||
CHECK(map.ResidualFactors()(index) > 0.0);
|
||||
}
|
||||
|
||||
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>;
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
mfem::Vector physicalState(problem.StateSize());
|
||||
for (int index = 0; index < physicalState.Size(); ++index) {
|
||||
physicalState(index) = std::sin(0.37 * static_cast<double>(index + 1));
|
||||
}
|
||||
mfem::Vector normalizedState;
|
||||
mfem::Vector recoveredState;
|
||||
map.NormalizeState(physicalState, normalizedState);
|
||||
map.DenormalizeState(normalizedState, recoveredState);
|
||||
checkVector(recoveredState, physicalState, 4.0e-15);
|
||||
|
||||
const normalization::ScaledJacobianOperator scaledJacobian(problem.GetLinearizationOperator(), map);
|
||||
CHECK(scaledJacobian.Width() == problem.StateSize());
|
||||
CHECK(scaledJacobian.Height() == problem.EquationSize());
|
||||
|
||||
// The existing provisional structure preconditioner remains available for this distinct problem type.
|
||||
const auto structureBlock = preconditioning::stellarStructureBlock(problem);
|
||||
STATIC_CHECK(preconditioning::PreconditionerComponent<std::remove_cvref_t<decltype(structureBlock)>>);
|
||||
}
|
||||
228
tests/normalization/plan.cpp
Normal file
228
tests/normalization/plan.cpp
Normal file
@@ -0,0 +1,228 @@
|
||||
#include <concepts>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
import mean_field;
|
||||
|
||||
namespace {
|
||||
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>;
|
||||
|
||||
struct ValueA final : blocks::value_block_base { };
|
||||
struct ValueB final : blocks::value_block_base { };
|
||||
struct ResidualA final : blocks::residual_block_base { };
|
||||
struct ResidualB final : blocks::residual_block_base { };
|
||||
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 ValueAIdentity = normalization::CoordinateComponent<
|
||||
normalization::CoordinateKind::value,
|
||||
blocks::type_list<ValueA>,
|
||||
normalization::IdentityCoordinate>;
|
||||
using ValueBIdentity = normalization::CoordinateComponent<
|
||||
normalization::CoordinateKind::value,
|
||||
blocks::type_list<ValueB>,
|
||||
normalization::IdentityCoordinate>;
|
||||
using ResidualAIdentity = normalization::CoordinateComponent<
|
||||
normalization::CoordinateKind::residual,
|
||||
blocks::type_list<ResidualA>,
|
||||
normalization::IdentityCoordinate>;
|
||||
using ResidualBIdentity = normalization::CoordinateComponent<
|
||||
normalization::CoordinateKind::residual,
|
||||
blocks::type_list<ResidualB>,
|
||||
normalization::IdentityCoordinate>;
|
||||
using ForeignIdentity = normalization::CoordinateComponent<
|
||||
normalization::CoordinateKind::value,
|
||||
blocks::type_list<ForeignValue>,
|
||||
normalization::IdentityCoordinate>;
|
||||
using ForeignResidualIdentity = normalization::CoordinateComponent<
|
||||
normalization::CoordinateKind::residual,
|
||||
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 ForeignSmallPlan = normalization::NormalizationPlan<
|
||||
ValueAIdentity,
|
||||
ValueBIdentity,
|
||||
ForeignIdentity,
|
||||
ResidualAIdentity,
|
||||
ResidualBIdentity,
|
||||
ForeignResidualIdentity>;
|
||||
|
||||
struct MalformedComponent final {
|
||||
using Blocks = blocks::type_list<ValueA>;
|
||||
using Method = normalization::IdentityCoordinate;
|
||||
};
|
||||
|
||||
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 WrongDensityTopology = normalization::CoordinateComponent<
|
||||
normalization::CoordinateKind::value,
|
||||
blocks::type_list<blocks::density::mass::value>,
|
||||
normalization::PhysicalRieszCoordinate<
|
||||
normalization::RieszTopology::vector_volume_l2,
|
||||
normalization::PhysicalScaleKind::density>>;
|
||||
|
||||
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 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 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<
|
||||
mean_field::eos::Polytrope,
|
||||
mean_field::surface::Isobaric,
|
||||
mean_field::integral::FixedTotalMass,
|
||||
mean_field::integral::FixedAngularMomentum>>;
|
||||
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
|
||||
);
|
||||
};
|
||||
} // namespace
|
||||
|
||||
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>);
|
||||
STATIC_CHECK(normalization::CompilableNormalizationFor<normalization::Unnormalized, PhysicalForm>);
|
||||
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::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 Duplicate = normalization::NormalizationCoverage<SmallForm, DuplicateSmallPlan>;
|
||||
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);
|
||||
STATIC_CHECK(blocks::contains_type_v<ValueA, typename Duplicate::RepeatedValueBlocks>);
|
||||
STATIC_CHECK(Foreign::UnexpectedValueBlocks::size == 1);
|
||||
STATIC_CHECK(Foreign::UnexpectedResidualBlocks::size == 1);
|
||||
}
|
||||
|
||||
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 EnthalpyResidual = normalization::PhysicalRieszBlockTraits<blocks::enthalpy::specific::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(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]") {
|
||||
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>);
|
||||
|
||||
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 &&>);
|
||||
STATIC_CHECK_FALSE(std::constructible_from<SmallBuilder, const SmallLayout &&>);
|
||||
}
|
||||
1187
tests/normalization/stellar_equilibrium.cpp
Normal file
1187
tests/normalization/stellar_equilibrium.cpp
Normal file
File diff suppressed because it is too large
Load Diff
531
tests/operators/prepared_angular_momentum.cpp
Normal file
531
tests/operators/prepared_angular_momentum.cpp
Normal file
@@ -0,0 +1,531 @@
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
|
||||
#include <catch2/catch_approx.hpp>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <mfem.hpp>
|
||||
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
|
||||
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}
|
||||
};
|
||||
}
|
||||
|
||||
[[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 gravityPotentialRevision = 17
|
||||
) {
|
||||
return {
|
||||
.discretization = {.value = dependencies.discretization.revision},
|
||||
.displacement = {.value = dependencies.displacement.revision},
|
||||
.density = {.value = dependencies.density.revision},
|
||||
.gravity_gradient = {.value = gravityGradientRevision},
|
||||
.gravity_potential = {.value = gravityPotentialRevision}
|
||||
};
|
||||
}
|
||||
|
||||
void prepareGravityContext(
|
||||
mean_field::operators::context::gravity_field::GravityFieldLinearizationContext &context,
|
||||
const mean_field::fem::FEM &finiteElements,
|
||||
const mfem::Vector &density,
|
||||
const mfem::Vector &displacement,
|
||||
const mean_field::operators::AngularMomentumDependencies &dependencies,
|
||||
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;
|
||||
gravityPotential = 0.0;
|
||||
context.Prepare(
|
||||
{.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)
|
||||
);
|
||||
}
|
||||
|
||||
[[nodiscard]] mfem::Vector projectDensity(
|
||||
const mean_field::fem::FEM &finiteElements,
|
||||
const double phase
|
||||
) {
|
||||
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);
|
||||
});
|
||||
field.ProjectCoefficient(coefficient);
|
||||
mfem::Vector result;
|
||||
field.GetTrueDofs(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] mfem::Vector projectDensityDirection(
|
||||
const mean_field::fem::FEM &finiteElements,
|
||||
const double phase
|
||||
) {
|
||||
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);
|
||||
});
|
||||
field.ProjectCoefficient(coefficient);
|
||||
mfem::Vector result;
|
||||
field.GetTrueDofs(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] mfem::Vector projectConstantDensity(
|
||||
const mean_field::fem::FEM &finiteElements,
|
||||
const double value,
|
||||
mfem::ParGridFunction *fieldOutput = nullptr
|
||||
) {
|
||||
mfem::ParGridFunction field(finiteElements.densityFes.get());
|
||||
mfem::ConstantCoefficient coefficient(value);
|
||||
field.ProjectCoefficient(coefficient);
|
||||
if (fieldOutput != nullptr) {
|
||||
*fieldOutput = field;
|
||||
}
|
||||
mfem::Vector result;
|
||||
field.GetTrueDofs(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] mfem::Vector projectAffineDisplacement(
|
||||
const mean_field::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;
|
||||
}
|
||||
|
||||
[[nodiscard]] mfem::Vector projectDisplacementDirection(
|
||||
const mean_field::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(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));
|
||||
value(2) = scale * (0.04 * position(2) - 0.011 * position(0) * position(1));
|
||||
}
|
||||
);
|
||||
field.ProjectCoefficient(coefficient);
|
||||
mfem::Vector result;
|
||||
field.GetTrueDofs(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] double residual(const mean_field::operators::PreparedAngularMomentumOperator &operation) {
|
||||
mfem::Vector value;
|
||||
operation.BuildResidual(value);
|
||||
REQUIRE(value.Size() == 1);
|
||||
return value(0);
|
||||
}
|
||||
|
||||
[[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()});
|
||||
}
|
||||
} // namespace angular_momentum_test_utils
|
||||
|
||||
TEST_CASE(
|
||||
"Prepared Angular Momentum Satisfies Moment Scaling And The Parallel Axis Theorem",
|
||||
"[fixed-angular-momentum][physics][analytic]"
|
||||
) {
|
||||
using namespace mean_field;
|
||||
using Catch::Approx;
|
||||
using Operator = operators::PreparedAngularMomentumOperator;
|
||||
|
||||
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();
|
||||
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 targetAngularMomentum = 0.41;
|
||||
mfem::ParGridFunction densityField(finiteElements.densityFes.get());
|
||||
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
|
||||
);
|
||||
angular_momentum_test_utils::prepareGravityContext(
|
||||
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
|
||||
);
|
||||
const auto initial = origin.Prepare(angularVelocity, dependencies);
|
||||
CHECK(initial.rebuiltStaticPlan);
|
||||
CHECK(initial.refreshedGeometry);
|
||||
CHECK(initial.refreshedDensity);
|
||||
CHECK(initial.updatedAngularVelocity);
|
||||
CHECK(initial.assembledResidual);
|
||||
|
||||
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));
|
||||
|
||||
const auto report = origin.GetConstraintReport();
|
||||
CHECK(report.targetAngularMomentum == targetAngularMomentum);
|
||||
CHECK(report.achievedAngularMomentum == origin.GetCurrentAngularMomentum());
|
||||
CHECK(report.momentOfInertia == origin.GetMomentOfInertia());
|
||||
CHECK(report.angularVelocity == angularVelocity);
|
||||
const physics::RigidRotation rotation = origin.GetRotation();
|
||||
CHECK(rotation.angular_velocity()(0) == 0.0);
|
||||
CHECK(rotation.angular_velocity()(1) == 0.0);
|
||||
CHECK(rotation.angular_velocity()(2) == angularVelocity);
|
||||
|
||||
constexpr double affineScale = 0.086;
|
||||
const mfem::Vector affineDisplacement =
|
||||
angular_momentum_test_utils::projectAffineDisplacement(finiteElements, affineScale);
|
||||
++dependencies.displacement.revision;
|
||||
angular_momentum_test_utils::prepareGravityContext(
|
||||
gravityContext,
|
||||
finiteElements,
|
||||
density,
|
||||
affineDisplacement,
|
||||
dependencies
|
||||
);
|
||||
const auto affine = origin.Prepare(angularVelocity, dependencies);
|
||||
CHECK(affine.refreshedGeometry);
|
||||
CHECK_FALSE(affine.refreshedDensity);
|
||||
const double expectedAffineRatio = std::pow(1.0 + affineScale, 5);
|
||||
const double measuredAffineRatio = origin.GetMomentOfInertia() / independentMoment;
|
||||
INFO("Expected homothetic I ratio = " << expectedAffineRatio);
|
||||
INFO("Measured homothetic I ratio = " << measuredAffineRatio);
|
||||
CHECK(angular_momentum_test_utils::relativeError(measuredAffineRatio, expectedAffineRatio) < 7.0e-7);
|
||||
|
||||
displacement = 0.0;
|
||||
++dependencies.displacement.revision;
|
||||
angular_momentum_test_utils::prepareGravityContext(
|
||||
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
|
||||
}))
|
||||
);
|
||||
shifted.Prepare(angularVelocity, dependencies);
|
||||
|
||||
const double mass = analysis::domain_integrate_grid_function(
|
||||
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));
|
||||
CHECK(angular_momentum_test_utils::relativeError(shifted.GetMomentOfInertia(), expectedShiftedMoment) < 3.0e-13);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Prepared Angular Momentum Jacobian Matches Density Geometry And Angular Velocity Differences",
|
||||
"[fixed-angular-momentum][jacobian][accuracy]"
|
||||
) {
|
||||
using namespace mean_field;
|
||||
|
||||
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 displacementDirection =
|
||||
angular_momentum_test_utils::projectDisplacementDirection(finiteElements, -0.79);
|
||||
constexpr double angularVelocity = 0.63;
|
||||
constexpr double angularVelocityDirection = -0.37;
|
||||
|
||||
auto dependencies = angular_momentum_test_utils::makeDependencies();
|
||||
operators::context::gravity_field::GravityFieldLinearizationContext gravityContext(
|
||||
finiteElements,
|
||||
*finiteElements.domainMapperStateless
|
||||
);
|
||||
angular_momentum_test_utils::prepareGravityContext(
|
||||
gravityContext,
|
||||
finiteElements,
|
||||
density,
|
||||
displacement,
|
||||
dependencies
|
||||
);
|
||||
operators::PreparedAngularMomentumOperator operation(
|
||||
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);
|
||||
mfem::Vector densityAction;
|
||||
mfem::Vector geometryAction;
|
||||
mfem::Vector angularVelocityAction;
|
||||
mfem::Vector completeAction;
|
||||
operation.ApplyDensityJacobianAction(reducedDensityDirection, densityAction);
|
||||
operation.ApplyDisplacementJacobianAction(reducedDisplacementDirection, geometryAction);
|
||||
operation.ApplyAngularVelocityJacobianAction(angularVelocityDirection, angularVelocityAction);
|
||||
operation.ApplyCompleteJacobianAction(
|
||||
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));
|
||||
|
||||
constexpr double angularStep = 1.0e-6;
|
||||
++dependencies.rotation.revision;
|
||||
operation.Prepare(angularVelocity + angularStep * angularVelocityDirection, dependencies);
|
||||
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 angularDifference = (angularPlus - angularMinus) / (2.0 * angularStep);
|
||||
CHECK(angular_momentum_test_utils::relativeError(angularVelocityAction(0), angularDifference) < 2.0e-10);
|
||||
|
||||
constexpr double densityStep = 1.0e-3;
|
||||
mfem::Vector densityPlus(density);
|
||||
densityPlus.Add(densityStep, densityDirection);
|
||||
++dependencies.density.revision;
|
||||
angular_momentum_test_utils::prepareGravityContext(
|
||||
gravityContext,
|
||||
finiteElements,
|
||||
densityPlus,
|
||||
displacement,
|
||||
dependencies
|
||||
);
|
||||
operation.Prepare(angularVelocity, dependencies);
|
||||
const double densityPlusResidual = angular_momentum_test_utils::residual(operation);
|
||||
mfem::Vector densityMinus(density);
|
||||
densityMinus.Add(-densityStep, densityDirection);
|
||||
++dependencies.density.revision;
|
||||
angular_momentum_test_utils::prepareGravityContext(
|
||||
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);
|
||||
CHECK(angular_momentum_test_utils::relativeError(densityAction(0), densityDifference) < 4.0e-8);
|
||||
|
||||
constexpr double geometryStep = 1.0e-6;
|
||||
mfem::Vector displacementPlus(displacement);
|
||||
displacementPlus.Add(geometryStep, displacementDirection);
|
||||
++dependencies.density.revision;
|
||||
++dependencies.displacement.revision;
|
||||
angular_momentum_test_utils::prepareGravityContext(
|
||||
gravityContext,
|
||||
finiteElements,
|
||||
density,
|
||||
displacementPlus,
|
||||
dependencies
|
||||
);
|
||||
operation.Prepare(angularVelocity, dependencies);
|
||||
const double geometryPlusResidual = angular_momentum_test_utils::residual(operation);
|
||||
mfem::Vector displacementMinus(displacement);
|
||||
displacementMinus.Add(-geometryStep, displacementDirection);
|
||||
++dependencies.displacement.revision;
|
||||
angular_momentum_test_utils::prepareGravityContext(
|
||||
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));
|
||||
CHECK(angular_momentum_test_utils::relativeError(geometryAction(0), geometryDifference) < 4.0e-7);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Prepared Angular Momentum Refreshes Only Changed Runtime Data",
|
||||
"[fixed-angular-momentum][prepared][lifecycle]"
|
||||
) {
|
||||
using namespace mean_field;
|
||||
|
||||
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();
|
||||
std::uint64_t gravityPotentialRevision = 17;
|
||||
|
||||
operators::context::gravity_field::GravityFieldLinearizationContext gravityContext(
|
||||
finiteElements,
|
||||
*finiteElements.domainMapperStateless
|
||||
);
|
||||
angular_momentum_test_utils::prepareGravityContext(
|
||||
gravityContext,
|
||||
finiteElements,
|
||||
density,
|
||||
displacement,
|
||||
dependencies,
|
||||
13,
|
||||
gravityPotentialRevision
|
||||
);
|
||||
operators::PreparedAngularMomentumOperator operation(
|
||||
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 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
|
||||
);
|
||||
const auto unrelatedPotential = operation.Prepare(0.52, dependencies);
|
||||
CHECK_FALSE(unrelatedPotential.DidAnyWork());
|
||||
|
||||
const double residualBeforeRotation = angular_momentum_test_utils::residual(operation);
|
||||
++dependencies.rotation.revision;
|
||||
const auto rotationOnly = operation.Prepare(0.81, dependencies);
|
||||
CHECK(rotationOnly.updatedAngularVelocity);
|
||||
CHECK(rotationOnly.assembledResidual);
|
||||
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));
|
||||
|
||||
density = angular_momentum_test_utils::projectDensity(finiteElements, 0.83);
|
||||
++dependencies.density.revision;
|
||||
angular_momentum_test_utils::prepareGravityContext(
|
||||
gravityContext,
|
||||
finiteElements,
|
||||
density,
|
||||
displacement,
|
||||
dependencies,
|
||||
13,
|
||||
gravityPotentialRevision
|
||||
);
|
||||
const auto densityOnly = operation.Prepare(0.81, dependencies);
|
||||
CHECK(densityOnly.refreshedDensity);
|
||||
CHECK_FALSE(densityOnly.refreshedGeometry);
|
||||
CHECK_FALSE(densityOnly.updatedAngularVelocity);
|
||||
|
||||
displacement = angular_momentum_test_utils::projectDisplacementDirection(finiteElements, 0.87);
|
||||
++dependencies.displacement.revision;
|
||||
angular_momentum_test_utils::prepareGravityContext(
|
||||
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);
|
||||
}
|
||||
@@ -44,20 +44,11 @@ namespace {
|
||||
} // namespace
|
||||
|
||||
TEST_CASE(
|
||||
"Central Density Bordered Root Preserves The Physical Operator Prefix",
|
||||
"Central Density Contribution Composes Through The Variadic Root",
|
||||
tags::central_density_phase_integration
|
||||
) {
|
||||
using namespace mean_field;
|
||||
|
||||
STATIC_CHECK_FALSE(
|
||||
std::same_as<
|
||||
operators::PreparedStellarEquilibriumOperator, operators::PreparedCentralDensityStellarEquilibriumOperator>
|
||||
);
|
||||
STATIC_CHECK(
|
||||
operators::CentralDensityStellarEquilibriumSpecificationModel::compilationClass ==
|
||||
models::ModelCompilationClass::isolated_root
|
||||
);
|
||||
|
||||
utils::Args args = test_utils::setup_args();
|
||||
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
|
||||
REQUIRE(f.okay());
|
||||
@@ -80,7 +71,8 @@ TEST_CASE(
|
||||
STATIC_CHECK(
|
||||
std::same_as<
|
||||
typename std::remove_cvref_t<decltype(equilibriumProblem)>::PreparedOperatorType,
|
||||
operators::PreparedCentralDensityStellarEquilibriumOperator>
|
||||
operators::PreparedVariadicStellarEquilibriumOperator<
|
||||
typename std::remove_cvref_t<decltype(equilibriumProblem)>::ModelType>>
|
||||
);
|
||||
CHECK(
|
||||
equilibriumProblem.GetStellarModel().specification<constraint::FixedCentralDensity>().targetDensity() ==
|
||||
@@ -95,19 +87,21 @@ TEST_CASE(
|
||||
CHECK(borderedOperator.GetRootManifest().constraints().size() == 3);
|
||||
CHECK(borderedOperator.GetRootManifest().specificationDescriptors().size() == 4);
|
||||
|
||||
const auto constraints = borderedOperator.GetRootManifest().constraints();
|
||||
CHECK(constraints[2].stableId == "FixedCentralDensity");
|
||||
CHECK(constraints[2].role == models::SpecificationRole::phase_condition);
|
||||
CHECK(constraints[2].columnPolicy == operators::RootColumnPolicy::solver_border);
|
||||
CHECK(constraints[2].target == 1.0);
|
||||
REQUIRE(constraints[2].carrierTarget.has_value());
|
||||
CHECK(*constraints[2].carrierTarget == 1.0);
|
||||
CHECK(constraints[2].targetUnits == "density");
|
||||
CHECK(constraints[2].residualUnits == "specific_enthalpy");
|
||||
const auto ¢ralDescriptor =
|
||||
borderedOperator.GetRootManifest().specification<constraint::FixedCentralDensity>();
|
||||
CHECK(centralDescriptor.stableId == "FixedCentralDensity");
|
||||
CHECK(centralDescriptor.role == models::SpecificationRole::phase_condition);
|
||||
CHECK(centralDescriptor.columnPolicy == operators::RootColumnPolicy::solver_border);
|
||||
CHECK(centralDescriptor.target == 1.0);
|
||||
REQUIRE(centralDescriptor.carrierTarget.has_value());
|
||||
CHECK(*centralDescriptor.carrierTarget == 1.0);
|
||||
CHECK(centralDescriptor.targetUnits == "density");
|
||||
CHECK(centralDescriptor.residualUnits == "specific_enthalpy");
|
||||
|
||||
mfem::Vector physicalState(physicalOperator.Width());
|
||||
physicalState = 0.0;
|
||||
const auto physicalStateView = physicalOperator.GetRootStateView(physicalState);
|
||||
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;
|
||||
|
||||
@@ -118,10 +112,9 @@ TEST_CASE(
|
||||
const operators::StellarEquilibriumDependencies dependencies = make_dependencies();
|
||||
const physics::RigidRotation rotation = make_zero_rotation();
|
||||
physicalOperator.Prepare(physicalState, dependencies, rotation);
|
||||
const operators::PreparedCentralDensityStellarEquilibriumReport initialReport =
|
||||
equilibriumProblem.Prepare(borderedState, dependencies, rotation);
|
||||
const auto initialReport = equilibriumProblem.Prepare(borderedState, dependencies, rotation);
|
||||
CHECK(initialReport.physical.assembledResidual);
|
||||
CHECK(initialReport.phase.assembledResidual);
|
||||
CHECK(initialReport.specification<constraint::FixedCentralDensity>().constraint.assembledResidual);
|
||||
CHECK(initialReport.assembledResidual);
|
||||
|
||||
mfem::Vector physicalResidual;
|
||||
@@ -164,13 +157,13 @@ TEST_CASE(
|
||||
|
||||
const auto repeatedReport = borderedOperator.Prepare(borderedState, dependencies, rotation);
|
||||
CHECK_FALSE(repeatedReport.physical.DidAnyWork());
|
||||
CHECK_FALSE(repeatedReport.phase.DidAnyWork());
|
||||
CHECK_FALSE(repeatedReport.assembledResidual);
|
||||
CHECK_FALSE(repeatedReport.specification<constraint::FixedCentralDensity>().DidAnyWork());
|
||||
CHECK(repeatedReport.assembledResidual);
|
||||
|
||||
borderedState(borderedState.Size() - 1) = 0.375;
|
||||
const auto borderReport = borderedOperator.Prepare(borderedState, dependencies, rotation);
|
||||
CHECK_FALSE(borderReport.physical.DidAnyWork());
|
||||
CHECK(borderReport.phase.refreshedBorder);
|
||||
CHECK(borderReport.specification<constraint::FixedCentralDensity>().constraint.refreshedBorder);
|
||||
CHECK(borderReport.assembledResidual);
|
||||
|
||||
mfem::Vector borderOnlyDirection(borderedOperator.Width());
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
2409
tests/operators/stellar_equilibrium_compiler.cpp
Normal file
2409
tests/operators/stellar_equilibrium_compiler.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,7 @@
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <concepts>
|
||||
#include <numbers>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
@@ -10,6 +11,83 @@
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
|
||||
namespace outer_manifest_report_test {
|
||||
template <mean_field::model::StellarModelType Model>
|
||||
class PreparedEarlierMultiplier;
|
||||
|
||||
class EarlierMultiplier final {
|
||||
public:
|
||||
struct Parameters final {
|
||||
mean_field::dimensions::SpecificEnergyValue target;
|
||||
};
|
||||
|
||||
using ModelDefinition = mean_field::integral::FixedWithMultiplier<
|
||||
EarlierMultiplier,
|
||||
"AardvarkOuterManifestMultiplier",
|
||||
mean_field::models::DependsOn<mean_field::models::stellar::state::Density>,
|
||||
mean_field::models::Affects<mean_field::models::stellar::equation::HydrostaticBalance>,
|
||||
mean_field::models::GlobalScalarNormalization<
|
||||
mean_field::models::PhysicalScaleLaw::specific_energy,
|
||||
mean_field::models::PhysicalScaleLaw::specific_energy>,
|
||||
mean_field::models::GeneratedManifest<
|
||||
"aardvark_outer_manifest.value",
|
||||
"a",
|
||||
"aardvark_outer_manifest.residual",
|
||||
"R_a",
|
||||
"specific_energy",
|
||||
"specific_energy">>;
|
||||
using EquilibriumPhysics =
|
||||
mean_field::operators::SpecificationEquilibriumPhysics<
|
||||
PreparedEarlierMultiplier>;
|
||||
|
||||
explicit EarlierMultiplier(const Parameters parameters) noexcept
|
||||
: m_target(parameters.target) {
|
||||
}
|
||||
|
||||
[[nodiscard]] mean_field::dimensions::SpecificEnergyValue target() const noexcept {
|
||||
return m_target;
|
||||
}
|
||||
|
||||
private:
|
||||
mean_field::dimensions::SpecificEnergyValue m_target;
|
||||
};
|
||||
|
||||
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 {
|
||||
return {};
|
||||
}
|
||||
|
||||
template <typename Equation, typename Row>
|
||||
[[nodiscard]] mean_field::stellar::StructuralZero AddResidual(
|
||||
Equation,
|
||||
Row &
|
||||
) const noexcept {
|
||||
return mean_field::stellar::structuralZero;
|
||||
}
|
||||
|
||||
template <typename Equation, typename State, typename Direction, typename Row>
|
||||
[[nodiscard]] mean_field::stellar::StructuralZero AddJacobianAction(
|
||||
mean_field::stellar::Derivative<Equation, State>,
|
||||
const Direction &,
|
||||
Row &
|
||||
) const noexcept {
|
||||
return mean_field::stellar::zeroDerivative;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsPrepared() const noexcept {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
} // namespace outer_manifest_report_test
|
||||
|
||||
namespace {
|
||||
using BaseModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
|
||||
mean_field::eos::Polytrope,
|
||||
@@ -22,9 +100,30 @@ namespace {
|
||||
mean_field::integral::FixedTotalMass,
|
||||
mean_field::constraint::FixedCentralDensity>>;
|
||||
|
||||
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 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>>;
|
||||
|
||||
template <typename Candidate>
|
||||
concept HasLegacyNumericalModelAdapter = requires { typename Candidate::NumericalModelAdapter; };
|
||||
|
||||
@@ -58,6 +157,7 @@ namespace {
|
||||
difference -= right;
|
||||
return difference.Norml2() / std::max({1.0, left.Norml2(), right.Norml2()});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE(
|
||||
@@ -68,26 +168,65 @@ TEST_CASE(
|
||||
|
||||
using BaseProblem = equilibrium::StellarEquilibriumProblem<BaseModel>;
|
||||
using CentralDensityProblem = equilibrium::StellarEquilibriumProblem<CentralDensityModel>;
|
||||
using AngularMomentumProblem = equilibrium::StellarEquilibriumProblem<AngularMomentumModel>;
|
||||
using AngularMomentumCentralDensityProblem =
|
||||
equilibrium::StellarEquilibriumProblem<AngularMomentumCentralDensityModel>;
|
||||
|
||||
STATIC_CHECK(equilibrium::StellarEquilibriumModel<BaseModel>);
|
||||
STATIC_CHECK(equilibrium::StellarEquilibriumModel<CentralDensityModel>);
|
||||
STATIC_CHECK(equilibrium::StellarEquilibriumModel<AngularMomentumModel>);
|
||||
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(std::same_as<BaseProblem, CentralDensityProblem>);
|
||||
STATIC_CHECK(BaseProblem::symbolicallySquare);
|
||||
STATIC_CHECK(CentralDensityProblem::symbolicallySquare);
|
||||
STATIC_CHECK_FALSE(BaseProblem::hasFixedCentralDensity);
|
||||
STATIC_CHECK(CentralDensityProblem::hasFixedCentralDensity);
|
||||
STATIC_CHECK(AngularMomentumProblem::hasFixedAngularMomentum);
|
||||
STATIC_CHECK_FALSE(AngularMomentumProblem::hasFixedCentralDensity);
|
||||
STATIC_CHECK(AngularMomentumCentralDensityProblem::hasFixedAngularMomentum);
|
||||
STATIC_CHECK(AngularMomentumCentralDensityProblem::hasFixedCentralDensity);
|
||||
STATIC_CHECK_FALSE(HasLegacyNumericalModelAdapter<BaseProblem>);
|
||||
STATIC_CHECK_FALSE(HasLegacyNumericalModelAdapter<CentralDensityProblem>);
|
||||
STATIC_CHECK(std::same_as<BaseProblem, equilibrium::StellarEquilibriumSystem<BaseModel>>);
|
||||
STATIC_CHECK(
|
||||
std::same_as<typename BaseProblem::PreparedOperatorType, operators::PreparedStellarEquilibriumOperator>
|
||||
std::same_as<
|
||||
typename BaseProblem::PreparedOperatorType,
|
||||
operators::PreparedVariadicStellarEquilibriumOperator<BaseModel>>
|
||||
);
|
||||
STATIC_CHECK(
|
||||
std::same_as<
|
||||
typename CentralDensityProblem::PreparedOperatorType,
|
||||
operators::PreparedCentralDensityStellarEquilibriumOperator>
|
||||
operators::PreparedVariadicStellarEquilibriumOperator<CentralDensityModel>>
|
||||
);
|
||||
STATIC_CHECK(
|
||||
std::same_as<
|
||||
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(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::CompiledSurfaceConstraintType,
|
||||
@@ -97,6 +236,183 @@ TEST_CASE(
|
||||
STATIC_CHECK(material::CompiledThermodynamicEquations<typename BaseProblem::ThermodynamicEquationsType>);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Fixed Angular Momentum Root Uses Its Generated Angular Velocity In Every Physical Row",
|
||||
"[fixed-angular-momentum][stellar-equilibrium][jacobian][integration]"
|
||||
) {
|
||||
using namespace mean_field;
|
||||
|
||||
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 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(
|
||||
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}
|
||||
})
|
||||
);
|
||||
auto problem = equilibrium::discretize(model, finiteElements);
|
||||
auto projected = seed::makeProjectedEquilibriumState(
|
||||
problem,
|
||||
seed::LaneEmden({
|
||||
.centralDensity = dimensions::DensityValue{seedCentralDensity},
|
||||
.radialSampleCount = 1024
|
||||
})
|
||||
);
|
||||
auto dependencies = make_dependencies();
|
||||
|
||||
const auto preparation = problem.Prepare(projected.values, dependencies);
|
||||
CHECK(preparation.generatedPhysicalControl);
|
||||
CHECK(preparation.physical.DidAnyWork());
|
||||
CHECK(preparation.template specification<models::FixedAngularMomentum>().constraint.DidAnyWork());
|
||||
CHECK(preparation.template specification<models::FixedAngularMomentum>().generatedRotation);
|
||||
CHECK(problem.IsPrepared());
|
||||
const auto angularReport = problem.GetPreparedOperator().GetAngularMomentumReport();
|
||||
CHECK(angularReport.targetAngularMomentum == targetAngularMomentum);
|
||||
CHECK(angularReport.angularVelocity > 0.0);
|
||||
CHECK(angularReport.momentOfInertia > 0.0);
|
||||
CHECK(std::abs(angularReport.scaledResidual) < 7.0e-4);
|
||||
|
||||
mfem::Vector direction(problem.StateSize());
|
||||
direction = 0.0;
|
||||
mfem::Vector angularVelocityDirection = problem.GetManifest().stateView(direction).block(
|
||||
utils::blocks::fixed_angular_momentum_constraint.angular_velocity_term
|
||||
);
|
||||
REQUIRE(angularVelocityDirection.Size() == 1);
|
||||
angularVelocityDirection(0) = -0.37;
|
||||
angularVelocityDirection.SyncAliasMemory(direction);
|
||||
|
||||
mfem::Vector analyticAction;
|
||||
problem.ApplyLinearization(direction, analyticAction);
|
||||
|
||||
constexpr double step = 1.0e-5;
|
||||
mfem::Vector plusState(projected.values);
|
||||
plusState.Add(step, direction);
|
||||
problem.Prepare(plusState, dependencies);
|
||||
mfem::Vector plusResidual;
|
||||
problem.BuildResidual(plusResidual);
|
||||
mfem::Vector minusState(projected.values);
|
||||
minusState.Add(-step, direction);
|
||||
problem.Prepare(minusState, dependencies);
|
||||
mfem::Vector minusResidual;
|
||||
problem.BuildResidual(minusResidual);
|
||||
plusResidual -= minusResidual;
|
||||
plusResidual /= 2.0 * step;
|
||||
|
||||
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 difference = differenceView.block(term);
|
||||
return relative_difference(analytic, difference);
|
||||
};
|
||||
|
||||
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);
|
||||
INFO("Generated-Omega surface-row centered-difference error = " << surfaceError);
|
||||
INFO("Generated-Omega hydrostatic-row centered-difference error = " << enthalpyError);
|
||||
INFO("Generated-Omega invariant-row centered-difference error = " << angularMomentumError);
|
||||
CHECK(surfaceError < 3.0e-7);
|
||||
CHECK(enthalpyError < 3.0e-7);
|
||||
CHECK(angularMomentumError < 3.0e-10);
|
||||
CHECK(analyticView.block(utils::blocks::surface_deformation_field.shape_equilibrium_term).Norml2() > 0.0);
|
||||
CHECK(analyticView.block(utils::blocks::enthalpy_field.specific_term).Norml2() > 0.0);
|
||||
CHECK(analyticView.block(utils::blocks::fixed_angular_momentum_constraint.angular_velocity_term).Norml2() > 0.0);
|
||||
CHECK(analyticView.block(utils::blocks::gravity_field.gradient_term).Norml2() == 0.0);
|
||||
CHECK(analyticView.block(utils::blocks::gravity_field.poisson_term).Norml2() == 0.0);
|
||||
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);
|
||||
|
||||
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);
|
||||
mfem::Vector zeroState(projected.values);
|
||||
zeroProblem.GetManifest().stateView(zeroState).block(
|
||||
utils::blocks::fixed_angular_momentum_constraint.angular_velocity_term
|
||||
) = 0.0;
|
||||
zeroProblem.Prepare(zeroState, dependencies);
|
||||
mfem::Vector zeroAction;
|
||||
zeroProblem.ApplyLinearization(direction, zeroAction);
|
||||
auto zeroView = zeroProblem.GetManifest().residualView(zeroAction);
|
||||
CHECK(zeroView.block(utils::blocks::surface_deformation_field.shape_equilibrium_term).Norml2() == 0.0);
|
||||
CHECK(zeroView.block(utils::blocks::enthalpy_field.specific_term).Norml2() == 0.0);
|
||||
CHECK(zeroView.block(utils::blocks::fixed_angular_momentum_constraint.angular_velocity_term).Norml2() > 0.0);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Fixed Mass Reports Use The Inferred Outer Manifest Indices",
|
||||
"[stellar-equilibrium][manifest][runtime][ordering]"
|
||||
) {
|
||||
using namespace mean_field;
|
||||
using Form = operators::CompiledStellarEquilibriumForm<EarlierMultiplierModel>;
|
||||
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);
|
||||
|
||||
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}}),
|
||||
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{1.25}})
|
||||
);
|
||||
auto problem = equilibrium::discretize(model, 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;
|
||||
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 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);
|
||||
CHECK(report.descriptor.valueBlock == 6);
|
||||
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);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Discretized Stellar Equilibrium Problem Is Exactly Equivalent To The Legacy Construction Path",
|
||||
tags::stellar_equilibrium_problem_integration
|
||||
@@ -122,6 +438,7 @@ TEST_CASE(
|
||||
discretization
|
||||
);
|
||||
auto &modelDrivenOperator = equilibriumProblem.GetPreparedOperator();
|
||||
const auto &physicalOperator = equilibriumProblem.GetPhysicalOperator();
|
||||
|
||||
CHECK(equilibriumProblem.StateSize() == legacyOperator.Width());
|
||||
CHECK(equilibriumProblem.EquationSize() == legacyOperator.Height());
|
||||
@@ -129,16 +446,16 @@ TEST_CASE(
|
||||
CHECK(&equilibriumProblem.GetDiscretization().finiteElementModel() == &f);
|
||||
CHECK(&equilibriumProblem.GetDiscretization().domainMapper() == f.domainMapperStateless.get());
|
||||
CHECK(equilibriumProblem.GetDiscretization().isCurrent());
|
||||
CHECK(modelDrivenOperator.GetTargetMass() == 1.25);
|
||||
CHECK(modelDrivenOperator.GetSurfaceConstraintOperator().GetPhysicalCondition().targetPressure == 0.0);
|
||||
CHECK(physicalOperator.GetTargetMass() == 1.25);
|
||||
CHECK(physicalOperator.GetSurfaceConstraintOperator().GetPhysicalCondition().targetPressure == 0.0);
|
||||
CHECK(equilibriumProblem.GetCompiledSurfaceConstraint().targetPressure() == dimensions::PressureValue{0.0});
|
||||
CHECK(modelDrivenOperator.GetDomainDeformation().matchesCurrentDiscretization());
|
||||
CHECK(physicalOperator.GetDomainDeformation().matchesCurrentDiscretization());
|
||||
CHECK(&equilibriumProblem.GetLinearizationOperator() == &modelDrivenOperator);
|
||||
CHECK(equilibriumProblem.GetManifest().constraints()[0].target == 1.25);
|
||||
CHECK(equilibriumProblem.GetManifest().template specification<models::FixedTotalMass>().target == 1.25);
|
||||
|
||||
mfem::Vector state(legacyOperator.Width());
|
||||
state = 0.0;
|
||||
const auto stateView = legacyOperator.GetRootStateView(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;
|
||||
|
||||
@@ -163,3 +480,65 @@ TEST_CASE(
|
||||
equilibriumProblem.ApplyLinearization(direction, modelDrivenAction);
|
||||
CHECK(relative_difference(modelDrivenAction, legacyAction) < 2.0e-15);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Fixed Angular Momentum Composes With The Optional Central Density Phase At Runtime",
|
||||
"[fixed-angular-momentum][central-density][stellar-equilibrium][integration]"
|
||||
) {
|
||||
using namespace mean_field;
|
||||
|
||||
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}}),
|
||||
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);
|
||||
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;
|
||||
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_angular_momentum_constraint.angular_velocity_term) = 0.4;
|
||||
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());
|
||||
CHECK(report.template specification<models::FixedAngularMomentum>().constraint.DidAnyWork());
|
||||
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");
|
||||
|
||||
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_central_density_phase.central_value_term)(0)));
|
||||
|
||||
mfem::Vector direction(problem.StateSize());
|
||||
for (int index = 0; index < direction.Size(); ++index) {
|
||||
direction(index) = 0.01 * std::sin(0.17 * static_cast<double>(index + 1));
|
||||
}
|
||||
mfem::Vector action;
|
||||
problem.ApplyLinearization(direction, action);
|
||||
REQUIRE(action.Size() == problem.EquationSize());
|
||||
for (int index = 0; index < action.Size(); ++index) {
|
||||
CHECK(std::isfinite(action(index)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <numbers>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <mfem.hpp>
|
||||
@@ -56,6 +57,33 @@ namespace {
|
||||
blocks::type_list<>,
|
||||
preconditioning::IdentityOperatorCharacteristics,
|
||||
preconditioning::backend::Identity>;
|
||||
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>;
|
||||
|
||||
template <typename Problem, typename 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));
|
||||
};
|
||||
|
||||
template <typename Problem, typename Block>
|
||||
concept CanPrepareStellarPreconditionerFromConstTemporary = requires(Block block) {
|
||||
preconditioning::prepare(std::declval<const Problem &&>(), std::move(block));
|
||||
};
|
||||
|
||||
[[nodiscard]] blocks::form_layout<Form> makeUnevenLayout() {
|
||||
return {
|
||||
@@ -102,6 +130,12 @@ TEST_CASE(
|
||||
) {
|
||||
STATIC_CHECK(preconditioning::EquilibriumCoordinateComponentFor<GroupedComponent, Form>);
|
||||
STATIC_CHECK_FALSE(preconditioning::EquilibriumCoordinateComponentFor<IncompleteComponent, Form>);
|
||||
STATIC_CHECK(CanPrepareStellarPreconditioner<LifetimeProblem, LifetimeBlock>);
|
||||
STATIC_CHECK_FALSE(CanPrepareStellarPreconditionerFromTemporary<LifetimeProblem, LifetimeBlock>);
|
||||
STATIC_CHECK_FALSE(CanPrepareStellarPreconditionerFromConstTemporary<LifetimeProblem, LifetimeBlock>);
|
||||
STATIC_CHECK(std::constructible_from<LifetimePrepared, const LifetimeProblem &, LifetimeBlock>);
|
||||
STATIC_CHECK_FALSE(std::constructible_from<LifetimePrepared, LifetimeProblem &&, LifetimeBlock>);
|
||||
STATIC_CHECK_FALSE(std::constructible_from<LifetimePrepared, const LifetimeProblem &&, LifetimeBlock>);
|
||||
|
||||
const auto layout = makeUnevenLayout();
|
||||
preconditioning::EquilibriumPreconditionerCoordinateMap<Form, GroupedComponent> coordinates(layout);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <concepts>
|
||||
#include <memory>
|
||||
#include <numbers>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
@@ -13,6 +14,130 @@
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
|
||||
namespace material_surface_runtime_contract_test {
|
||||
struct RegisteredAlternateEquationOfState final {
|
||||
struct Parameters final { };
|
||||
|
||||
using ModelDefinition = mean_field::models::ConstitutiveLaw<
|
||||
RegisteredAlternateEquationOfState,
|
||||
"RegisteredAlternateMaterialSurfaceEquationOfState">;
|
||||
using Relations = mean_field::eos::RelationCatalog<mean_field::eos::SpecificEnthalpyFromPressure>;
|
||||
|
||||
explicit RegisteredAlternateEquationOfState(Parameters) noexcept { }
|
||||
|
||||
[[nodiscard]] mean_field::dimensions::SpecificEnthalpyValue evaluate(
|
||||
mean_field::eos::SpecificEnthalpyFromPressure,
|
||||
mean_field::dimensions::PressureValue
|
||||
) const;
|
||||
};
|
||||
|
||||
class AlternatePhysicalCore final : public mfem::Operator {
|
||||
public:
|
||||
using BackendSpecifications = mean_field::models::ModelTypeList<
|
||||
RegisteredAlternateEquationOfState,
|
||||
mean_field::surface::Isobaric,
|
||||
mean_field::models::FixedTotalMass>;
|
||||
|
||||
using mfem::Operator::Operator;
|
||||
|
||||
void Mult(const mfem::Vector &, mfem::Vector &) const override;
|
||||
|
||||
[[nodiscard]] const mean_field::operators::StellarEquilibriumLayout &GetLayout() const noexcept;
|
||||
[[nodiscard]] mean_field::operators::PreparedStellarEquilibriumReport Prepare(
|
||||
const mfem::Vector &,
|
||||
const mean_field::operators::StellarEquilibriumDependencies &,
|
||||
const mean_field::physics::RigidRotation &
|
||||
);
|
||||
void BuildResidual(mfem::Vector &) const;
|
||||
[[nodiscard]] bool IsPrepared() const noexcept;
|
||||
[[nodiscard]] mean_field::operators::RootConstraintReport GetFixedMassReport() const;
|
||||
[[nodiscard]] const mean_field::operators::StellarEquilibriumDependencies &GetDependencies() const;
|
||||
[[nodiscard]] const mean_field::operators::StellarEquilibriumDependencyStamp &
|
||||
GetGeneratedDisplacementDependency() const;
|
||||
[[nodiscard]] const mean_field::operators::PreparedPressureSurfaceConstraint &
|
||||
GetSurfaceConstraintOperator() const;
|
||||
};
|
||||
|
||||
template <mean_field::model::StellarModelType Model>
|
||||
class AlternateEquationOfStateRuntime final {
|
||||
public:
|
||||
using Report = mean_field::operators::EmptySpecificationPreparationReport;
|
||||
|
||||
AlternateEquationOfStateRuntime(
|
||||
mean_field::fem::FEM &,
|
||||
const mean_field::mapping::DomainMapper &,
|
||||
AlternatePhysicalCore &,
|
||||
const Model &
|
||||
) noexcept { }
|
||||
|
||||
template <typename StateView, typename Controls>
|
||||
void ReadPhysicalControls(const StateView &, Controls &) noexcept { }
|
||||
|
||||
template <typename StateView>
|
||||
[[nodiscard]] Report PrepareAfterPhysical(
|
||||
const StateView &,
|
||||
const mean_field::operators::StellarEquilibriumDependencies &,
|
||||
const AlternatePhysicalCore &
|
||||
) noexcept {
|
||||
return {};
|
||||
}
|
||||
|
||||
template <typename ResidualView>
|
||||
void AddResidual(const ResidualView &) const noexcept { }
|
||||
|
||||
template <typename DirectionView, typename ActionView>
|
||||
void AddJacobianAction(
|
||||
const DirectionView &,
|
||||
const ActionView &,
|
||||
const AlternatePhysicalCore &
|
||||
) const noexcept { }
|
||||
|
||||
[[nodiscard]] constexpr bool IsPrepared() const noexcept {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
} // namespace material_surface_runtime_contract_test
|
||||
|
||||
namespace mean_field::operators {
|
||||
template <>
|
||||
struct StellarEquilibriumCoreRuntime<
|
||||
material_surface_runtime_contract_test::RegisteredAlternateEquationOfState> {
|
||||
static constexpr bool registered = true;
|
||||
using CoreType = material_surface_runtime_contract_test::AlternatePhysicalCore;
|
||||
|
||||
[[nodiscard]] static std::unique_ptr<CoreType> Make(
|
||||
fem::FEM &,
|
||||
const mapping::DomainMapper &,
|
||||
const material_surface_runtime_contract_test::RegisteredAlternateEquationOfState &,
|
||||
const models::CompiledFixedMass &,
|
||||
PressureSurfaceConstraintView,
|
||||
deformation::PreparedDomainDeformationRuntime
|
||||
);
|
||||
|
||||
[[nodiscard]] static int SurfaceEquationCount(const CoreType &) noexcept;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct StellarEquilibriumRuntimeContribution<
|
||||
material_surface_runtime_contract_test::RegisteredAlternateEquationOfState>
|
||||
: PreparedStellarEquilibriumContribution<
|
||||
material_surface_runtime_contract_test::AlternateEquationOfStateRuntime> { };
|
||||
} // namespace mean_field::operators
|
||||
|
||||
namespace mean_field::preconditioning {
|
||||
/*
|
||||
* This registration deliberately advertises a distinct core without an
|
||||
* executable material/surface implementation. Registration alone must
|
||||
* therefore remain insufficient for MaterialSurfaceRuntimeFor.
|
||||
*/
|
||||
template <>
|
||||
struct MaterialSurfaceEquationOfStateBackend<
|
||||
material_surface_runtime_contract_test::RegisteredAlternateEquationOfState> {
|
||||
static constexpr bool registered = true;
|
||||
using CoreType = material_surface_runtime_contract_test::AlternatePhysicalCore;
|
||||
};
|
||||
} // namespace mean_field::preconditioning
|
||||
|
||||
namespace {
|
||||
namespace backend = mean_field::preconditioning::backend;
|
||||
namespace blocks = mean_field::utils::blocks;
|
||||
@@ -25,6 +150,11 @@ 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<
|
||||
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<
|
||||
PolytropicMaterialSurfaceDescriptor,
|
||||
backend::Diagonal,
|
||||
@@ -44,6 +174,30 @@ namespace {
|
||||
PolytropicMaterialSurfaceDescriptor,
|
||||
preconditioning::ApproximateMaterialSurfaceLDU,
|
||||
backend::FixedCycles>;
|
||||
using RegisteredAlternateGravityComponent = preconditioning::GravityFieldBlock<
|
||||
backend::Diagonal,
|
||||
FixedCycleAMG,
|
||||
preconditioning::GravityApproximateLDU>;
|
||||
|
||||
using RegisteredAlternateMaterialSurfaceDescriptor =
|
||||
preconditioning::MaterialSurfaceDescriptorFor<RegisteredAlternateProblem>;
|
||||
|
||||
struct DistinctPhysicalCore final { };
|
||||
|
||||
template <typename Problem>
|
||||
concept CanMakeDefaultMaterialSurface = requires(const Problem &problem) {
|
||||
preconditioning::materialSurfaceBlock(problem);
|
||||
};
|
||||
|
||||
template <typename Problem>
|
||||
concept CanPrepareDefaultMaterialSurface = requires(const Problem &problem) {
|
||||
preconditioning::prepare(problem, preconditioning::materialSurfaceBlock(problem));
|
||||
};
|
||||
|
||||
template <typename Problem>
|
||||
concept CanMakeDefaultStellarStructure = requires(const Problem &problem) {
|
||||
preconditioning::stellarStructureBlock(problem);
|
||||
};
|
||||
|
||||
class KnownCouplings final {
|
||||
public:
|
||||
@@ -161,6 +315,53 @@ 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_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>);
|
||||
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(preconditioning::MaterialSurfacePreconditionerProblem<PolytropicProblem>);
|
||||
STATIC_CHECK_FALSE(preconditioning::MaterialSurfacePreconditionerProblem<RegisteredAlternateProblem>);
|
||||
STATIC_CHECK(CanMakeDefaultMaterialSurface<PolytropicProblem>);
|
||||
STATIC_CHECK_FALSE(CanMakeDefaultMaterialSurface<RegisteredAlternateProblem>);
|
||||
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(CanMakeDefaultStellarStructure<RegisteredAlternateProblem>);
|
||||
STATIC_CHECK(
|
||||
mean_field::material::CompiledThermodynamicEquations<typename PolytropicProblem::ThermodynamicEquationsType>
|
||||
);
|
||||
@@ -171,7 +372,7 @@ TEST_CASE(
|
||||
);
|
||||
STATIC_CHECK(MaterialSurfaceDiagonal::CorrectionBlocks::size == 3);
|
||||
STATIC_CHECK(MaterialSurfaceDiagonal::ResidualBlocks::size == 3);
|
||||
STATIC_CHECK(MaterialSurfaceDiagonal::RequiredCouplings::size == 8);
|
||||
STATIC_CHECK(MaterialSurfaceDiagonal::RequiredCouplings::size == 9);
|
||||
STATIC_CHECK(preconditioning::CompletePreconditionerFor<Plan, Form>);
|
||||
STATIC_CHECK(preconditioning::CompatiblePreconditionerFor<Plan, Form, JacobianForm>);
|
||||
STATIC_CHECK(preconditioning::backend::ArnoldiAdmissible<typename MaterialSurfaceDiagonal::BackendType>);
|
||||
@@ -409,7 +610,7 @@ TEST_CASE(
|
||||
|
||||
mfem::Vector fullDirection(physical.Width());
|
||||
fullDirection = 0.0;
|
||||
const auto fullDirectionView = physical.GetRootManifest().directionView(fullDirection);
|
||||
const auto fullDirectionView = physical.GetRootManifest().stateView(fullDirection);
|
||||
const auto &offsets = restricted.GetOffsets();
|
||||
const mfem::Vector densityDirection(restrictedDirection.GetData(), offsets[1]);
|
||||
const mfem::Vector surfaceDirection(restrictedDirection.GetData() + offsets[1], offsets[2] - offsets[1]);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -71,6 +71,10 @@ namespace preconditioning_runtime_test {
|
||||
++m_snapshot.geometry.revision;
|
||||
}
|
||||
|
||||
void AdvancePreparation() noexcept {
|
||||
++m_snapshot.preparedOperatorGeneration;
|
||||
}
|
||||
|
||||
void SetPrepared(const bool prepared) noexcept {
|
||||
m_prepared = prepared;
|
||||
}
|
||||
@@ -108,6 +112,136 @@ namespace preconditioning_runtime_test {
|
||||
};
|
||||
} // namespace preconditioning_runtime_test
|
||||
|
||||
namespace unsupported_physical_preconditioner_test {
|
||||
class Constraint;
|
||||
class PreparedConstraint;
|
||||
|
||||
class Constraint final {
|
||||
public:
|
||||
struct Parameters final {
|
||||
mean_field::dimensions::SpecificEnthalpyValue target;
|
||||
};
|
||||
|
||||
using TargetValue = mean_field::dimensions::SpecificEnthalpyValue;
|
||||
using ScalarDescription = mean_field::stellar::ScalarConstraint<
|
||||
mean_field::dimensions::quantity::SpecificEnthalpy,
|
||||
mean_field::dimensions::quantity::Dimensionless,
|
||||
mean_field::dimensions::quantity::SpecificEnthalpy,
|
||||
"test.unsupported_physical_edge.coordinate",
|
||||
"q_u",
|
||||
"test.unsupported_physical_edge.residual",
|
||||
"R_u">;
|
||||
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>,
|
||||
ScalarDescription>;
|
||||
using EquilibriumPhysics =
|
||||
mean_field::operators::LocalSpecificationEquilibriumPhysics<
|
||||
PreparedConstraint>;
|
||||
|
||||
explicit constexpr Constraint(const Parameters parameters) noexcept
|
||||
: m_target(parameters.target) {
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr TargetValue target() const noexcept {
|
||||
return m_target;
|
||||
}
|
||||
|
||||
private:
|
||||
TargetValue m_target;
|
||||
};
|
||||
|
||||
struct PreparationReport final {
|
||||
bool stateChanged{true};
|
||||
};
|
||||
|
||||
class PreparedConstraint final {
|
||||
public:
|
||||
using Report = PreparationReport;
|
||||
|
||||
explicit PreparedConstraint(const Constraint &) noexcept {
|
||||
}
|
||||
|
||||
template <typename StateView>
|
||||
[[nodiscard]] Report PrepareAfterPhysical(const StateView &) noexcept {
|
||||
m_isPrepared = true;
|
||||
return {};
|
||||
}
|
||||
|
||||
template <typename Row>
|
||||
[[nodiscard]] mean_field::stellar::StructuralZero AddResidual(
|
||||
mean_field::stellar::equation::OwnConstraint,
|
||||
Row &
|
||||
) const noexcept {
|
||||
return mean_field::stellar::structuralZero;
|
||||
}
|
||||
|
||||
template <typename Row>
|
||||
[[nodiscard]] mean_field::stellar::StructuralZero AddResidual(
|
||||
mean_field::stellar::equation::PoissonEquation,
|
||||
Row &
|
||||
) const noexcept {
|
||||
return mean_field::stellar::structuralZero;
|
||||
}
|
||||
|
||||
template <typename Direction, typename Row>
|
||||
[[nodiscard]] mean_field::stellar::StructuralZero AddJacobianAction(
|
||||
mean_field::stellar::Derivative<
|
||||
mean_field::stellar::equation::OwnConstraint,
|
||||
mean_field::stellar::state::SpecificEnthalpy>,
|
||||
const Direction &,
|
||||
Row &
|
||||
) const noexcept {
|
||||
return mean_field::stellar::zeroDerivative;
|
||||
}
|
||||
|
||||
template <typename Direction, typename Row>
|
||||
[[nodiscard]] mean_field::stellar::StructuralZero AddJacobianAction(
|
||||
mean_field::stellar::Derivative<
|
||||
mean_field::stellar::equation::OwnConstraint,
|
||||
mean_field::stellar::state::OwnGeneratedCoordinate>,
|
||||
const Direction &,
|
||||
Row &
|
||||
) const noexcept {
|
||||
return mean_field::stellar::zeroDerivative;
|
||||
}
|
||||
|
||||
template <typename Direction, typename Row>
|
||||
[[nodiscard]] mean_field::stellar::StructuralZero AddJacobianAction(
|
||||
mean_field::stellar::Derivative<
|
||||
mean_field::stellar::equation::PoissonEquation,
|
||||
mean_field::stellar::state::SpecificEnthalpy>,
|
||||
const Direction &,
|
||||
Row &
|
||||
) const noexcept {
|
||||
return mean_field::stellar::zeroDerivative;
|
||||
}
|
||||
|
||||
template <typename Direction, typename Row>
|
||||
[[nodiscard]] mean_field::stellar::StructuralZero AddJacobianAction(
|
||||
mean_field::stellar::Derivative<
|
||||
mean_field::stellar::equation::PoissonEquation,
|
||||
mean_field::stellar::state::OwnGeneratedCoordinate>,
|
||||
const Direction &,
|
||||
Row &
|
||||
) const noexcept {
|
||||
return mean_field::stellar::zeroDerivative;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsPrepared() const noexcept {
|
||||
return m_isPrepared;
|
||||
}
|
||||
|
||||
private:
|
||||
bool m_isPrepared{false};
|
||||
};
|
||||
} // namespace unsupported_physical_preconditioner_test
|
||||
|
||||
template <> struct mean_field::preconditioning::StellarEquilibriumProblemTraits<preconditioning_runtime_test::Problem> {
|
||||
using Problem = preconditioning_runtime_test::Problem;
|
||||
using Form = preconditioning_runtime_test::Form;
|
||||
@@ -146,14 +280,40 @@ namespace {
|
||||
namespace blocks = mean_field::utils::blocks;
|
||||
namespace preconditioning = mean_field::preconditioning;
|
||||
|
||||
using ModelWithoutPhase = mean_field::operators::StellarEquilibriumSpecificationModel;
|
||||
using CentralDensityModel = mean_field::operators::CentralDensityStellarEquilibriumSpecificationModel;
|
||||
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<
|
||||
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<
|
||||
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 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>;
|
||||
|
||||
template <typename Problem>
|
||||
concept CanMakeDefaultStellarStructureBlock = requires(const Problem &problem) {
|
||||
preconditioning::stellarStructureBlock(problem);
|
||||
};
|
||||
|
||||
using RefreshingDensityIdentity = preconditioning::ComponentDeclaration<
|
||||
blocks::type_list<blocks::density::mass::value>,
|
||||
blocks::type_list<blocks::density::mass::residual>,
|
||||
@@ -203,6 +363,73 @@ TEST_CASE(
|
||||
STATIC_CHECK(CentralDensityPlan::ComponentTypes::size == 7);
|
||||
}
|
||||
|
||||
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 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>;
|
||||
|
||||
// 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(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(CanMakeDefaultStellarStructureBlock<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(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>);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Prepared Stellar Identity Preconditioning Is Bitwise Equivalent To The P0 Baseline",
|
||||
tags::preconditioning_runtime_unit
|
||||
@@ -286,6 +513,14 @@ TEST_CASE(
|
||||
CHECK_FALSE(geometryRefresh.changes.linearization);
|
||||
CHECK(preconditioner.GetStatistics().refreshes == 2);
|
||||
|
||||
problem.AdvancePreparation();
|
||||
CHECK_FALSE(preconditioner.IsCurrent());
|
||||
const auto preparationRefresh = preconditioner.Refresh();
|
||||
CHECK(preparationRefresh.changes.linearization);
|
||||
CHECK_FALSE(preparationRefresh.changes.geometry);
|
||||
CHECK(preconditioner.IsCurrent());
|
||||
CHECK(preconditioner.GetStatistics().refreshes == 3);
|
||||
|
||||
problem.SetPrepared(false);
|
||||
CHECK_FALSE(preconditioner.IsCurrent());
|
||||
CHECK_THROWS_AS(preconditioner.Refresh(), std::logic_error);
|
||||
|
||||
@@ -114,6 +114,7 @@ namespace {
|
||||
mean_field::integral::FixedTotalMass,
|
||||
mean_field::constraint::FixedCentralDensity>>;
|
||||
using PolytropicProblem = mean_field::equilibrium::StellarEquilibriumProblem<PolytropicModel>;
|
||||
using PolytropicMaterialSurfaceDescriptor = preconditioning::MaterialSurfaceDescriptorFor<PolytropicProblem>;
|
||||
using MaterialComponent =
|
||||
decltype(preconditioning::materialSurfaceBlock(std::declval<const PolytropicProblem &>()));
|
||||
using FixedAMG = backend::HypreBoomerAMG<backend::FixedCycles>;
|
||||
@@ -126,6 +127,13 @@ namespace {
|
||||
preconditioning::IndependentStellarSubsystems{}
|
||||
));
|
||||
|
||||
template <typename Problem>
|
||||
concept CanPrepareDefaultStellarStructure = requires(const Problem &problem) {
|
||||
preconditioning::prepare(problem, preconditioning::stellarStructureBlock(problem));
|
||||
};
|
||||
|
||||
struct DistinctPhysicalCore final { };
|
||||
|
||||
[[nodiscard]] mean_field::operators::StellarEquilibriumDependencies
|
||||
makeDependencies(const std::uint64_t revision = 1) {
|
||||
return {
|
||||
@@ -173,11 +181,26 @@ 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_FALSE(preconditioning::ExecutableStellarStructureRuntimeFor<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(CanPrepareDefaultStellarStructure<PolytropicProblem>);
|
||||
STATIC_CHECK(std::same_as<typename PolytropicStructure::MaterialToGravityCouplings, ExpectedMaterialToGravity>);
|
||||
STATIC_CHECK(std::same_as<typename PolytropicStructure::GravityToMaterialCouplings, ExpectedGravityToMaterial>);
|
||||
STATIC_CHECK(PolytropicStructure::MaterialToGravityCouplings::size == 3);
|
||||
STATIC_CHECK(PolytropicStructure::GravityToMaterialCouplings::size == 2);
|
||||
STATIC_CHECK(PolytropicStructure::RequiredCouplings::size == 16);
|
||||
STATIC_CHECK(PolytropicStructure::RequiredCouplings::size == 17);
|
||||
STATIC_CHECK(preconditioning::backend::ArnoldiAdmissible<typename PolytropicStructure::BackendType>);
|
||||
|
||||
using FixedMassIdentity = preconditioning::IdentityBlock<
|
||||
@@ -265,7 +288,7 @@ TEST_CASE(
|
||||
|
||||
mfem::Vector materialOnlyDirection(physical.Width());
|
||||
materialOnlyDirection = 0.0;
|
||||
auto materialOnlyView = physical.GetRootManifest().directionView(materialOnlyDirection);
|
||||
auto materialOnlyView = physical.GetRootManifest().stateView(materialOnlyDirection);
|
||||
mfem::Vector materialDensity = materialOnlyView.block(blocks::density_field.mass_term);
|
||||
mfem::Vector materialSurface = materialOnlyView.block(blocks::surface_deformation_field.parameters_term);
|
||||
mfem::Vector materialEnthalpy = materialOnlyView.block(blocks::enthalpy_field.specific_term);
|
||||
@@ -290,7 +313,7 @@ TEST_CASE(
|
||||
|
||||
mfem::Vector gravityOnlyDirection(physical.Width());
|
||||
gravityOnlyDirection = 0.0;
|
||||
auto gravityOnlyView = physical.GetRootManifest().directionView(gravityOnlyDirection);
|
||||
auto gravityOnlyView = physical.GetRootManifest().stateView(gravityOnlyDirection);
|
||||
mfem::Vector gravityGradient = gravityOnlyView.block(blocks::gravity_field.gradient_term);
|
||||
mfem::Vector gravityPotential = gravityOnlyView.block(blocks::gravity_field.poisson_term);
|
||||
const mfem::Vector sourceGravityGradient(
|
||||
|
||||
@@ -13,6 +13,137 @@ import mean_field;
|
||||
import test_helpers;
|
||||
|
||||
namespace {
|
||||
template <typename... Specifications>
|
||||
using ProjectionModelWith = mean_field::model::StellarModel<
|
||||
mean_field::models::SpecificationSet<Specifications...>>;
|
||||
|
||||
class UnregisteredProjectionConstraint final {
|
||||
public:
|
||||
struct Parameters final { };
|
||||
using ModelDefinition = mean_field::constraint::PhaseCondition<
|
||||
UnregisteredProjectionConstraint,
|
||||
"UnregisteredProjectionConstraint",
|
||||
mean_field::models::DependsOn<mean_field::models::stellar::state::SpecificEnthalpy>,
|
||||
mean_field::models::Affects<mean_field::models::stellar::equation::HydrostaticBalance>>;
|
||||
|
||||
explicit constexpr UnregisteredProjectionConstraint(Parameters) noexcept {
|
||||
}
|
||||
};
|
||||
|
||||
class ExplicitNoChangeProjectionConstraint final {
|
||||
public:
|
||||
struct Parameters final { };
|
||||
using ModelDefinition = mean_field::constraint::PhaseCondition<
|
||||
ExplicitNoChangeProjectionConstraint,
|
||||
"ExplicitNoChangeProjectionConstraint",
|
||||
mean_field::models::DependsOn<mean_field::models::stellar::state::SpecificEnthalpy>,
|
||||
mean_field::models::Affects<mean_field::models::stellar::equation::HydrostaticBalance>>;
|
||||
using RadialProjection = mean_field::seed::projection::Use<mean_field::seed::projection::NoStateChange>;
|
||||
|
||||
explicit constexpr ExplicitNoChangeProjectionConstraint(Parameters) noexcept {
|
||||
}
|
||||
};
|
||||
|
||||
struct IncompleteProjectionPhysics final {
|
||||
static constexpr bool registered = true;
|
||||
static constexpr bool providesRadialMass = false;
|
||||
|
||||
template <typename Model>
|
||||
static constexpr bool supports = true;
|
||||
};
|
||||
|
||||
class IncompleteProjectionConstraint final {
|
||||
public:
|
||||
struct Parameters final { };
|
||||
using ModelDefinition = mean_field::constraint::PhaseCondition<
|
||||
IncompleteProjectionConstraint,
|
||||
"IncompleteProjectionConstraint",
|
||||
mean_field::models::DependsOn<mean_field::models::stellar::state::SpecificEnthalpy>,
|
||||
mean_field::models::Affects<mean_field::models::stellar::equation::HydrostaticBalance>>;
|
||||
using RadialProjection = mean_field::seed::projection::Use<IncompleteProjectionPhysics>;
|
||||
|
||||
explicit constexpr IncompleteProjectionConstraint(Parameters) noexcept {
|
||||
}
|
||||
};
|
||||
|
||||
class UnregisteredProjectionEquationOfState final {
|
||||
public:
|
||||
struct Parameters final { };
|
||||
using ModelDefinition = mean_field::eos::ConstitutiveLaw<
|
||||
UnregisteredProjectionEquationOfState,
|
||||
"UnregisteredProjectionEquationOfState">;
|
||||
|
||||
explicit constexpr UnregisteredProjectionEquationOfState(Parameters) noexcept {
|
||||
}
|
||||
};
|
||||
|
||||
class UnregisteredProjectionSurface final {
|
||||
public:
|
||||
struct Parameters final { };
|
||||
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;
|
||||
|
||||
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>
|
||||
static void validate(
|
||||
const Specification &,
|
||||
const Model &,
|
||||
const mean_field::seed::RadialProfile &,
|
||||
const mean_field::seed::StellarEquilibriumProjectionOptions &
|
||||
) noexcept {
|
||||
}
|
||||
|
||||
template <typename Specification, typename Model>
|
||||
static void initialize(
|
||||
const Specification &,
|
||||
const Model &,
|
||||
const mean_field::seed::RadialProjectionScales &,
|
||||
mean_field::seed::RadialProjectionState &,
|
||||
mfem::Vector coordinate
|
||||
) {
|
||||
if (coordinate.Size() == 1) {
|
||||
coordinate(0) = 0.0;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class SecondRadialMassConstraint final {
|
||||
public:
|
||||
struct Parameters final {
|
||||
mean_field::dimensions::MassValue mass;
|
||||
};
|
||||
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) {
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr mean_field::dimensions::MassValue targetMass() const noexcept {
|
||||
return m_mass;
|
||||
}
|
||||
|
||||
private:
|
||||
mean_field::dimensions::MassValue m_mass;
|
||||
};
|
||||
|
||||
[[nodiscard]] mean_field::operators::StellarEquilibriumDependencies make_dependencies() {
|
||||
return {
|
||||
.discretization = {.identity = 7001, .revision = 1},
|
||||
@@ -42,6 +173,65 @@ namespace {
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE(
|
||||
"Radial Projection Capabilities Are Inferred From Every Model Specification",
|
||||
tags::stellar_seed_projection_type_contract
|
||||
) {
|
||||
using namespace mean_field;
|
||||
using BaseModel = ProjectionModelWith<eos::Polytrope, surface::Isobaric, integral::FixedTotalMass>;
|
||||
using CentralModel = ProjectionModelWith<
|
||||
eos::Polytrope,
|
||||
surface::Isobaric,
|
||||
integral::FixedTotalMass,
|
||||
constraint::FixedCentralDensity>;
|
||||
using AngularModel = ProjectionModelWith<
|
||||
eos::Polytrope,
|
||||
surface::Isobaric,
|
||||
integral::FixedTotalMass,
|
||||
integral::FixedAngularMomentum>;
|
||||
using ExplicitExtensionModel = ProjectionModelWith<
|
||||
eos::Polytrope,
|
||||
surface::Isobaric,
|
||||
integral::FixedTotalMass,
|
||||
ExplicitNoChangeProjectionConstraint>;
|
||||
using MissingConstraintRuleModel = ProjectionModelWith<
|
||||
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>;
|
||||
using MissingMassProviderModel = ProjectionModelWith<eos::Polytrope, surface::Isobaric>;
|
||||
using AmbiguousMassProviderModel = ProjectionModelWith<
|
||||
eos::Polytrope,
|
||||
surface::Isobaric,
|
||||
integral::FixedTotalMass,
|
||||
SecondRadialMassConstraint>;
|
||||
|
||||
STATIC_CHECK(seed::RadialProfileProjectableModel<BaseModel>);
|
||||
STATIC_CHECK(seed::RadialProfileProjectableModel<CentralModel>);
|
||||
STATIC_CHECK(seed::RadialProfileProjectableModel<AngularModel>);
|
||||
STATIC_CHECK(seed::RadialProfileProjectableModel<ExplicitExtensionModel>);
|
||||
STATIC_CHECK_FALSE(seed::RadialProfileProjectableModel<MissingConstraintRuleModel>);
|
||||
STATIC_CHECK_FALSE(seed::RadialProfileProjectableModel<IncompleteConstraintRuleModel>);
|
||||
STATIC_CHECK_FALSE(seed::RadialProfileProjectableModel<MissingEquationOfStateRuleModel>);
|
||||
STATIC_CHECK_FALSE(seed::RadialProfileProjectableModel<MissingSurfaceRuleModel>);
|
||||
STATIC_CHECK_FALSE(seed::RadialProfileProjectableModel<MissingMassProviderModel>);
|
||||
STATIC_CHECK_FALSE(seed::RadialProfileProjectableModel<AmbiguousMassProviderModel>);
|
||||
STATIC_CHECK_FALSE(seed::RadialProfileProjectableModel<int>);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Projected Equilibrium States Preserve Their Compiled Stellar Model Type",
|
||||
tags::stellar_seed_projection_type_contract
|
||||
@@ -116,9 +306,9 @@ TEST_CASE(
|
||||
REQUIRE(centralDensityBorder.Size() == 1);
|
||||
CHECK(centralDensityBorder(0) == 0.0);
|
||||
|
||||
const operators::PreparedCentralDensityStellarEquilibriumReport preparation =
|
||||
problem.Prepare(projected.values, make_dependencies(), make_zero_rotation());
|
||||
const auto preparation = problem.Prepare(projected.values, make_dependencies(), make_zero_rotation());
|
||||
CHECK(preparation.assembledResidual);
|
||||
CHECK(preparation.template specification<models::FixedCentralDensity>().constraint.DidAnyWork());
|
||||
|
||||
mfem::Vector residual;
|
||||
problem.BuildResidual(residual);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include <type_traits>
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <mfem.hpp>
|
||||
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
@@ -11,6 +12,25 @@ namespace {
|
||||
const mean_field::utils::Args arguments = test_utils::setup_args();
|
||||
return mean_field::fem::setup_fem(arguments.mesh_file, arguments, 0);
|
||||
}
|
||||
|
||||
template <typename Problem>
|
||||
concept PreparesWithGeneratedRotation = requires(
|
||||
Problem &problem,
|
||||
const mfem::Vector &state,
|
||||
const mean_field::operators::StellarEquilibriumDependencies &dependencies
|
||||
) {
|
||||
problem.Prepare(state, dependencies);
|
||||
};
|
||||
|
||||
template <typename Problem>
|
||||
concept PreparesWithPrescribedRotation = requires(
|
||||
Problem &problem,
|
||||
const mfem::Vector &state,
|
||||
const mean_field::operators::StellarEquilibriumDependencies &dependencies,
|
||||
const mean_field::physics::RigidRotation &rotation
|
||||
) {
|
||||
problem.Prepare(state, dependencies, rotation);
|
||||
};
|
||||
} // namespace
|
||||
|
||||
TEST_CASE(
|
||||
@@ -42,6 +62,49 @@ TEST_CASE(
|
||||
CHECK(gravity.potentialSchurBackend().application.cycles == 3);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Fixed Angular Momentum User API Generates Rotation And Its Composable Solver Border",
|
||||
"[user-api][fixed-angular-momentum][type]"
|
||||
) {
|
||||
using namespace mean_field;
|
||||
|
||||
auto finiteElements = makeFiniteElements();
|
||||
REQUIRE(finiteElements.okay());
|
||||
|
||||
auto model = model::StellarModel(
|
||||
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}
|
||||
})
|
||||
);
|
||||
|
||||
auto problem = equilibrium::discretize(model, 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);
|
||||
STATIC_CHECK_FALSE(Problem::hasFixedCentralDensity);
|
||||
STATIC_CHECK(PreparesWithGeneratedRotation<Problem>);
|
||||
STATIC_CHECK_FALSE(PreparesWithPrescribedRotation<Problem>);
|
||||
STATIC_CHECK(Problem::FormType::value_block_count == 7);
|
||||
STATIC_CHECK(Problem::FormType::residual_block_count == 7);
|
||||
STATIC_CHECK(preconditioning::PreconditionerComponent<Preconditioner>);
|
||||
STATIC_CHECK(Preconditioner::borderValueArity == 2);
|
||||
STATIC_CHECK(Preconditioner::borderResidualArity == 2);
|
||||
|
||||
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().valueBlocks().back().symbol == "Omega");
|
||||
CHECK(problem.GetManifest().residualBlocks().back().symbol == "R_J");
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Intermediate User API Selects A Coupled Stellar Factorization",
|
||||
"[user-api][intermediate]"
|
||||
|
||||
Reference in New Issue
Block a user