#include #include #include #include #include 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; class MockBarotropicEquationOfState final { public: struct Parameters final { double densityScale; }; using ModelDefinition = eos::ConstitutiveLaw; using Relations = eos::RelationCatalog; 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 partialDerivative(eos::DensityFromSpecificEnthalpy, eos::WithRespectTo, eos::SpecificEnthalpyValue) const noexcept { return eos::PartialDerivative{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; using Relations = eos::RelationCatalog; 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) 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; 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{0.0}; }; using ModelDefinition = surface::BoundaryCondition; using PhysicalQuantity = Temperature; using TargetValue = eos::QuantityValue; 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; 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::Changes, 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::Affects, models::GlobalScalarNormalization, 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>, mean_field::stellar::Changes< mean_field::stellar::equation::ConstraintOf, 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, models::Affects, 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::Affects, 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::Changes, 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::Affects, models::GlobalScalarNormalization, 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::Changes, 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, models::ModelTypeList, models::GeneratedNormalization>; 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; explicit MisidentifiedSpecification(Parameters) noexcept { } }; template class MockRoleStateSpecification final { public: struct Parameters final {}; using ModelDefinition = models::ModelDefinition< MockRoleStateSpecification, "MockRoleStateSpecification", Role, StateKind>; explicit constexpr MockRoleStateSpecification(Parameters) noexcept {} }; template concept HasModelDefinitionProjection = requires { typename models::ModelDefinitionForT; }; template concept CanEnterSpecificationSet = requires { typename models::SpecificationSet; }; template concept HasSpecificationContribution = requires { typename models::SpecificationContribution; }; template concept CanDeduceStellarModel = requires { model::StellarModel(std::declval()...); }; template concept HasContributionStateView = requires { typename mean_field::operators::StellarEquilibriumContributionStateView< Specification, Model>; }; template concept HasContributionResidualView = requires { typename mean_field::operators::StellarEquilibriumContributionResidualView< Specification, Model>; }; template concept HasDensityVolumeIntegralContext = requires { typename mean_field::stellar::DensityVolumeIntegralContext< Specification>; }; template concept HasSurfaceConditionType = requires { typename model::SurfaceConditionType; }; template concept HasSurfaceConditionAccessor = requires(const Candidate &candidate) { candidate.surfaceCondition(); }; template concept HasInvariantRoleAccessor = requires(const Candidate &candidate) { candidate.template specificationForRole(); }; template 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; using BarotropicIsothermalModel = MockModel; using NonBarotropicIsobaricModel = MockModel; using NonBarotropicIsothermalModel = MockModel; using BasePhysicsModel = mean_field::model::StellarModel>; using MalformedRuntimeModel = mean_field::model::StellarModel>; using DimensionallyMismatchedModel = mean_field::model::StellarModel>; using DimensionallyMismatchedForm = mean_field::operators::CompiledStellarEquilibriumForm< DimensionallyMismatchedModel>; using DanglingCrossConstraintModel = mean_field::model::StellarModel>; template 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; using MockSurfaceConditions = TypeList; using ConstraintPacks = TypeList; template struct MatrixModel; template < typename EquationOfState, typename SurfaceCondition, std::size_t ExpectedContributionCouplings, std::size_t ExpectedBorderCouplings, std::size_t ExpectedSystemCouplings, typename... Specifications> struct MatrixModel> { using Type = model::StellarModel< models::SpecificationSet>; using FactoryType = decltype(model::StellarModel( std::declval(), std::declval(), std::declval()... )); static_assert(std::same_as); }; template using MatrixModelT = typename MatrixModel::Type; template struct TypeListIsSubset : std::false_type {}; template struct TypeListIsSubset, Haystack> : std::bool_constant<(mean_field::utils::blocks::contains_type_v && ...)> {}; template struct SingleType; template struct SingleType> { using type = Type; }; template using GeneratedValueBlockFor = typename SingleType< typename mean_field::operators::StellarEquilibriumSpecificationCompilation< Specification>::GeneratedValueBlocks>::type; template using GeneratedResidualBlockFor = typename SingleType< typename mean_field::operators::StellarEquilibriumSpecificationCompilation< Specification>::GeneratedResidualBlocks>::type; template concept CanFormStellarEquilibriumProblem = requires { typename mean_field::equilibrium::StellarEquilibriumProblem; }; template struct SymbolicMatrixCell; template < typename Model, std::size_t ExpectedContributionCouplings, std::size_t ExpectedBorderCouplings, std::size_t ExpectedSystemCouplings, typename... Specifications> struct SymbolicMatrixCell> { using System = mean_field::operators::CompiledStellarEquilibriumSystem; using Form = typename System::FormType; using Jacobian = typename System::JacobianType; using Border = mean_field::preconditioning::CompiledSpecificationBorderFor; static constexpr bool value = [] { static_assert(mean_field::model::StellarModelType); static_assert(mean_field::operators::StellarEquilibriumSymbolicallyCompilable); static_assert(mean_field::operators::StellarEquilibriumSystemCompilable); static_assert(mean_field::operators::stellarEquilibriumIsSymbolicallyCompilable == mean_field::operators::stellarEquilibriumSystemIsCompilable); 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
); static_assert(mean_field::utils::blocks::types_are_unique_v); static_assert(mean_field::utils::blocks::types_are_unique_v); static_assert(mean_field::utils::blocks::valid_jacobian_form); static_assert(System::ContributionJacobianCouplings::size == ExpectedContributionCouplings); static_assert(System::JacobianCouplings::size == ExpectedSystemCouplings); static_assert(mean_field::utils::blocks::types_are_unique_v); 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); // 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); static_assert(!mean_field::operators::hasStellarEquilibriumCoreRuntime); static_assert(!mean_field::operators::hasCompleteStellarEquilibriumRuntime); static_assert(!mean_field::operators::hasCompatibleStellarEquilibriumPhysicalRoot); static_assert(!mean_field::equilibrium::hasStellarEquilibriumSurfaceCompilation); static_assert(!mean_field::equilibrium::StellarEquilibriumModel); static_assert(!CanFormStellarEquilibriumProblem); return true; }(); }; template struct AuditConstraintPacks; template struct AuditConstraintPacks> : std::bool_constant<(SymbolicMatrixCell, Packs>::value && ...)> {}; template struct AuditSurfaceConditions; template struct AuditSurfaceConditions, Packs> : std::bool_constant<(AuditConstraintPacks::value && ...)> {}; template struct AuditCartesianProduct; template struct AuditCartesianProduct, Surfaces, Packs> : std::bool_constant<(AuditSurfaceConditions::value && ...)> {}; template 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; using BaryonCompilation = mean_field::operators::StellarEquilibriumSpecificationCompilation; STATIC_CHECK(models::SelfDescribingModelSpecification); STATIC_CHECK(models::SelfDescribingModelSpecification); STATIC_CHECK(models::SelfDescribingModelSpecification); STATIC_CHECK(models::SelfDescribingModelSpecification); STATIC_CHECK(models::SelfDescribingModelSpecification); STATIC_CHECK_FALSE( mean_field::operators::DensityVolumeIntegralSpecification< MockFixedBaryonMass>); STATIC_CHECK_FALSE( HasDensityVolumeIntegralContext); 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); STATIC_CHECK(models::SelfDescribingModelSpecification); STATIC_CHECK(models::SelfDescribingModelSpecification); STATIC_CHECK(models::SelfDescribingModelSpecification); STATIC_CHECK(models::SelfDescribingModelSpecification); STATIC_CHECK_FALSE(models::SelfDescribingModelSpecification); STATIC_CHECK_FALSE(models::ModelSpecification); STATIC_CHECK(eos::EquationOfStateModel); STATIC_CHECK(eos::BarotropicClosureEquationOfState); STATIC_CHECK(eos::EquationOfStateModel); STATIC_CHECK_FALSE(eos::BarotropicClosureEquationOfState); STATIC_CHECK(std::same_as>>); STATIC_CHECK(std::same_as>>); STATIC_CHECK(std::same_as>); STATIC_CHECK(std::same_as>); 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); STATIC_CHECK(models::CompleteGeneratedNormalizationFor); STATIC_CHECK(models::CompleteGeneratedManifestFor); STATIC_CHECK(models::CompleteGeneratedScalarDimensionsFor); STATIC_CHECK(models::CompleteGeneratedNormalizationFor); STATIC_CHECK(models::CompleteGeneratedManifestFor); STATIC_CHECK(models::CompleteGeneratedScalarDimensionsFor); STATIC_CHECK( models::physicalScaleForQuantity == 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 == 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); STATIC_CHECK_FALSE( models::CompleteGeneratedScalarDimensionsFor); STATIC_CHECK_FALSE( models::CompleteGeneratedNormalizationFor); STATIC_CHECK_FALSE( models::CompleteGeneratedManifestFor); 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); STATIC_CHECK(models::CompleteGeneratedScalarDimensionsFor); STATIC_CHECK(models::CompleteGeneratedScalarDimensionsFor); 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); STATIC_CHECK(std::same_as< typename BaryonCompilation::DependsOnValueBlocks, mean_field::utils::blocks::type_list>); STATIC_CHECK(std::same_as< typename BaryonCompilation::AffectedResidualBlocks, mean_field::utils::blocks::type_list>); 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; using BaryonResidual = GeneratedResidualBlockFor; using CrossValue = GeneratedValueBlockFor; using CrossResidual = GeneratedResidualBlockFor; STATIC_CHECK(std::same_as< typename CrossCompilation::DependsOnValueBlocks, mean_field::utils::blocks::type_list>); STATIC_CHECK(std::same_as< typename CrossCompilation::AffectedResidualBlocks, mean_field::utils::blocks::type_list>); STATIC_CHECK(CrossCompilation::JacobianCouplings::size == 4); STATIC_CHECK(CrossCompilation::IncidentJacobianCouplings::size == 4); using CrossDerivative = mean_field::stellar::Derivative< mean_field::stellar::equation::ConstraintOf, mean_field::stellar::state::GeneratedCoordinateOf>; STATIC_CHECK(std::same_as< typename CrossDerivative::EquationType, mean_field::stellar::equation::ConstraintOf>); STATIC_CHECK(std::same_as< typename CrossDerivative::StateType, mean_field::stellar::state::GeneratedCoordinateOf>); using CrossModel = MatrixModelT< MockBarotropicEquationOfState, MockIsobaricSurface, CrossConstraintPack>; using CrossSystem = mean_field::operators::CompiledStellarEquilibriumSystem; 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; STATIC_CHECK(models::ModelSpecification); STATIC_CHECK(Contribution::generatedValueArity == 1); STATIC_CHECK(std::same_as); STATIC_CHECK_FALSE(models::CompleteGeneratedNormalizationFor); STATIC_CHECK_FALSE(models::CompleteGeneratedManifestFor); STATIC_CHECK_FALSE(models::CompleteGeneratedNormalizationFor); STATIC_CHECK_FALSE(models::CompleteGeneratedManifestFor); STATIC_CHECK_FALSE(models::CompleteGeneratedScalarDimensionsFor); STATIC_CHECK_FALSE(mean_field::stellar::ScalarConstraintDescription< MalformedNormalization>); STATIC_CHECK(models::ModelSpecification); 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); STATIC_CHECK(HasContributionResidualView); STATIC_CHECK_FALSE(HasContributionStateView); STATIC_CHECK_FALSE(HasContributionResidualView); STATIC_CHECK(mean_field::operators::StellarEquilibriumPhysicsAvailableFor< Contained, BasePhysicsModel>); STATIC_CHECK_FALSE(mean_field::operators::StellarEquilibriumPhysicsAvailableFor< Foreign, BasePhysicsModel>); STATIC_CHECK_FALSE(mean_field::operators::StellarEquilibriumSpecificationBelongsToModel); STATIC_CHECK_FALSE(mean_field::operators::StellarEquilibriumPhysicsAvailableFor); STATIC_CHECK_FALSE(HasContributionStateView); STATIC_CHECK_FALSE(HasContributionResidualView); } 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); STATIC_CHECK_FALSE( models::CompatibleSpecificationRoleAndGeneratedState); STATIC_CHECK_FALSE( models::CompatibleSpecificationRoleAndGeneratedState); STATIC_CHECK_FALSE( models::CompatibleSpecificationRoleAndGeneratedState); STATIC_CHECK(models::CompatibleSpecificationRoleAndGeneratedState); STATIC_CHECK_FALSE( models::CompatibleSpecificationRoleAndGeneratedState); STATIC_CHECK_FALSE( models::CompatibleSpecificationRoleAndGeneratedState); STATIC_CHECK_FALSE( models::CompatibleSpecificationRoleAndGeneratedState); // Invariants own either a Lagrange multiplier or a physical coordinate. STATIC_CHECK(models::CompatibleSpecificationRoleAndGeneratedState); STATIC_CHECK(models::CompatibleSpecificationRoleAndGeneratedState); STATIC_CHECK_FALSE(models::CompatibleSpecificationRoleAndGeneratedState); STATIC_CHECK_FALSE(models::CompatibleSpecificationRoleAndGeneratedState); // Phase and gauge choices close a null direction with a solver border. STATIC_CHECK(models::CompatibleSpecificationRoleAndGeneratedState); STATIC_CHECK_FALSE(models::CompatibleSpecificationRoleAndGeneratedState); STATIC_CHECK_FALSE(models::CompatibleSpecificationRoleAndGeneratedState); STATIC_CHECK_FALSE( models::CompatibleSpecificationRoleAndGeneratedState); STATIC_CHECK(models::CompatibleSpecificationRoleAndGeneratedState); STATIC_CHECK_FALSE(models::CompatibleSpecificationRoleAndGeneratedState); STATIC_CHECK_FALSE(models::CompatibleSpecificationRoleAndGeneratedState); STATIC_CHECK_FALSE( models::CompatibleSpecificationRoleAndGeneratedState); // Rotation laws are currently prescribed closures, not root-state owners. STATIC_CHECK(models::CompatibleSpecificationRoleAndGeneratedState); STATIC_CHECK_FALSE(models::CompatibleSpecificationRoleAndGeneratedState); STATIC_CHECK_FALSE( models::CompatibleSpecificationRoleAndGeneratedState); STATIC_CHECK_FALSE(models::CompatibleSpecificationRoleAndGeneratedState); constexpr auto unknownRole = static_cast(1000); constexpr auto unknownKind = static_cast(1000); STATIC_CHECK_FALSE(models::CompatibleSpecificationRoleAndGeneratedState); STATIC_CHECK_FALSE(models::CompatibleSpecificationRoleAndGeneratedState); using ValidConstitutive = MockRoleStateSpecification; using ValidBoundary = MockRoleStateSpecification; using ValidInvariant = MockRoleStateSpecification; using ValidPhysicalInvariant = MockRoleStateSpecification; using ValidPhase = MockRoleStateSpecification; using ValidGauge = MockRoleStateSpecification; using ValidRotation = MockRoleStateSpecification; using InvalidInvariant = MockRoleStateSpecification; using InvalidBoundary = MockRoleStateSpecification; using InvalidRotation = MockRoleStateSpecification; using UnknownRole = MockRoleStateSpecification; using UnknownKind = MockRoleStateSpecification; STATIC_CHECK(models::ModelSpecification); STATIC_CHECK(models::ModelSpecification); STATIC_CHECK(models::SelfDescribingModelSpecification); STATIC_CHECK(models::ModelSpecification); STATIC_CHECK(models::ModelSpecification); STATIC_CHECK(models::ModelSpecification); STATIC_CHECK(models::ModelSpecification); STATIC_CHECK(models::ModelSpecification); STATIC_CHECK(HasModelDefinitionProjection); STATIC_CHECK(HasSpecificationContribution); STATIC_CHECK(CanEnterSpecificationSet); STATIC_CHECK(models::ValidModelSpecificationPack); STATIC_CHECK(CanDeduceStellarModel); STATIC_CHECK_FALSE(models::SelfDescribingModelSpecification); STATIC_CHECK_FALSE(models::ModelSpecification); STATIC_CHECK_FALSE(models::ResolvedModelSpecification); STATIC_CHECK_FALSE(HasModelDefinitionProjection); STATIC_CHECK_FALSE(HasSpecificationContribution); STATIC_CHECK_FALSE(CanEnterSpecificationSet); STATIC_CHECK_FALSE(models::ValidModelSpecificationPack); STATIC_CHECK_FALSE(CanDeduceStellarModel); STATIC_CHECK_FALSE(models::ModelSpecification); STATIC_CHECK_FALSE(CanDeduceStellarModel); STATIC_CHECK_FALSE(models::ModelSpecification); STATIC_CHECK_FALSE(models::ModelSpecification); STATIC_CHECK_FALSE(models::ModelSpecification); // The physics-facing aliases continue to produce accepted combinations. STATIC_CHECK(models::ModelSpecification); STATIC_CHECK(models::ModelSpecification); STATIC_CHECK(models::ModelSpecification); STATIC_CHECK(models::ModelSpecification); STATIC_CHECK(models::ModelSpecification); STATIC_CHECK(models::ModelSpecification); STATIC_CHECK(models::ModelSpecification); } TEST_CASE("Stable Names Distinguish Independent Same-Role Extensions", tags::stellar_model_specification_api) { constexpr auto baryonKey = models::SpecificationTraits::key; constexpr auto restKey = models::SpecificationTraits::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); STATIC_CHECK_FALSE(models::specificationKeysAreUnique); using Canonical = models::SpecificationSet; using Reordered = models::SpecificationSet; STATIC_CHECK(std::same_as); } 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::value); STATIC_CHECK(model::StellarModelType); STATIC_CHECK(model::StellarModelType); STATIC_CHECK(model::StellarModelType); STATIC_CHECK(model::StellarModelType); STATIC_CHECK_FALSE(std::same_as); STATIC_CHECK_FALSE(std::same_as); 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); STATIC_CHECK(mean_field::operators::StellarEquilibriumSymbolicallyCompilable); STATIC_CHECK(mean_field::operators::StellarEquilibriumSymbolicallyCompilable); STATIC_CHECK(mean_field::operators::StellarEquilibriumSymbolicallyCompilable); STATIC_CHECK(mean_field::normalization::CompleteStellarNormalizationFor< BarotropicIsobaricModel, mean_field::operators::CompiledStellarEquilibriumForm>); STATIC_CHECK_FALSE(mean_field::equilibrium::StellarEquilibriumModel); STATIC_CHECK_FALSE(mean_field::equilibrium::StellarEquilibriumModel); } TEST_CASE("Constraint Packs Produce Their Declared Jacobian Incidence Without Bespoke Combination Types", tags::stellar_model_specification_api) { using Model = MatrixModelT; using System = mean_field::operators::CompiledStellarEquilibriumSystem; 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; using BaryonResidual = GeneratedResidualBlockFor; using RestMassValue = GeneratedValueBlockFor; using RestMassResidual = GeneratedResidualBlockFor; using MagneticAmplitude = GeneratedValueBlockFor; using MagneticEnergyResidual = GeneratedResidualBlockFor; using PhaseBorder = GeneratedValueBlockFor; using PhaseResidual = GeneratedResidualBlockFor; using BaryonCompilation = mean_field::operators::StellarEquilibriumSpecificationCompilation; using RestMassCompilation = mean_field::operators::StellarEquilibriumSpecificationCompilation; using MagneticCompilation = mean_field::operators::StellarEquilibriumSpecificationCompilation; using PhaseCompilation = mean_field::operators::StellarEquilibriumSpecificationCompilation; 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); STATIC_CHECK(mean_field::utils::blocks::has_jacobian_coupling_v); STATIC_CHECK(mean_field::utils::blocks::has_jacobian_coupling_v); STATIC_CHECK_FALSE(mean_field::utils::blocks::has_jacobian_coupling_v); STATIC_CHECK(mean_field::utils::blocks::has_jacobian_coupling_v); STATIC_CHECK(mean_field::utils::blocks::has_jacobian_coupling_v); STATIC_CHECK(mean_field::utils::blocks::has_jacobian_coupling_v); STATIC_CHECK(mean_field::utils::blocks::has_jacobian_coupling_v); STATIC_CHECK(mean_field::utils::blocks::has_jacobian_coupling_v); STATIC_CHECK(mean_field::utils::blocks::has_jacobian_coupling_v); STATIC_CHECK(mean_field::utils::blocks::has_jacobian_coupling_v); STATIC_CHECK(mean_field::utils::blocks::has_jacobian_coupling_v); STATIC_CHECK(mean_field::utils::blocks::has_jacobian_coupling_v); STATIC_CHECK( mean_field::utils::blocks::has_jacobian_coupling_v); STATIC_CHECK_FALSE( mean_field::utils::blocks::has_jacobian_coupling_v); STATIC_CHECK(mean_field::utils::blocks::has_jacobian_coupling_v); STATIC_CHECK(mean_field::utils::blocks::has_jacobian_coupling_v); STATIC_CHECK(mean_field::utils::blocks::has_jacobian_coupling_v); STATIC_CHECK_FALSE(mean_field::utils::blocks::has_jacobian_coupling_v); } TEST_CASE("Canonical Ordering Makes A Constraint Product Independent Of User Spelling", tags::stellar_model_specification_api) { using Canonical = MatrixModelT; using Permuted = model::StellarModel>; STATIC_CHECK(std::same_as); STATIC_CHECK(std::same_as, mean_field::operators::CompiledStellarEquilibriumForm>); STATIC_CHECK(std::same_as, mean_field::operators::CompiledStellarEquilibriumJacobianForm>); } TEST_CASE("Manifest Capability Is Inferred Separately From Symbolic And Runtime Capability", tags::stellar_model_specification_api) { using BarotropicManifestModel = MatrixModelT; using NonBarotropicManifestModel = MatrixModelT; using BarotropicForm = mean_field::operators::CompiledStellarEquilibriumForm; using NonBarotropicForm = mean_field::operators::CompiledStellarEquilibriumForm; STATIC_CHECK(mean_field::operators::StellarEquilibriumSymbolicallyCompilable); STATIC_CHECK(mean_field::operators::StellarEquilibriumSymbolicallyCompilable); STATIC_CHECK(mean_field::normalization::CompleteStellarNormalizationFor); STATIC_CHECK(mean_field::normalization::CompleteStellarNormalizationFor); STATIC_CHECK(mean_field::operators::CompilableRootManifestFor); STATIC_CHECK(mean_field::operators::CompilableRootManifestFor); // 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); STATIC_CHECK_FALSE(mean_field::operators::hasStellarEquilibriumCoreRuntime); STATIC_CHECK_FALSE(mean_field::equilibrium::StellarEquilibriumModel); STATIC_CHECK_FALSE(mean_field::equilibrium::StellarEquilibriumModel); } 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{0.375}}), MockFixedBaryonMass({.targetMass = 1.75}), MockFixedMagneticSpecificEnergy({.targetSpecificEnergy = 0.125}), MockCentralEnthalpyPhase({.targetEnthalpy = 0.75})); using Model = std::remove_cvref_t; STATIC_CHECK(Model::template specificationRoleCount == 1); STATIC_CHECK(Model::template specificationRoleCount == 1); STATIC_CHECK(Model::template specificationRoleCount == 2); STATIC_CHECK(Model::template specificationRoleCount == 1); STATIC_CHECK(std::same_as); STATIC_CHECK(std::same_as< typename Model::template SpecificationForRole, MockNonBarotropicEquationOfState>); STATIC_CHECK(std::same_as< typename Model::template SpecificationsForRole, models::ModelTypeList>); STATIC_CHECK(std::same_as, MockNonBarotropicEquationOfState>); STATIC_CHECK(std::same_as, MockIsothermalSurface>); STATIC_CHECK(model::HasEquationOfState); STATIC_CHECK(model::HasSurfaceCondition); STATIC_CHECK(model::HasUniqueSurfaceCondition); STATIC_CHECK_FALSE(HasInvariantRoleAccessor); CHECK(stellarModel.equationOfState().entropyCoupling() == 2.5); CHECK(stellarModel.surfaceCondition().targetTemperature() == eos::QuantityValue{0.375}); CHECK(stellarModel.specification().targetMass() == 1.75); CHECK(stellarModel.specificationForRole().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{0.0}}))); STATIC_CHECK(model::StellarModelType); STATIC_CHECK(model::specificationRoleCount == 0); STATIC_CHECK_FALSE(model::HasSurfaceCondition); STATIC_CHECK_FALSE(model::HasUniqueSurfaceCondition); STATIC_CHECK_FALSE(HasSurfaceConditionType); STATIC_CHECK_FALSE(HasSurfaceConditionAccessor); STATIC_CHECK(model::StellarModelType); STATIC_CHECK(model::specificationRoleCount == 2); STATIC_CHECK(model::HasSurfaceCondition); STATIC_CHECK_FALSE(model::HasUniqueSurfaceCondition); STATIC_CHECK_FALSE(HasSurfaceConditionType); STATIC_CHECK_FALSE(HasSurfaceConditionAccessor); STATIC_CHECK(model::specificationRoleCount == 0); STATIC_CHECK_FALSE(model::HasSpecificationsForRole); }