Files
MeanField/tests/models/physics_specification_frontend.cpp

1426 lines
71 KiB
C++

#include <concepts>
#include <cstddef>
#include <type_traits>
#include <utility>
#include <catch2/catch_test_macros.hpp>
import mean_field;
import test_helpers;
namespace {
namespace eos = mean_field::eos;
namespace model = mean_field::model;
namespace models = mean_field::models;
namespace surface = mean_field::surface;
struct Entropy final : eos::ThermodynamicQuantity { };
struct Temperature final : eos::ThermodynamicQuantity { };
using SpecificEnthalpyFromPressureAndEntropy =
eos::Relation<eos::quantity::SpecificEnthalpy, eos::quantity::Pressure, Entropy>;
class MockBarotropicEquationOfState final {
public:
struct Parameters final {
double densityScale;
};
using ModelDefinition = eos::ConstitutiveLaw<MockBarotropicEquationOfState, "MockBarotropicEquationOfState">;
using Relations = eos::RelationCatalog<eos::DensityFromSpecificEnthalpy>;
explicit constexpr MockBarotropicEquationOfState(const Parameters parameters) noexcept
: m_densityScale(parameters.densityScale) {
}
[[nodiscard]] constexpr eos::DensityValue evaluate(
eos::DensityFromSpecificEnthalpy,
const eos::SpecificEnthalpyValue enthalpy
) const noexcept {
return eos::DensityValue{m_densityScale * enthalpy.value()};
}
[[nodiscard]] constexpr eos::PartialDerivative<
eos::quantity::Density,
eos::quantity::SpecificEnthalpy>
partialDerivative(
eos::DensityFromSpecificEnthalpy,
eos::WithRespectTo<eos::quantity::SpecificEnthalpy>,
eos::SpecificEnthalpyValue
) const noexcept {
return eos::PartialDerivative<eos::quantity::Density, eos::quantity::SpecificEnthalpy>{m_densityScale};
}
[[nodiscard]] constexpr double densityScale() const noexcept {
return m_densityScale;
}
private:
double m_densityScale;
};
class MockNonBarotropicEquationOfState final {
public:
struct Parameters final {
double entropyCoupling;
};
using ModelDefinition =
eos::ConstitutiveLaw<MockNonBarotropicEquationOfState, "MockNonBarotropicEquationOfState">;
using Relations = eos::RelationCatalog<SpecificEnthalpyFromPressureAndEntropy>;
explicit constexpr MockNonBarotropicEquationOfState(const Parameters parameters) noexcept
: m_entropyCoupling(parameters.entropyCoupling) {
}
[[nodiscard]] constexpr eos::SpecificEnthalpyValue evaluate(
SpecificEnthalpyFromPressureAndEntropy,
const eos::PressureValue pressure,
const eos::QuantityValue<Entropy> entropy
) const noexcept {
return eos::SpecificEnthalpyValue{pressure.value() + m_entropyCoupling * entropy.value()};
}
[[nodiscard]] constexpr double entropyCoupling() const noexcept {
return m_entropyCoupling;
}
private:
double m_entropyCoupling;
};
class MockIsobaricSurface final {
public:
struct Parameters final {
eos::PressureValue pressure{0.0};
};
using ModelDefinition = surface::BoundaryCondition<MockIsobaricSurface, "MockIsobaricSurface">;
using PhysicalQuantity = eos::quantity::Pressure;
using TargetValue = eos::PressureValue;
explicit constexpr MockIsobaricSurface(const Parameters parameters) noexcept : m_pressure(parameters.pressure) {
}
[[nodiscard]] constexpr TargetValue targetPressure() const noexcept {
return m_pressure;
}
private:
TargetValue m_pressure;
};
class MockIsothermalSurface final {
public:
struct Parameters final {
eos::QuantityValue<Temperature> temperature{0.0};
};
using ModelDefinition = surface::BoundaryCondition<MockIsothermalSurface, "MockIsothermalSurface">;
using PhysicalQuantity = Temperature;
using TargetValue = eos::QuantityValue<Temperature>;
explicit constexpr MockIsothermalSurface(const Parameters parameters) noexcept
: m_temperature(parameters.temperature) {
}
[[nodiscard]] constexpr TargetValue targetTemperature() const noexcept {
return m_temperature;
}
private:
TargetValue m_temperature;
};
struct DensityValueBlock final : mean_field::utils::blocks::value_block_base { };
struct MagneticFieldValueBlock final : mean_field::utils::blocks::value_block_base { };
struct EnthalpyValueBlock final : mean_field::utils::blocks::value_block_base { };
struct HydrostaticResidualBlock final : mean_field::utils::blocks::residual_block_base { };
struct InductionResidualBlock final : mean_field::utils::blocks::residual_block_base { };
struct MockScalarTarget final {
double magnitude;
[[nodiscard]] constexpr double value() const noexcept {
return magnitude;
}
};
using ScalarMassNormalization =
models::CoordinateNormalization<models::RieszTopology::global_scalar, models::PhysicalScaleLaw::mass>;
class MockFixedBaryonMass final {
public:
struct Parameters final {
double targetMass;
};
using ScalarDescription = mean_field::stellar::ScalarConstraint<
mean_field::dimensions::quantity::Mass,
mean_field::dimensions::quantity::SpecificEnergy,
mean_field::dimensions::quantity::Mass,
"mock_baryon_mass.multiplier",
"C_b",
"mock_baryon_mass.residual",
"R_Mb">;
using TargetValue = typename ScalarDescription::TargetValue;
using ModelDefinition = mean_field::integral::FixedScalarWithMultiplier<
MockFixedBaryonMass,
"MockFixedBaryonMass",
mean_field::stellar::Reads<mean_field::stellar::state::Density>,
mean_field::stellar::Changes<mean_field::stellar::equation::HydrostaticBalance>,
ScalarDescription>;
explicit constexpr MockFixedBaryonMass(const Parameters parameters) noexcept
: m_targetMass(parameters.targetMass) {
}
[[nodiscard]] constexpr double targetMass() const noexcept {
return m_targetMass;
}
[[nodiscard]] constexpr TargetValue target() const noexcept {
return TargetValue{m_targetMass};
}
private:
double m_targetMass;
};
class MockFixedRestMass final {
public:
struct Parameters final {
double targetMass;
};
using ModelDefinition = mean_field::integral::FixedWithMultiplier<
MockFixedRestMass,
"MockFixedRestMass",
models::DependsOn<models::stellar::state::Density>,
models::Affects<models::stellar::equation::HydrostaticBalance>,
models::
GlobalScalarNormalization<models::PhysicalScaleLaw::specific_energy, models::PhysicalScaleLaw::mass>,
models::GeneratedManifest<
"mock_rest_mass.multiplier",
"C_0",
"mock_rest_mass.residual",
"R_M0",
"mass",
"mass">>;
explicit constexpr MockFixedRestMass(const Parameters parameters) noexcept
: m_targetMass(parameters.targetMass) {
}
[[nodiscard]] constexpr double targetMass() const noexcept {
return m_targetMass;
}
[[nodiscard]] constexpr MockScalarTarget target() const noexcept {
return MockScalarTarget{m_targetMass};
}
private:
double m_targetMass;
};
/*
* A coupled integral written entirely in physics terms. Its residual
* reads the baryon-mass multiplier, while its own multiplier changes both
* its own scalar equation and the baryon-mass scalar equation. No
* generated backend block types appear in this declaration.
*/
class MockCrossCoupledIntegral final {
public:
struct Parameters final {
double targetMass;
};
using ScalarDescription = mean_field::stellar::ScalarConstraint<
mean_field::dimensions::quantity::Mass,
mean_field::dimensions::quantity::SpecificEnergy,
mean_field::dimensions::quantity::Mass,
"mock_cross_coupled.multiplier",
"C_x",
"mock_cross_coupled.residual",
"R_x">;
using TargetValue = typename ScalarDescription::TargetValue;
using ModelDefinition = mean_field::integral::FixedScalarWithMultiplier<
MockCrossCoupledIntegral,
"MockCrossCoupledIntegral",
mean_field::stellar::Reads<mean_field::stellar::state::GeneratedCoordinateOf<MockFixedBaryonMass>>,
mean_field::stellar::Changes<
mean_field::stellar::equation::ConstraintOf<MockFixedBaryonMass>,
mean_field::stellar::equation::OwnConstraint>,
ScalarDescription>;
explicit constexpr MockCrossCoupledIntegral(const Parameters parameters) noexcept
: m_targetMass(parameters.targetMass) {
}
[[nodiscard]] constexpr TargetValue target() const noexcept {
return TargetValue{m_targetMass};
}
private:
double m_targetMass;
};
class MockConflictingBaryonMass final {
public:
struct Parameters final {
double targetMass;
};
// A different generated-coordinate mechanism does not create a new
// identity. Stable names remain unique within a physical role.
using ModelDefinition =
mean_field::integral::FixedWithPhysicalCoordinate<MockConflictingBaryonMass, "MockFixedBaryonMass">;
explicit constexpr MockConflictingBaryonMass(const Parameters parameters) noexcept
: m_targetMass(parameters.targetMass) {
}
private:
double m_targetMass;
};
class MockFixedMagneticSpecificEnergy final {
public:
struct Parameters final {
double targetSpecificEnergy;
};
using ScalarDescription = mean_field::stellar::ScalarConstraint<
mean_field::dimensions::quantity::SpecificEnergy,
mean_field::dimensions::quantity::Dimensionless,
mean_field::dimensions::quantity::SpecificEnergy,
"mock_magnetic_specific_energy.amplitude",
"a_B",
"mock_magnetic_specific_energy.residual",
"R_EB">;
using TargetValue = typename ScalarDescription::TargetValue;
using ModelDefinition = mean_field::integral::FixedScalarWithPhysicalCoordinate<
MockFixedMagneticSpecificEnergy,
"MockFixedMagneticSpecificEnergy",
models::DependsOn<DensityValueBlock, MagneticFieldValueBlock>,
models::Affects<HydrostaticResidualBlock, InductionResidualBlock>,
ScalarDescription>;
explicit constexpr MockFixedMagneticSpecificEnergy(const Parameters parameters) noexcept
: m_targetSpecificEnergy(parameters.targetSpecificEnergy) {
}
[[nodiscard]] constexpr TargetValue targetSpecificEnergy() const noexcept {
return m_targetSpecificEnergy;
}
[[nodiscard]] constexpr TargetValue target() const noexcept {
return m_targetSpecificEnergy;
}
private:
TargetValue m_targetSpecificEnergy;
};
/*
* This mock deliberately does not claim that a magnetic field block is
* already present in the stellar core. It represents a future magnetic
* specific-energy amplitude using only physics dependencies that the current
* translation layer can map. A real MHD extension can replace those
* dependencies when magnetic state and induction blocks exist.
*/
class MockFixedMagneticEnergyAmplitude final {
public:
struct Parameters final {
double targetSpecificEnergy;
};
using ScalarDescription = mean_field::stellar::ScalarConstraint<
mean_field::dimensions::quantity::SpecificEnergy,
mean_field::dimensions::quantity::Dimensionless,
mean_field::dimensions::quantity::SpecificEnergy,
"mock_magnetic_energy.amplitude",
"a_B",
"mock_magnetic_energy.residual",
"R_EB">;
using TargetValue = typename ScalarDescription::TargetValue;
using ModelDefinition = mean_field::integral::FixedScalarWithPhysicalCoordinate<
MockFixedMagneticEnergyAmplitude,
"MockFixedMagneticEnergyAmplitude",
models::DependsOn<
models::stellar::state::Density,
models::stellar::state::SurfaceShape,
models::stellar::state::OwnGeneratedCoordinate>,
models::
Affects<models::stellar::equation::SurfaceShapeBalance, models::stellar::equation::HydrostaticBalance>,
ScalarDescription>;
explicit constexpr MockFixedMagneticEnergyAmplitude(const Parameters parameters) noexcept
: m_targetSpecificEnergy(parameters.targetSpecificEnergy) {
}
[[nodiscard]] constexpr TargetValue target() const noexcept {
return m_targetSpecificEnergy;
}
private:
TargetValue m_targetSpecificEnergy;
};
/*
* A physics-vocabulary audit for the gravitational half of the current
* stellar core. The declaration names no backend blocks: it says only
* that a virial-like scalar reads the gravitational field and potential
* and changes the field-definition and Poisson equations.
*/
class MockFixedVirialSpecificEnergy final {
public:
struct Parameters final {
double targetSpecificEnergy;
};
using ScalarDescription = mean_field::stellar::ScalarConstraint<
mean_field::dimensions::quantity::SpecificEnergy,
mean_field::dimensions::quantity::Dimensionless,
mean_field::dimensions::quantity::SpecificEnergy,
"mock_virial_specific_energy.multiplier",
"lambda_W",
"mock_virial_specific_energy.residual",
"R_W">;
using TargetValue = typename ScalarDescription::TargetValue;
using ModelDefinition = mean_field::integral::FixedScalarWithMultiplier<
MockFixedVirialSpecificEnergy,
"MockFixedVirialSpecificEnergy",
mean_field::stellar::
Reads<mean_field::stellar::state::GravityGradient, mean_field::stellar::state::GravitationalPotential>,
mean_field::stellar::Changes<
mean_field::stellar::equation::GravityGradientDefinition,
mean_field::stellar::equation::PoissonEquation>,
ScalarDescription>;
explicit constexpr MockFixedVirialSpecificEnergy(const Parameters parameters) noexcept
: m_targetSpecificEnergy(parameters.targetSpecificEnergy) {
}
[[nodiscard]] constexpr TargetValue target() const noexcept {
return TargetValue{m_targetSpecificEnergy};
}
private:
double m_targetSpecificEnergy;
};
class MockDimensionallyMismatchedMagneticEnergy final {
public:
struct Parameters final {
double targetEnergy;
};
using ScalarDescription = mean_field::stellar::ScalarConstraint<
mean_field::dimensions::quantity::SpecificEnergy,
mean_field::dimensions::quantity::Dimensionless,
mean_field::dimensions::quantity::SpecificEnergy,
"mock_mismatched_magnetic_energy.amplitude",
"a_bad",
"mock_mismatched_magnetic_energy.residual",
"R_bad">;
using ModelDefinition = mean_field::integral::FixedScalarWithPhysicalCoordinate<
MockDimensionallyMismatchedMagneticEnergy,
"MockDimensionallyMismatchedMagneticEnergy",
mean_field::stellar::Reads<mean_field::stellar::state::Density>,
mean_field::stellar::Changes<mean_field::stellar::equation::HydrostaticBalance>,
ScalarDescription>;
explicit constexpr MockDimensionallyMismatchedMagneticEnergy(const Parameters parameters) noexcept
: m_targetEnergy(parameters.targetEnergy) {
}
/* Deliberately disagrees with the declared SpecificEnergy target. */
[[nodiscard]] constexpr mean_field::dimensions::EnergyValue target() const noexcept {
return mean_field::dimensions::EnergyValue{m_targetEnergy};
}
private:
double m_targetEnergy;
};
class MockCentralEnthalpyPhase final {
public:
struct Parameters final {
double targetEnthalpy;
};
using ModelDefinition = mean_field::constraint::PhaseCondition<
MockCentralEnthalpyPhase,
"MockCentralEnthalpyPhase",
models::DependsOn<models::stellar::state::SpecificEnthalpy>,
models::Affects<models::stellar::equation::HydrostaticBalance>,
models::GlobalScalarNormalization<
models::PhysicalScaleLaw::specific_energy,
models::PhysicalScaleLaw::specific_energy>,
models::GeneratedManifest<
"mock_central_enthalpy.border",
"lambda_hc",
"mock_central_enthalpy.residual",
"R_hc",
"specific_enthalpy",
"specific_enthalpy">>;
explicit constexpr MockCentralEnthalpyPhase(const Parameters parameters) noexcept
: m_targetEnthalpy(parameters.targetEnthalpy) {
}
[[nodiscard]] constexpr double targetEnthalpy() const noexcept {
return m_targetEnthalpy;
}
[[nodiscard]] constexpr MockScalarTarget target() const noexcept {
return MockScalarTarget{m_targetEnthalpy};
}
private:
double m_targetEnthalpy;
};
class MockMalformedEquilibriumPhysics final {
public:
struct Parameters final {
double target;
};
using ScalarDescription = mean_field::stellar::ScalarConstraint<
mean_field::dimensions::quantity::SpecificEnergy,
mean_field::dimensions::quantity::SpecificEnergy,
mean_field::dimensions::quantity::SpecificEnergy,
"mock_malformed_runtime.value",
"lambda_bad",
"mock_malformed_runtime.residual",
"R_bad">;
using TargetValue = typename ScalarDescription::TargetValue;
using ModelDefinition = mean_field::constraint::ScalarPhaseCondition<
MockMalformedEquilibriumPhysics,
"MockMalformedEquilibriumPhysics",
mean_field::stellar::Reads<mean_field::stellar::state::SpecificEnthalpy>,
mean_field::stellar::Changes<mean_field::stellar::equation::HydrostaticBalance>,
ScalarDescription>;
// Deliberately present but not a valid SpecificationEquilibriumPhysics
// package. Selection must remain detection-safe.
using EquilibriumPhysics = int;
explicit constexpr MockMalformedEquilibriumPhysics(const Parameters parameters) noexcept
: m_target(parameters.target) {
}
[[nodiscard]] constexpr TargetValue target() const noexcept {
return TargetValue{m_target};
}
private:
double m_target;
};
struct MalformedNormalization final { };
class MockIntegralWithMalformedNormalization final {
public:
struct Parameters final {
double target;
};
using ModelDefinition = mean_field::integral::FixedWithMultiplier<
MockIntegralWithMalformedNormalization,
"MockIntegralWithMalformedNormalization",
models::ModelTypeList<DensityValueBlock>,
models::ModelTypeList<HydrostaticResidualBlock>,
models::GeneratedNormalization<MalformedNormalization, ScalarMassNormalization>>;
explicit constexpr MockIntegralWithMalformedNormalization(const Parameters parameters) noexcept
: m_target(parameters.target) {
}
private:
double m_target;
};
class MisidentifiedSpecification final {
public:
struct Parameters final { };
using ModelDefinition = eos::ConstitutiveLaw<MockBarotropicEquationOfState, "MisidentifiedSpecification">;
explicit MisidentifiedSpecification(Parameters) noexcept {
}
};
template <models::SpecificationRole Role, models::GeneratedStateKind StateKind>
class MockRoleStateSpecification final {
public:
struct Parameters final { };
using ModelDefinition =
models::ModelDefinition<MockRoleStateSpecification, "MockRoleStateSpecification", Role, StateKind>;
explicit constexpr MockRoleStateSpecification(Parameters) noexcept {
}
};
template <typename Candidate>
concept HasModelDefinitionProjection = requires { typename models::ModelDefinitionForT<Candidate>; };
template <typename Candidate>
concept CanEnterSpecificationSet =
requires { typename models::SpecificationSet<mean_field::eos::Polytrope, Candidate>; };
template <typename Candidate>
concept HasSpecificationContribution = requires { typename models::SpecificationContribution<Candidate>; };
template <typename... Specifications>
concept CanDeduceStellarModel = requires { model::StellarModel(std::declval<Specifications>()...); };
template <typename Specification, typename Model>
concept HasContributionStateView =
requires { typename mean_field::operators::StellarEquilibriumContributionStateView<Specification, Model>; };
template <typename Specification, typename Model>
concept HasContributionResidualView =
requires { typename mean_field::operators::StellarEquilibriumContributionResidualView<Specification, Model>; };
template <typename Specification>
concept HasDensityVolumeIntegralContext =
requires { typename mean_field::stellar::DensityVolumeIntegralContext<Specification>; };
template <typename Candidate>
concept HasSurfaceConditionType = requires { typename model::SurfaceConditionType<Candidate>; };
template <typename Candidate>
concept HasSurfaceConditionAccessor = requires(const Candidate &candidate) { candidate.surfaceCondition(); };
template <typename Candidate>
concept HasInvariantRoleAccessor = requires(const Candidate &candidate) {
candidate.template specificationForRole<models::SpecificationRole::invariant>();
};
template <typename EquationOfState, typename SurfaceCondition>
using MockModel = decltype(model::StellarModel(
EquationOfState(typename EquationOfState::Parameters{}),
SurfaceCondition(typename SurfaceCondition::Parameters{}),
MockFixedBaryonMass({.targetMass = 1.0}),
MockFixedRestMass({.targetMass = 0.875}),
MockCentralEnthalpyPhase({.targetEnthalpy = 0.125})
));
using BarotropicIsobaricModel = MockModel<MockBarotropicEquationOfState, MockIsobaricSurface>;
using BarotropicIsothermalModel = MockModel<MockBarotropicEquationOfState, MockIsothermalSurface>;
using NonBarotropicIsobaricModel = MockModel<MockNonBarotropicEquationOfState, MockIsobaricSurface>;
using NonBarotropicIsothermalModel = MockModel<MockNonBarotropicEquationOfState, MockIsothermalSurface>;
using BasePhysicsModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
mean_field::eos::Polytrope,
mean_field::surface::Isobaric,
mean_field::models::FixedTotalMass>>;
using MalformedRuntimeModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
mean_field::eos::Polytrope,
mean_field::surface::Isobaric,
mean_field::models::FixedTotalMass,
MockMalformedEquilibriumPhysics>>;
using DimensionallyMismatchedModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
mean_field::eos::Polytrope,
mean_field::surface::Isobaric,
mean_field::models::FixedTotalMass,
MockDimensionallyMismatchedMagneticEnergy>>;
using DimensionallyMismatchedForm =
mean_field::operators::CompiledStellarEquilibriumForm<DimensionallyMismatchedModel>;
using DanglingCrossConstraintModel = mean_field::model::StellarModel<
mean_field::models::
SpecificationSet<mean_field::eos::Polytrope, mean_field::surface::Isobaric, MockCrossCoupledIntegral>>;
template <typename... Types> struct TypeList final { };
template <
std::size_t ExpectedContributionCouplings,
std::size_t ExpectedBorderCouplings,
std::size_t ExpectedSystemCouplings,
typename... Specifications>
struct ConstraintPack final {
static constexpr std::size_t generatedArity = sizeof...(Specifications);
static constexpr std::size_t expectedContributionCouplings = ExpectedContributionCouplings;
static constexpr std::size_t expectedBorderCouplings = ExpectedBorderCouplings;
static constexpr std::size_t expectedSystemCouplings = ExpectedSystemCouplings;
};
using FixedMassPack = ConstraintPack<3, 2, 19, MockFixedBaryonMass>;
using TwoIntegralPack = ConstraintPack<5, 4, 21, MockFixedBaryonMass, MockFixedRestMass>;
using PhysicalCoordinatePack = ConstraintPack<11, 7, 24, MockFixedBaryonMass, MockFixedMagneticEnergyAmplitude>;
using GravityVocabularyPack = ConstraintPack<11, 6, 24, MockFixedBaryonMass, MockFixedVirialSpecificEnergy>;
using CrossConstraintPack = ConstraintPack<7, 6, 23, MockFixedBaryonMass, MockCrossCoupledIntegral>;
using PhasePack = ConstraintPack<6, 4, 21, MockFixedBaryonMass, MockCentralEnthalpyPhase>;
using AllConstraintKindsPack = ConstraintPack<
16,
11,
28,
MockFixedBaryonMass,
MockFixedRestMass,
MockFixedMagneticEnergyAmplitude,
MockCentralEnthalpyPhase>;
using MockEquationOfStates = TypeList<MockBarotropicEquationOfState, MockNonBarotropicEquationOfState>;
using MockSurfaceConditions = TypeList<MockIsobaricSurface, MockIsothermalSurface>;
using ConstraintPacks = TypeList<
FixedMassPack,
TwoIntegralPack,
PhysicalCoordinatePack,
GravityVocabularyPack,
CrossConstraintPack,
PhasePack,
AllConstraintKindsPack>;
template <typename EquationOfState, typename SurfaceCondition, typename Pack> struct MatrixModel;
template <
typename EquationOfState,
typename SurfaceCondition,
std::size_t ExpectedContributionCouplings,
std::size_t ExpectedBorderCouplings,
std::size_t ExpectedSystemCouplings,
typename... Specifications>
struct MatrixModel<
EquationOfState,
SurfaceCondition,
ConstraintPack<
ExpectedContributionCouplings,
ExpectedBorderCouplings,
ExpectedSystemCouplings,
Specifications...>> {
using Type =
model::StellarModel<models::SpecificationSet<EquationOfState, SurfaceCondition, Specifications...>>;
using FactoryType = decltype(model::StellarModel(
std::declval<EquationOfState>(),
std::declval<SurfaceCondition>(),
std::declval<Specifications>()...
));
static_assert(std::same_as<
Type,
FactoryType>);
};
template <typename EquationOfState, typename SurfaceCondition, typename Pack>
using MatrixModelT = typename MatrixModel<EquationOfState, SurfaceCondition, Pack>::Type;
template <typename Needles, typename Haystack> struct TypeListIsSubset : std::false_type { };
template <typename Haystack, typename... Needles>
struct TypeListIsSubset<mean_field::utils::blocks::type_list<Needles...>, Haystack>
: std::bool_constant<(mean_field::utils::blocks::contains_type_v<Needles, Haystack> && ...)> { };
template <typename List> struct SingleType;
template <typename Type> struct SingleType<mean_field::utils::blocks::type_list<Type>> {
using type = Type;
};
template <typename Specification>
using GeneratedValueBlockFor =
typename SingleType<typename mean_field::operators::StellarEquilibriumSpecificationCompilation<
Specification>::GeneratedValueBlocks>::type;
template <typename Specification>
using GeneratedResidualBlockFor =
typename SingleType<typename mean_field::operators::StellarEquilibriumSpecificationCompilation<
Specification>::GeneratedResidualBlocks>::type;
template <typename Candidate>
concept CanFormStellarEquilibriumProblem =
requires { typename mean_field::equilibrium::StellarEquilibriumProblem<Candidate>; };
template <typename Model, typename Pack> struct SymbolicMatrixCell;
template <
typename Model,
std::size_t ExpectedContributionCouplings,
std::size_t ExpectedBorderCouplings,
std::size_t ExpectedSystemCouplings,
typename... Specifications>
struct SymbolicMatrixCell<
Model,
ConstraintPack<
ExpectedContributionCouplings,
ExpectedBorderCouplings,
ExpectedSystemCouplings,
Specifications...>> {
using System = mean_field::operators::CompiledStellarEquilibriumSystem<Model>;
using Form = typename System::FormType;
using Jacobian = typename System::JacobianType;
using Border = mean_field::preconditioning::CompiledSpecificationBorderFor<Model>;
static constexpr bool value = [] {
static_assert(mean_field::model::StellarModelType<Model>);
static_assert(mean_field::operators::StellarEquilibriumSymbolicallyCompilable<Model>);
static_assert(mean_field::operators::StellarEquilibriumSystemCompilable<Model>);
static_assert(
mean_field::operators::stellarEquilibriumIsSymbolicallyCompilable<Model> ==
mean_field::operators::stellarEquilibriumSystemIsCompilable<Model>
);
static_assert(Model::OperatorSignature::generatedValueArity == sizeof...(Specifications));
static_assert(Model::OperatorSignature::generatedResidualArity == sizeof...(Specifications));
static_assert(Model::symbolicallySquare);
static_assert(System::GeneratedValueBlocks::size == sizeof...(Specifications));
static_assert(System::GeneratedResidualBlocks::size == sizeof...(Specifications));
static_assert(Form::value_block_count == 5 + sizeof...(Specifications));
static_assert(Form::residual_block_count == 5 + sizeof...(Specifications));
static_assert(mean_field::utils::blocks::block_form_is_valid_v<Form>);
static_assert(mean_field::utils::blocks::types_are_unique_v<typename Form::value_blocks>);
static_assert(mean_field::utils::blocks::types_are_unique_v<typename Form::residual_blocks>);
static_assert(mean_field::utils::blocks::valid_jacobian_form<Form, Jacobian>);
static_assert(System::ContributionJacobianCouplings::size == ExpectedContributionCouplings);
static_assert(System::JacobianCouplings::size == ExpectedSystemCouplings);
static_assert(mean_field::utils::blocks::types_are_unique_v<typename System::JacobianCouplings>);
static_assert(Border::valueArity == sizeof...(Specifications));
static_assert(Border::residualArity == sizeof...(Specifications));
static_assert(Border::specificationCount == sizeof...(Specifications));
static_assert(std::same_as<typename Border::CorrectionBlocks, typename System::GeneratedCorrectionBlocks>);
static_assert(std::same_as<typename Border::ResidualBlocks, typename System::GeneratedResidualBlocks>);
static_assert(Border::RequiredCouplings::size == ExpectedBorderCouplings);
static_assert(
(TypeListIsSubset<
typename mean_field::operators::StellarEquilibriumSpecificationCompilation<
Specifications>::JacobianCouplings,
typename System::JacobianCouplings>::value &&
...)
);
static_assert(mean_field::normalization::CompleteStellarNormalizationFor<Model, Form>);
// These boundary mocks intentionally describe physics but do not
// register a numerical surface or manifest adapter. Symbolic
// success therefore never overclaims an executable problem.
static_assert(!mean_field::operators::CompilableRootManifestFor<Model, Form>);
static_assert(!mean_field::operators::hasStellarEquilibriumCoreRuntime<Model>);
static_assert(!mean_field::operators::hasCompleteStellarEquilibriumRuntime<Model>);
static_assert(!mean_field::operators::hasCompatibleStellarEquilibriumPhysicalRoot<Model>);
static_assert(!mean_field::equilibrium::hasStellarEquilibriumSurfaceCompilation<Model>);
static_assert(!mean_field::equilibrium::StellarEquilibriumModel<Model>);
static_assert(!CanFormStellarEquilibriumProblem<Model>);
return true;
}();
};
template <typename EquationOfState, typename SurfaceCondition, typename Packs> struct AuditConstraintPacks;
template <typename EquationOfState, typename SurfaceCondition, typename... Packs>
struct AuditConstraintPacks<EquationOfState, SurfaceCondition, TypeList<Packs...>>
: std::bool_constant<
(SymbolicMatrixCell<MatrixModelT<EquationOfState, SurfaceCondition, Packs>, Packs>::value && ...)> { };
template <typename EquationOfState, typename Surfaces, typename Packs> struct AuditSurfaceConditions;
template <typename EquationOfState, typename Packs, typename... Surfaces>
struct AuditSurfaceConditions<EquationOfState, TypeList<Surfaces...>, Packs>
: std::bool_constant<(AuditConstraintPacks<EquationOfState, Surfaces, Packs>::value && ...)> { };
template <typename EquationOfStates, typename Surfaces, typename Packs> struct AuditCartesianProduct;
template <typename Surfaces, typename Packs, typename... EquationOfStates>
struct AuditCartesianProduct<TypeList<EquationOfStates...>, Surfaces, Packs>
: std::bool_constant<(AuditSurfaceConditions<EquationOfStates, Surfaces, Packs>::value && ...)> { };
template <typename TargetQuantity, typename CoordinateQuantity, typename ResidualQuantity>
concept CanDescribeDimensionalScalar = requires {
typename mean_field::stellar::ScalarConstraint<
TargetQuantity, CoordinateQuantity, ResidualQuantity, "dimensional_probe.value", "q_probe",
"dimensional_probe.residual", "R_probe">;
};
} // namespace
TEST_CASE(
"Physics Specifications Describe Their Compile-Time Contribution In "
"One Place",
tags::stellar_model_specification_api
) {
using Contribution = models::SpecificationContribution<MockFixedMagneticSpecificEnergy>;
using BaryonCompilation = mean_field::operators::StellarEquilibriumSpecificationCompilation<MockFixedBaryonMass>;
STATIC_CHECK(models::SelfDescribingModelSpecification<MockBarotropicEquationOfState>);
STATIC_CHECK(models::SelfDescribingModelSpecification<MockNonBarotropicEquationOfState>);
STATIC_CHECK(models::SelfDescribingModelSpecification<MockIsobaricSurface>);
STATIC_CHECK(models::SelfDescribingModelSpecification<MockIsothermalSurface>);
STATIC_CHECK(models::SelfDescribingModelSpecification<MockFixedBaryonMass>);
STATIC_CHECK_FALSE(mean_field::operators::DensityVolumeIntegralSpecification<MockFixedBaryonMass>);
STATIC_CHECK_FALSE(HasDensityVolumeIntegralContext<MockFixedBaryonMass>);
STATIC_CHECK(mean_field::operators::DensityVolumeIntegralSpecification<mean_field::models::FixedTotalMass>);
STATIC_CHECK(HasDensityVolumeIntegralContext<mean_field::models::FixedTotalMass>);
STATIC_CHECK(mean_field::stellar::ScalarConstraintDescription<MockFixedBaryonMass::ScalarDescription>);
STATIC_CHECK(models::SelfDescribingModelSpecification<MockFixedRestMass>);
STATIC_CHECK(models::SelfDescribingModelSpecification<MockCrossCoupledIntegral>);
STATIC_CHECK(models::SelfDescribingModelSpecification<MockFixedMagneticSpecificEnergy>);
STATIC_CHECK(models::SelfDescribingModelSpecification<MockFixedMagneticEnergyAmplitude>);
STATIC_CHECK(models::SelfDescribingModelSpecification<MockFixedVirialSpecificEnergy>);
STATIC_CHECK_FALSE(models::SelfDescribingModelSpecification<MisidentifiedSpecification>);
STATIC_CHECK_FALSE(models::ModelSpecification<MisidentifiedSpecification>);
STATIC_CHECK(eos::EquationOfStateModel<MockBarotropicEquationOfState>);
STATIC_CHECK(eos::BarotropicClosureEquationOfState<MockBarotropicEquationOfState>);
STATIC_CHECK(eos::EquationOfStateModel<MockNonBarotropicEquationOfState>);
STATIC_CHECK_FALSE(eos::BarotropicClosureEquationOfState<MockNonBarotropicEquationOfState>);
STATIC_CHECK(
std::same_as<
typename Contribution::GeneratedValues,
models::ModelTypeList<models::PhysicalCoordinateFor<MockFixedMagneticSpecificEnergy>>>
);
STATIC_CHECK(
std::same_as<
typename Contribution::GeneratedResiduals,
models::ModelTypeList<models::ResidualFor<MockFixedMagneticSpecificEnergy>>>
);
STATIC_CHECK(
std::same_as<
typename Contribution::DependsOn, models::ModelTypeList<DensityValueBlock, MagneticFieldValueBlock>>
);
STATIC_CHECK(
std::same_as<
typename Contribution::Affects, models::ModelTypeList<HydrostaticResidualBlock, InductionResidualBlock>>
);
STATIC_CHECK(Contribution::generatedValueArity == 1);
STATIC_CHECK(Contribution::generatedResidualArity == 1);
STATIC_CHECK(Contribution::generatedStateKind == models::GeneratedStateKind::physical_coordinate);
STATIC_CHECK(Contribution::Normalization::available);
STATIC_CHECK(Contribution::Manifest::available);
STATIC_CHECK(Contribution::Manifest::valueStableId == "mock_magnetic_specific_energy.amplitude");
STATIC_CHECK(Contribution::Manifest::residualSymbol == "R_EB");
STATIC_CHECK(Contribution::Manifest::targetUnits == "specific_energy");
STATIC_CHECK(Contribution::Manifest::residualUnits == "specific_energy");
STATIC_CHECK(
MockFixedMagneticSpecificEnergy::ScalarDescription::targetScale == models::PhysicalScaleLaw::specific_energy
);
STATIC_CHECK(Contribution::Normalization::Value::scale == models::PhysicalScaleLaw::dimensionless);
STATIC_CHECK(Contribution::Normalization::Residual::scale == models::PhysicalScaleLaw::specific_energy);
STATIC_CHECK(models::CompleteGeneratedScalarDimensionsFor<MockFixedMagneticSpecificEnergy>);
STATIC_CHECK(models::CompleteGeneratedNormalizationFor<MockFixedMagneticSpecificEnergy>);
STATIC_CHECK(models::CompleteGeneratedManifestFor<MockFixedMagneticSpecificEnergy>);
STATIC_CHECK(models::CompleteGeneratedScalarDimensionsFor<MockFixedMagneticEnergyAmplitude>);
STATIC_CHECK(models::CompleteGeneratedNormalizationFor<MockFixedMagneticEnergyAmplitude>);
STATIC_CHECK(models::CompleteGeneratedManifestFor<MockFixedMagneticEnergyAmplitude>);
STATIC_CHECK(models::CompleteGeneratedScalarDimensionsFor<MockFixedVirialSpecificEnergy>);
STATIC_CHECK(
models::physicalScaleForQuantity<mean_field::dimensions::quantity::Pressure> ==
models::PhysicalScaleLaw::pressure
);
STATIC_CHECK(
CanDescribeDimensionalScalar<
mean_field::dimensions::quantity::Pressure, mean_field::dimensions::quantity::SpecificEnergy,
mean_field::dimensions::quantity::Pressure>
);
// Total energy has no numerical reference-scale law yet. It cannot be
// mislabeled as specific energy merely by choosing that enum or unit text.
STATIC_CHECK(
models::physicalScaleForQuantity<mean_field::dimensions::quantity::Energy> ==
models::PhysicalScaleLaw::unavailable
);
STATIC_CHECK_FALSE(
CanDescribeDimensionalScalar<
mean_field::dimensions::quantity::Energy, mean_field::dimensions::quantity::Dimensionless,
mean_field::dimensions::quantity::Energy>
);
STATIC_CHECK(models::ModelSpecification<MockDimensionallyMismatchedMagneticEnergy>);
STATIC_CHECK_FALSE(models::CompleteGeneratedScalarDimensionsFor<MockDimensionallyMismatchedMagneticEnergy>);
STATIC_CHECK_FALSE(models::CompleteGeneratedNormalizationFor<MockDimensionallyMismatchedMagneticEnergy>);
STATIC_CHECK_FALSE(models::CompleteGeneratedManifestFor<MockDimensionallyMismatchedMagneticEnergy>);
STATIC_CHECK(mean_field::operators::StellarEquilibriumSymbolicallyCompilable<DimensionallyMismatchedModel>);
STATIC_CHECK_FALSE(
mean_field::normalization::CompleteStellarNormalizationFor<
DimensionallyMismatchedModel, DimensionallyMismatchedForm>
);
STATIC_CHECK_FALSE(
mean_field::operators::CompilableRootManifestFor<DimensionallyMismatchedModel, DimensionallyMismatchedForm>
);
STATIC_CHECK_FALSE(mean_field::equilibrium::StellarEquilibriumModel<DimensionallyMismatchedModel>);
STATIC_CHECK(models::CompleteGeneratedScalarDimensionsFor<mean_field::models::FixedTotalMass>);
STATIC_CHECK(models::CompleteGeneratedScalarDimensionsFor<mean_field::models::FixedAngularMomentum>);
STATIC_CHECK(models::CompleteGeneratedScalarDimensionsFor<mean_field::models::FixedCentralDensity>);
STATIC_CHECK(
mean_field::models::FixedCentralDensity::ScalarDescription::targetScale == models::PhysicalScaleLaw::density
);
STATIC_CHECK_FALSE(
std::same_as<
typename mean_field::models::FixedCentralDensity::ScalarDescription::TargetQuantity,
typename mean_field::models::FixedCentralDensity::ScalarDescription::ConstraintResidualQuantity>
);
// A third-party integral uses the same semantic translation path as the
// built-ins; it does not register an operator-compiler specialization.
STATIC_CHECK(mean_field::operators::stellarEquilibriumSpecificationCompilationComplete<MockFixedBaryonMass>);
STATIC_CHECK(
std::same_as<
typename BaryonCompilation::DependsOnValueBlocks,
mean_field::utils::blocks::type_list<mean_field::utils::blocks::density::mass::value>>
);
STATIC_CHECK(
std::same_as<
typename BaryonCompilation::AffectedResidualBlocks,
mean_field::utils::blocks::type_list<mean_field::utils::blocks::enthalpy::specific::residual>>
);
using VirialCompilation =
mean_field::operators::StellarEquilibriumSpecificationCompilation<MockFixedVirialSpecificEnergy>;
STATIC_CHECK(
std::same_as<
typename VirialCompilation::DependsOnValueBlocks, mean_field::utils::blocks::type_list<
mean_field::utils::blocks::gravity::gradient::value,
mean_field::utils::blocks::gravity::poisson::value>>
);
STATIC_CHECK(
std::same_as<
typename VirialCompilation::AffectedResidualBlocks,
mean_field::utils::blocks::type_list<
mean_field::utils::blocks::gravity::gradient::residual,
mean_field::utils::blocks::gravity::poisson::residual>>
);
STATIC_CHECK(VirialCompilation::JacobianCouplings::size == 8);
STATIC_CHECK(VirialCompilation::IncidentJacobianCouplings::size == 4);
using CrossCompilation =
mean_field::operators::StellarEquilibriumSpecificationCompilation<MockCrossCoupledIntegral>;
using BaryonValue = GeneratedValueBlockFor<MockFixedBaryonMass>;
using BaryonResidual = GeneratedResidualBlockFor<MockFixedBaryonMass>;
using CrossValue = GeneratedValueBlockFor<MockCrossCoupledIntegral>;
using CrossResidual = GeneratedResidualBlockFor<MockCrossCoupledIntegral>;
STATIC_CHECK(
std::same_as<typename CrossCompilation::DependsOnValueBlocks, mean_field::utils::blocks::type_list<BaryonValue>>
);
STATIC_CHECK(
std::same_as<
typename CrossCompilation::AffectedResidualBlocks,
mean_field::utils::blocks::type_list<BaryonResidual, CrossResidual>>
);
STATIC_CHECK(CrossCompilation::JacobianCouplings::size == 4);
STATIC_CHECK(CrossCompilation::IncidentJacobianCouplings::size == 4);
using CrossDerivative = mean_field::stellar::Derivative<
mean_field::stellar::equation::ConstraintOf<MockFixedBaryonMass>,
mean_field::stellar::state::GeneratedCoordinateOf<MockCrossCoupledIntegral>>;
STATIC_CHECK(
std::same_as<
typename CrossDerivative::EquationType, mean_field::stellar::equation::ConstraintOf<MockFixedBaryonMass>>
);
STATIC_CHECK(
std::same_as<
typename CrossDerivative::StateType,
mean_field::stellar::state::GeneratedCoordinateOf<MockCrossCoupledIntegral>>
);
using CrossModel = MatrixModelT<MockBarotropicEquationOfState, MockIsobaricSurface, CrossConstraintPack>;
using CrossSystem = mean_field::operators::CompiledStellarEquilibriumSystem<CrossModel>;
using CrossJacobian = typename CrossSystem::JacobianType;
STATIC_CHECK(mean_field::utils::blocks::has_jacobian_coupling_v<CrossResidual, BaryonValue, CrossJacobian>);
STATIC_CHECK(mean_field::utils::blocks::has_jacobian_coupling_v<BaryonResidual, BaryonValue, CrossJacobian>);
STATIC_CHECK(mean_field::utils::blocks::has_jacobian_coupling_v<BaryonResidual, CrossValue, CrossJacobian>);
STATIC_CHECK(mean_field::utils::blocks::has_jacobian_coupling_v<CrossResidual, CrossValue, CrossJacobian>);
// A named cross-constraint dependency is not silently admitted when its
// owner is absent from the model and therefore absent from the block form.
STATIC_CHECK_FALSE(mean_field::operators::StellarEquilibriumSymbolicallyCompilable<DanglingCrossConstraintModel>);
}
TEST_CASE(
"Malformed Optional Declarations Are Detectable Without Breaking "
"Type Queries",
tags::stellar_model_specification_api
) {
using Contribution = models::SpecificationContribution<MockIntegralWithMalformedNormalization>;
STATIC_CHECK(models::ModelSpecification<MockIntegralWithMalformedNormalization>);
STATIC_CHECK(Contribution::generatedValueArity == 1);
STATIC_CHECK(std::same_as<typename Contribution::Normalization, models::UnavailableGeneratedNormalization>);
STATIC_CHECK_FALSE(models::CompleteGeneratedNormalizationFor<MockIntegralWithMalformedNormalization>);
STATIC_CHECK_FALSE(models::CompleteGeneratedManifestFor<MockIntegralWithMalformedNormalization>);
STATIC_CHECK_FALSE(models::CompleteGeneratedNormalizationFor<int>);
STATIC_CHECK_FALSE(models::CompleteGeneratedManifestFor<int>);
STATIC_CHECK_FALSE(models::CompleteGeneratedScalarDimensionsFor<int>);
STATIC_CHECK_FALSE(mean_field::stellar::ScalarConstraintDescription<MalformedNormalization>);
STATIC_CHECK(models::ModelSpecification<MockMalformedEquilibriumPhysics>);
STATIC_CHECK(mean_field::operators::StellarEquilibriumSystemCompilable<MalformedRuntimeModel>);
STATIC_CHECK_FALSE(
mean_field::operators::StellarEquilibriumPhysicsAvailableFor<
MockMalformedEquilibriumPhysics, MalformedRuntimeModel>
);
STATIC_CHECK_FALSE(mean_field::operators::hasCompleteStellarEquilibriumRuntime<MalformedRuntimeModel>);
STATIC_CHECK_FALSE(mean_field::equilibrium::StellarEquilibriumModel<MalformedRuntimeModel>);
}
TEST_CASE(
"Physics Capability And Restricted Views Require Model Membership",
tags::stellar_model_specification_api
) {
using Contained = mean_field::models::FixedTotalMass;
using Foreign = mean_field::models::FixedCentralDensity;
STATIC_CHECK(mean_field::operators::StellarEquilibriumSpecificationBelongsToModel<Contained, BasePhysicsModel>);
STATIC_CHECK_FALSE(mean_field::operators::StellarEquilibriumSpecificationBelongsToModel<Foreign, BasePhysicsModel>);
STATIC_CHECK(HasContributionStateView<Contained, BasePhysicsModel>);
STATIC_CHECK(HasContributionResidualView<Contained, BasePhysicsModel>);
STATIC_CHECK_FALSE(HasContributionStateView<Foreign, BasePhysicsModel>);
STATIC_CHECK_FALSE(HasContributionResidualView<Foreign, BasePhysicsModel>);
STATIC_CHECK(mean_field::operators::StellarEquilibriumPhysicsAvailableFor<Contained, BasePhysicsModel>);
STATIC_CHECK_FALSE(mean_field::operators::StellarEquilibriumPhysicsAvailableFor<Foreign, BasePhysicsModel>);
STATIC_CHECK_FALSE(mean_field::operators::StellarEquilibriumSpecificationBelongsToModel<int, int>);
STATIC_CHECK_FALSE(mean_field::operators::StellarEquilibriumPhysicsAvailableFor<int, int>);
STATIC_CHECK_FALSE(HasContributionStateView<int, int>);
STATIC_CHECK_FALSE(HasContributionResidualView<int, int>);
}
TEST_CASE(
"Specification Roles Admit Only Their Implemented Generated State Kinds",
tags::stellar_model_specification_api
) {
using Role = models::SpecificationRole;
using Kind = models::GeneratedStateKind;
// Constitutive laws and boundary conditions describe existing fields.
STATIC_CHECK(models::CompatibleSpecificationRoleAndGeneratedState<Role::constitutive_law, Kind::none>);
STATIC_CHECK_FALSE(models::CompatibleSpecificationRoleAndGeneratedState<Role::constitutive_law, Kind::multiplier>);
STATIC_CHECK_FALSE(
models::CompatibleSpecificationRoleAndGeneratedState<Role::constitutive_law, Kind::physical_coordinate>
);
STATIC_CHECK_FALSE(
models::CompatibleSpecificationRoleAndGeneratedState<Role::constitutive_law, Kind::solver_border>
);
STATIC_CHECK(models::CompatibleSpecificationRoleAndGeneratedState<Role::boundary_condition, Kind::none>);
STATIC_CHECK_FALSE(
models::CompatibleSpecificationRoleAndGeneratedState<Role::boundary_condition, Kind::multiplier>
);
STATIC_CHECK_FALSE(
models::CompatibleSpecificationRoleAndGeneratedState<Role::boundary_condition, Kind::physical_coordinate>
);
STATIC_CHECK_FALSE(
models::CompatibleSpecificationRoleAndGeneratedState<Role::boundary_condition, Kind::solver_border>
);
// Invariants own either a Lagrange multiplier or a physical coordinate.
STATIC_CHECK(models::CompatibleSpecificationRoleAndGeneratedState<Role::invariant, Kind::multiplier>);
STATIC_CHECK(models::CompatibleSpecificationRoleAndGeneratedState<Role::invariant, Kind::physical_coordinate>);
STATIC_CHECK_FALSE(models::CompatibleSpecificationRoleAndGeneratedState<Role::invariant, Kind::none>);
STATIC_CHECK_FALSE(models::CompatibleSpecificationRoleAndGeneratedState<Role::invariant, Kind::solver_border>);
// Phase and gauge choices close a null direction with a solver border.
STATIC_CHECK(models::CompatibleSpecificationRoleAndGeneratedState<Role::phase_condition, Kind::solver_border>);
STATIC_CHECK_FALSE(models::CompatibleSpecificationRoleAndGeneratedState<Role::phase_condition, Kind::none>);
STATIC_CHECK_FALSE(models::CompatibleSpecificationRoleAndGeneratedState<Role::phase_condition, Kind::multiplier>);
STATIC_CHECK_FALSE(
models::CompatibleSpecificationRoleAndGeneratedState<Role::phase_condition, Kind::physical_coordinate>
);
STATIC_CHECK(models::CompatibleSpecificationRoleAndGeneratedState<Role::gauge_choice, Kind::solver_border>);
STATIC_CHECK_FALSE(models::CompatibleSpecificationRoleAndGeneratedState<Role::gauge_choice, Kind::none>);
STATIC_CHECK_FALSE(models::CompatibleSpecificationRoleAndGeneratedState<Role::gauge_choice, Kind::multiplier>);
STATIC_CHECK_FALSE(
models::CompatibleSpecificationRoleAndGeneratedState<Role::gauge_choice, Kind::physical_coordinate>
);
// Rotation laws are currently prescribed closures, not root-state owners.
STATIC_CHECK(models::CompatibleSpecificationRoleAndGeneratedState<Role::rotation_law, Kind::none>);
STATIC_CHECK_FALSE(models::CompatibleSpecificationRoleAndGeneratedState<Role::rotation_law, Kind::multiplier>);
STATIC_CHECK_FALSE(
models::CompatibleSpecificationRoleAndGeneratedState<Role::rotation_law, Kind::physical_coordinate>
);
STATIC_CHECK_FALSE(models::CompatibleSpecificationRoleAndGeneratedState<Role::rotation_law, Kind::solver_border>);
constexpr auto unknownRole = static_cast<Role>(1000);
constexpr auto unknownKind = static_cast<Kind>(1000);
STATIC_CHECK_FALSE(models::CompatibleSpecificationRoleAndGeneratedState<unknownRole, Kind::none>);
STATIC_CHECK_FALSE(models::CompatibleSpecificationRoleAndGeneratedState<Role::constitutive_law, unknownKind>);
using ValidConstitutive = MockRoleStateSpecification<Role::constitutive_law, Kind::none>;
using ValidBoundary = MockRoleStateSpecification<Role::boundary_condition, Kind::none>;
using ValidInvariant = MockRoleStateSpecification<Role::invariant, Kind::multiplier>;
using ValidPhysicalInvariant = MockRoleStateSpecification<Role::invariant, Kind::physical_coordinate>;
using ValidPhase = MockRoleStateSpecification<Role::phase_condition, Kind::solver_border>;
using ValidGauge = MockRoleStateSpecification<Role::gauge_choice, Kind::solver_border>;
using ValidRotation = MockRoleStateSpecification<Role::rotation_law, Kind::none>;
using InvalidInvariant = MockRoleStateSpecification<Role::invariant, Kind::none>;
using InvalidBoundary = MockRoleStateSpecification<Role::boundary_condition, Kind::solver_border>;
using InvalidRotation = MockRoleStateSpecification<Role::rotation_law, Kind::physical_coordinate>;
using UnknownRole = MockRoleStateSpecification<unknownRole, Kind::none>;
using UnknownKind = MockRoleStateSpecification<Role::constitutive_law, unknownKind>;
STATIC_CHECK(models::ModelSpecification<ValidConstitutive>);
STATIC_CHECK(models::ModelSpecification<ValidBoundary>);
STATIC_CHECK(models::SelfDescribingModelSpecification<ValidInvariant>);
STATIC_CHECK(models::ModelSpecification<ValidInvariant>);
STATIC_CHECK(models::ModelSpecification<ValidPhysicalInvariant>);
STATIC_CHECK(models::ModelSpecification<ValidPhase>);
STATIC_CHECK(models::ModelSpecification<ValidGauge>);
STATIC_CHECK(models::ModelSpecification<ValidRotation>);
STATIC_CHECK(HasModelDefinitionProjection<ValidInvariant>);
STATIC_CHECK(HasSpecificationContribution<ValidInvariant>);
STATIC_CHECK(CanEnterSpecificationSet<ValidInvariant>);
STATIC_CHECK(models::ValidModelSpecificationPack<mean_field::eos::Polytrope, ValidInvariant>);
STATIC_CHECK(CanDeduceStellarModel<mean_field::eos::Polytrope, ValidInvariant>);
STATIC_CHECK_FALSE(models::SelfDescribingModelSpecification<InvalidInvariant>);
STATIC_CHECK_FALSE(models::ModelSpecification<InvalidInvariant>);
STATIC_CHECK_FALSE(models::ResolvedModelSpecification<InvalidInvariant>);
STATIC_CHECK_FALSE(HasModelDefinitionProjection<InvalidInvariant>);
STATIC_CHECK_FALSE(HasSpecificationContribution<InvalidInvariant>);
STATIC_CHECK_FALSE(CanEnterSpecificationSet<InvalidInvariant>);
STATIC_CHECK_FALSE(models::ValidModelSpecificationPack<mean_field::eos::Polytrope, InvalidInvariant>);
STATIC_CHECK_FALSE(CanDeduceStellarModel<mean_field::eos::Polytrope, InvalidInvariant>);
STATIC_CHECK_FALSE(models::ModelSpecification<InvalidBoundary>);
STATIC_CHECK_FALSE(CanDeduceStellarModel<mean_field::eos::Polytrope, InvalidBoundary>);
STATIC_CHECK_FALSE(models::ModelSpecification<InvalidRotation>);
STATIC_CHECK_FALSE(models::ModelSpecification<UnknownRole>);
STATIC_CHECK_FALSE(models::ModelSpecification<UnknownKind>);
// The physics-facing aliases continue to produce accepted combinations.
STATIC_CHECK(models::ModelSpecification<MockBarotropicEquationOfState>);
STATIC_CHECK(models::ModelSpecification<MockIsobaricSurface>);
STATIC_CHECK(models::ModelSpecification<MockFixedBaryonMass>);
STATIC_CHECK(models::ModelSpecification<MockCrossCoupledIntegral>);
STATIC_CHECK(models::ModelSpecification<MockFixedMagneticEnergyAmplitude>);
STATIC_CHECK(models::ModelSpecification<MockFixedVirialSpecificEnergy>);
STATIC_CHECK(models::ModelSpecification<MockCentralEnthalpyPhase>);
}
TEST_CASE(
"Stable Names Distinguish Independent Same-Role Extensions",
tags::stellar_model_specification_api
) {
constexpr auto baryonKey = models::SpecificationTraits<MockFixedBaryonMass>::key;
constexpr auto restKey = models::SpecificationTraits<MockFixedRestMass>::key;
STATIC_CHECK(baryonKey.role == models::SpecificationRole::invariant);
STATIC_CHECK(restKey.role == models::SpecificationRole::invariant);
STATIC_CHECK(baryonKey.generatedStateKind == models::GeneratedStateKind::multiplier);
STATIC_CHECK(restKey.generatedStateKind == models::GeneratedStateKind::multiplier);
STATIC_CHECK(baryonKey.stableName == "MockFixedBaryonMass");
STATIC_CHECK(restKey.stableName == "MockFixedRestMass");
STATIC_CHECK(baryonKey != restKey);
STATIC_CHECK(models::specificationKeysAreUnique<MockFixedBaryonMass, MockFixedRestMass>);
STATIC_CHECK_FALSE(models::specificationKeysAreUnique<MockFixedBaryonMass, MockConflictingBaryonMass>);
using Canonical = models::SpecificationSet<MockFixedRestMass, MockBarotropicEquationOfState, MockFixedBaryonMass>;
using Reordered = models::SpecificationSet<MockFixedBaryonMass, MockFixedRestMass, MockBarotropicEquationOfState>;
STATIC_CHECK(std::same_as<Canonical, Reordered>);
}
TEST_CASE(
"EOS Surface And Integral Choices Compose As A Compile-Time Product",
tags::stellar_model_specification_api
) {
// Twenty-eight independently inferred types: 2 EOS choices x 2 boundary
// choices x 7 materially different global-constraint packs.
STATIC_CHECK(AuditCartesianProduct<MockEquationOfStates, MockSurfaceConditions, ConstraintPacks>::value);
STATIC_CHECK(model::StellarModelType<BarotropicIsobaricModel>);
STATIC_CHECK(model::StellarModelType<BarotropicIsothermalModel>);
STATIC_CHECK(model::StellarModelType<NonBarotropicIsobaricModel>);
STATIC_CHECK(model::StellarModelType<NonBarotropicIsothermalModel>);
STATIC_CHECK_FALSE(std::same_as<BarotropicIsobaricModel, BarotropicIsothermalModel>);
STATIC_CHECK_FALSE(std::same_as<BarotropicIsobaricModel, NonBarotropicIsobaricModel>);
STATIC_CHECK(BarotropicIsobaricModel::OperatorSignature::generatedValueArity == 3);
STATIC_CHECK(BarotropicIsobaricModel::OperatorSignature::generatedResidualArity == 3);
STATIC_CHECK(BarotropicIsobaricModel::symbolicallySquare);
STATIC_CHECK(NonBarotropicIsothermalModel::symbolicallySquare);
STATIC_CHECK(mean_field::operators::StellarEquilibriumSymbolicallyCompilable<BarotropicIsobaricModel>);
STATIC_CHECK(mean_field::operators::StellarEquilibriumSymbolicallyCompilable<BarotropicIsothermalModel>);
STATIC_CHECK(mean_field::operators::StellarEquilibriumSymbolicallyCompilable<NonBarotropicIsobaricModel>);
STATIC_CHECK(mean_field::operators::StellarEquilibriumSymbolicallyCompilable<NonBarotropicIsothermalModel>);
STATIC_CHECK(
mean_field::normalization::CompleteStellarNormalizationFor<
BarotropicIsobaricModel, mean_field::operators::CompiledStellarEquilibriumForm<BarotropicIsobaricModel>>
);
STATIC_CHECK_FALSE(mean_field::equilibrium::StellarEquilibriumModel<BarotropicIsobaricModel>);
STATIC_CHECK_FALSE(mean_field::equilibrium::StellarEquilibriumModel<NonBarotropicIsothermalModel>);
}
TEST_CASE(
"Constraint Packs Produce Their Declared Jacobian Incidence Without Bespoke Combination Types",
tags::stellar_model_specification_api
) {
using Model = MatrixModelT<MockBarotropicEquationOfState, MockIsothermalSurface, AllConstraintKindsPack>;
using System = mean_field::operators::CompiledStellarEquilibriumSystem<Model>;
using Jacobian = typename System::JacobianType;
using Density = mean_field::utils::blocks::density::mass::value;
using SurfaceShape = mean_field::utils::blocks::surface_deformation::parameters::value;
using Enthalpy = mean_field::utils::blocks::enthalpy::specific::value;
using SurfaceBalance = mean_field::utils::blocks::surface_deformation::shape_equilibrium::residual;
using HydrostaticBalance = mean_field::utils::blocks::enthalpy::specific::residual;
using BaryonValue = GeneratedValueBlockFor<MockFixedBaryonMass>;
using BaryonResidual = GeneratedResidualBlockFor<MockFixedBaryonMass>;
using RestMassValue = GeneratedValueBlockFor<MockFixedRestMass>;
using RestMassResidual = GeneratedResidualBlockFor<MockFixedRestMass>;
using MagneticAmplitude = GeneratedValueBlockFor<MockFixedMagneticEnergyAmplitude>;
using MagneticEnergyResidual = GeneratedResidualBlockFor<MockFixedMagneticEnergyAmplitude>;
using PhaseBorder = GeneratedValueBlockFor<MockCentralEnthalpyPhase>;
using PhaseResidual = GeneratedResidualBlockFor<MockCentralEnthalpyPhase>;
using BaryonCompilation = mean_field::operators::StellarEquilibriumSpecificationCompilation<MockFixedBaryonMass>;
using RestMassCompilation = mean_field::operators::StellarEquilibriumSpecificationCompilation<MockFixedRestMass>;
using MagneticCompilation =
mean_field::operators::StellarEquilibriumSpecificationCompilation<MockFixedMagneticEnergyAmplitude>;
using PhaseCompilation =
mean_field::operators::StellarEquilibriumSpecificationCompilation<MockCentralEnthalpyPhase>;
STATIC_CHECK(BaryonCompilation::JacobianCouplings::size == 3);
STATIC_CHECK(BaryonCompilation::IncidentJacobianCouplings::size == 2);
STATIC_CHECK(RestMassCompilation::JacobianCouplings::size == 3);
STATIC_CHECK(RestMassCompilation::IncidentJacobianCouplings::size == 2);
STATIC_CHECK(MagneticCompilation::JacobianCouplings::size == 9);
STATIC_CHECK(MagneticCompilation::IncidentJacobianCouplings::size == 5);
STATIC_CHECK(PhaseCompilation::JacobianCouplings::size == 3);
STATIC_CHECK(PhaseCompilation::IncidentJacobianCouplings::size == 2);
STATIC_CHECK(System::ContributionJacobianCouplings::size == 16);
STATIC_CHECK(System::JacobianCouplings::size == 28);
STATIC_CHECK(mean_field::utils::blocks::has_jacobian_coupling_v<HydrostaticBalance, BaryonValue, Jacobian>);
STATIC_CHECK(mean_field::utils::blocks::has_jacobian_coupling_v<BaryonResidual, Density, Jacobian>);
STATIC_CHECK(mean_field::utils::blocks::has_jacobian_coupling_v<HydrostaticBalance, Density, Jacobian>);
STATIC_CHECK_FALSE(mean_field::utils::blocks::has_jacobian_coupling_v<BaryonResidual, Enthalpy, Jacobian>);
STATIC_CHECK(mean_field::utils::blocks::has_jacobian_coupling_v<HydrostaticBalance, RestMassValue, Jacobian>);
STATIC_CHECK(mean_field::utils::blocks::has_jacobian_coupling_v<RestMassResidual, Density, Jacobian>);
STATIC_CHECK(mean_field::utils::blocks::has_jacobian_coupling_v<SurfaceBalance, MagneticAmplitude, Jacobian>);
STATIC_CHECK(mean_field::utils::blocks::has_jacobian_coupling_v<HydrostaticBalance, MagneticAmplitude, Jacobian>);
STATIC_CHECK(mean_field::utils::blocks::has_jacobian_coupling_v<SurfaceBalance, Density, Jacobian>);
STATIC_CHECK(mean_field::utils::blocks::has_jacobian_coupling_v<SurfaceBalance, SurfaceShape, Jacobian>);
STATIC_CHECK(mean_field::utils::blocks::has_jacobian_coupling_v<HydrostaticBalance, SurfaceShape, Jacobian>);
STATIC_CHECK(mean_field::utils::blocks::has_jacobian_coupling_v<MagneticEnergyResidual, Density, Jacobian>);
STATIC_CHECK(mean_field::utils::blocks::has_jacobian_coupling_v<MagneticEnergyResidual, SurfaceShape, Jacobian>);
STATIC_CHECK(
mean_field::utils::blocks::has_jacobian_coupling_v<MagneticEnergyResidual, MagneticAmplitude, Jacobian>
);
STATIC_CHECK_FALSE(mean_field::utils::blocks::has_jacobian_coupling_v<MagneticEnergyResidual, Enthalpy, Jacobian>);
STATIC_CHECK(mean_field::utils::blocks::has_jacobian_coupling_v<HydrostaticBalance, PhaseBorder, Jacobian>);
STATIC_CHECK(mean_field::utils::blocks::has_jacobian_coupling_v<PhaseResidual, Enthalpy, Jacobian>);
STATIC_CHECK(mean_field::utils::blocks::has_jacobian_coupling_v<HydrostaticBalance, Enthalpy, Jacobian>);
STATIC_CHECK_FALSE(mean_field::utils::blocks::has_jacobian_coupling_v<PhaseResidual, Density, Jacobian>);
}
TEST_CASE(
"Canonical Ordering Makes A Constraint Product Independent Of User Spelling",
tags::stellar_model_specification_api
) {
using Canonical = MatrixModelT<MockBarotropicEquationOfState, MockIsothermalSurface, AllConstraintKindsPack>;
using Permuted = model::StellarModel<models::SpecificationSet<
MockCentralEnthalpyPhase, MockFixedMagneticEnergyAmplitude, MockIsothermalSurface, MockFixedRestMass,
MockBarotropicEquationOfState, MockFixedBaryonMass>>;
STATIC_CHECK(std::same_as<Canonical, Permuted>);
STATIC_CHECK(
std::same_as<
mean_field::operators::CompiledStellarEquilibriumForm<Canonical>,
mean_field::operators::CompiledStellarEquilibriumForm<Permuted>>
);
STATIC_CHECK(
std::same_as<
mean_field::operators::CompiledStellarEquilibriumJacobianForm<Canonical>,
mean_field::operators::CompiledStellarEquilibriumJacobianForm<Permuted>>
);
}
TEST_CASE(
"Manifest Capability Is Inferred Separately From Symbolic And Runtime Capability",
tags::stellar_model_specification_api
) {
using BarotropicManifestModel =
MatrixModelT<MockBarotropicEquationOfState, surface::Isobaric, AllConstraintKindsPack>;
using NonBarotropicManifestModel =
MatrixModelT<MockNonBarotropicEquationOfState, surface::Isobaric, AllConstraintKindsPack>;
using BarotropicForm = mean_field::operators::CompiledStellarEquilibriumForm<BarotropicManifestModel>;
using NonBarotropicForm = mean_field::operators::CompiledStellarEquilibriumForm<NonBarotropicManifestModel>;
STATIC_CHECK(mean_field::operators::StellarEquilibriumSymbolicallyCompilable<BarotropicManifestModel>);
STATIC_CHECK(mean_field::operators::StellarEquilibriumSymbolicallyCompilable<NonBarotropicManifestModel>);
STATIC_CHECK(mean_field::normalization::CompleteStellarNormalizationFor<BarotropicManifestModel, BarotropicForm>);
STATIC_CHECK(
mean_field::normalization::CompleteStellarNormalizationFor<NonBarotropicManifestModel, NonBarotropicForm>
);
STATIC_CHECK(mean_field::operators::CompilableRootManifestFor<BarotropicManifestModel, BarotropicForm>);
STATIC_CHECK(mean_field::operators::CompilableRootManifestFor<NonBarotropicManifestModel, NonBarotropicForm>);
// A complete outer manifest still does not manufacture a numerical EOS
// core. The unsupported mock EOS types remain rejected at the full
// problem boundary.
STATIC_CHECK_FALSE(mean_field::operators::hasStellarEquilibriumCoreRuntime<BarotropicManifestModel>);
STATIC_CHECK_FALSE(mean_field::operators::hasStellarEquilibriumCoreRuntime<NonBarotropicManifestModel>);
STATIC_CHECK_FALSE(mean_field::equilibrium::StellarEquilibriumModel<BarotropicManifestModel>);
STATIC_CHECK_FALSE(mean_field::equilibrium::StellarEquilibriumModel<NonBarotropicManifestModel>);
}
TEST_CASE(
"Role Accessors Present Physics Names While Preserving Exact Types",
tags::stellar_model_specification_api
) {
const auto stellarModel = model::StellarModel(
MockNonBarotropicEquationOfState({.entropyCoupling = 2.5}),
MockIsothermalSurface({.temperature = eos::QuantityValue<Temperature>{0.375}}),
MockFixedBaryonMass({.targetMass = 1.75}), MockFixedMagneticSpecificEnergy({.targetSpecificEnergy = 0.125}),
MockCentralEnthalpyPhase({.targetEnthalpy = 0.75})
);
using Model = std::remove_cvref_t<decltype(stellarModel)>;
STATIC_CHECK(Model::template specificationRoleCount<models::SpecificationRole::constitutive_law> == 1);
STATIC_CHECK(Model::template specificationRoleCount<models::SpecificationRole::boundary_condition> == 1);
STATIC_CHECK(Model::template specificationRoleCount<models::SpecificationRole::invariant> == 2);
STATIC_CHECK(Model::template specificationRoleCount<models::SpecificationRole::phase_condition> == 1);
STATIC_CHECK(std::same_as<typename Model::EquationOfStateType, MockNonBarotropicEquationOfState>);
STATIC_CHECK(
std::same_as<
typename Model::template SpecificationForRole<models::SpecificationRole::constitutive_law>,
MockNonBarotropicEquationOfState>
);
STATIC_CHECK(
std::same_as<
typename Model::template SpecificationsForRole<models::SpecificationRole::invariant>,
models::ModelTypeList<MockFixedBaryonMass, MockFixedMagneticSpecificEnergy>>
);
STATIC_CHECK(std::same_as<model::EquationOfStateType<Model>, MockNonBarotropicEquationOfState>);
STATIC_CHECK(std::same_as<model::SurfaceConditionType<Model>, MockIsothermalSurface>);
STATIC_CHECK(model::HasEquationOfState<Model>);
STATIC_CHECK(model::HasSurfaceCondition<Model>);
STATIC_CHECK(model::HasUniqueSurfaceCondition<Model>);
STATIC_CHECK_FALSE(HasInvariantRoleAccessor<Model>);
CHECK(stellarModel.equationOfState().entropyCoupling() == 2.5);
CHECK(stellarModel.surfaceCondition().targetTemperature() == eos::QuantityValue<Temperature>{0.375});
CHECK(stellarModel.specification<MockFixedBaryonMass>().targetMass() == 1.75);
CHECK(stellarModel.specificationForRole<models::SpecificationRole::phase_condition>().targetEnthalpy() == 0.75);
}
TEST_CASE(
"Optional And Ambiguous Roles Remain Safe To Inspect",
tags::stellar_model_specification_api
) {
using NoSurfaceModel = decltype(model::StellarModel(MockBarotropicEquationOfState({.densityScale = 1.0})));
using TwoSurfaceModel = decltype(model::StellarModel(
MockBarotropicEquationOfState({.densityScale = 1.0}),
MockIsobaricSurface({.pressure = eos::PressureValue{0.0}}),
MockIsothermalSurface({.temperature = eos::QuantityValue<Temperature>{0.0}})
));
STATIC_CHECK(model::StellarModelType<NoSurfaceModel>);
STATIC_CHECK(model::specificationRoleCount<models::SpecificationRole::boundary_condition, NoSurfaceModel> == 0);
STATIC_CHECK_FALSE(model::HasSurfaceCondition<NoSurfaceModel>);
STATIC_CHECK_FALSE(model::HasUniqueSurfaceCondition<NoSurfaceModel>);
STATIC_CHECK_FALSE(HasSurfaceConditionType<NoSurfaceModel>);
STATIC_CHECK_FALSE(HasSurfaceConditionAccessor<NoSurfaceModel>);
STATIC_CHECK(model::StellarModelType<TwoSurfaceModel>);
STATIC_CHECK(model::specificationRoleCount<models::SpecificationRole::boundary_condition, TwoSurfaceModel> == 2);
STATIC_CHECK(model::HasSurfaceCondition<TwoSurfaceModel>);
STATIC_CHECK_FALSE(model::HasUniqueSurfaceCondition<TwoSurfaceModel>);
STATIC_CHECK_FALSE(HasSurfaceConditionType<TwoSurfaceModel>);
STATIC_CHECK_FALSE(HasSurfaceConditionAccessor<TwoSurfaceModel>);
STATIC_CHECK(model::specificationRoleCount<models::SpecificationRole::invariant, int> == 0);
STATIC_CHECK_FALSE(model::HasSpecificationsForRole<models::SpecificationRole::invariant, int>);
}