module; #include #include #include #include #include #include #include #include #include #include #include #include export module mean_field:model.specifications; export import :eos.polytrope; export import :surface.constant; export namespace mean_field::models { enum class SpecificationRole { constitutive_law, boundary_condition, invariant, phase_condition, gauge_choice, rotation_law }; /* * The small declarations in this section are the physics-facing model * extension API. A specification owns one nested ModelDefinition and the * compiler projects the lower-level traits from it. Extension authors do * not specialize a registry or choose a globally coordinated ordinal. */ template struct FixedString final { char characters[Extent]{}; consteval FixedString(const char (&text)[Extent]) noexcept { for (std::size_t index = 0; index < Extent; ++index) { characters[index] = text[index]; } } [[nodiscard]] constexpr std::string_view view() const noexcept { static_assert(Extent > 0); return {characters, Extent - 1}; } constexpr bool operator==(const FixedString &) const = default; }; template FixedString(const char (&)[Extent]) -> FixedString; template struct ModelTypeList final { static constexpr std::size_t size = sizeof...(Types); }; template using DependsOn = ModelTypeList; template using Affects = ModelTypeList; /* * Physics vocabulary for declaring how a stellar specification couples to * the equilibrium system. These names deliberately do not import the * solver's block registry: the operator compiler translates them once at * its backend boundary. Advanced extensions may still place an existing * backend block type directly in DependsOn/Affects. */ namespace stellar { namespace state { struct Density final { }; struct SurfaceShape final { }; struct GravityGradient final { }; struct GravitationalPotential final { }; struct SpecificEnthalpy final { }; /* * A coordinate generated by another named specification. This is * the physics-facing spelling for coupled global constraints: an * extension names the constraint it reads, never its solver block. */ template struct GeneratedCoordinateOf final { using SpecificationType = Specification; }; /* * The coordinate generated by the specification containing this * marker. For example, FixedAngularMomentum uses it to state that * its integral residual depends on angular velocity without naming * a generated solver block. */ struct OwnGeneratedCoordinate final { }; } // namespace state namespace equation { struct GravityGradientDefinition final { }; struct PoissonEquation final { }; struct DensityClosure final { }; struct SurfaceShapeBalance final { }; struct HydrostaticBalance final { }; /* The scalar constraint equation owned by this specification. */ struct OwnConstraint final { }; /* The scalar constraint equation owned by another specification. */ template struct ConstraintOf final { using SpecificationType = Specification; }; } // namespace equation /* * A readable, compile-time label for one declared Jacobian derivative. * Runtime providers consume this vocabulary without learning backend * row and column block types. */ template struct Derivative final { using EquationType = Equation; using StateType = State; }; } // namespace stellar enum class GeneratedStateKind { none, multiplier, physical_coordinate, solver_border }; enum class RieszTopology { unavailable, identity, scalar_volume_l2, vector_volume_l2, scalar_boundary_l2, hybrid_scalar_volume_point_rows, global_scalar }; enum class PhysicalScaleLaw { unavailable, dimensionless, density, length, acceleration, inverse_time_squared, specific_energy, pressure, mass, force, angular_velocity, angular_momentum }; /* * PhysicalScaleLaw is the numerical scaling vocabulary, while the * dimensions module carries the authoritative semantic quantity types. * Keep their relationship in one extensible trait so a physics-facing * scalar declaration cannot spell a quantity in one place and an * unrelated normalization scale somewhere else. */ template struct PhysicalScaleForQuantity { static constexpr PhysicalScaleLaw value = PhysicalScaleLaw::unavailable; }; template <> struct PhysicalScaleForQuantity { static constexpr PhysicalScaleLaw value = PhysicalScaleLaw::dimensionless; }; template <> struct PhysicalScaleForQuantity { static constexpr PhysicalScaleLaw value = PhysicalScaleLaw::mass; }; template <> struct PhysicalScaleForQuantity { static constexpr PhysicalScaleLaw value = PhysicalScaleLaw::length; }; template <> struct PhysicalScaleForQuantity { static constexpr PhysicalScaleLaw value = PhysicalScaleLaw::density; }; template <> struct PhysicalScaleForQuantity { static constexpr PhysicalScaleLaw value = PhysicalScaleLaw::acceleration; }; template <> struct PhysicalScaleForQuantity { static constexpr PhysicalScaleLaw value = PhysicalScaleLaw::specific_energy; }; template <> struct PhysicalScaleForQuantity { static constexpr PhysicalScaleLaw value = PhysicalScaleLaw::specific_energy; }; template <> struct PhysicalScaleForQuantity { static constexpr PhysicalScaleLaw value = PhysicalScaleLaw::specific_energy; }; template <> struct PhysicalScaleForQuantity { static constexpr PhysicalScaleLaw value = PhysicalScaleLaw::pressure; }; template <> struct PhysicalScaleForQuantity { static constexpr PhysicalScaleLaw value = PhysicalScaleLaw::force; }; template <> struct PhysicalScaleForQuantity { static constexpr PhysicalScaleLaw value = PhysicalScaleLaw::angular_velocity; }; template <> struct PhysicalScaleForQuantity { static constexpr PhysicalScaleLaw value = PhysicalScaleLaw::angular_momentum; }; template inline constexpr PhysicalScaleLaw physicalScaleForQuantity = PhysicalScaleForQuantity>::value; template concept PhysicalScaleRepresentedQuantity = dimensions::PhysicalQuantityType && physicalScaleForQuantity != PhysicalScaleLaw::unavailable && requires { typename std::bool_constant(Quantity::identifier).empty()>; }; namespace detail { template [[nodiscard]] consteval bool declaredCoordinateNormalizationIsAvailable() { if constexpr (requires { { Candidate::available } -> std::convertible_to; }) { return static_cast(Candidate::available); } else { return false; } } [[nodiscard]] consteval bool isKnownSpecificationRole(const SpecificationRole role) { switch (role) { case SpecificationRole::constitutive_law: case SpecificationRole::boundary_condition: case SpecificationRole::invariant: case SpecificationRole::phase_condition: case SpecificationRole::gauge_choice: case SpecificationRole::rotation_law: return true; } return false; } [[nodiscard]] consteval bool isKnownGeneratedStateKind(const GeneratedStateKind kind) { switch (kind) { case GeneratedStateKind::none: case GeneratedStateKind::multiplier: case GeneratedStateKind::physical_coordinate: case GeneratedStateKind::solver_border: return true; } return false; } [[nodiscard]] consteval bool specificationRoleAcceptsGeneratedStateKind( const SpecificationRole role, const GeneratedStateKind kind ) { switch (role) { case SpecificationRole::constitutive_law: case SpecificationRole::boundary_condition: return kind == GeneratedStateKind::none; case SpecificationRole::invariant: return kind == GeneratedStateKind::multiplier || kind == GeneratedStateKind::physical_coordinate; case SpecificationRole::phase_condition: case SpecificationRole::gauge_choice: return kind == GeneratedStateKind::solver_border; case SpecificationRole::rotation_law: /* * Rotation laws currently prescribe a physical profile; they * do not own a root coordinate. Fixed angular momentum owns * angular velocity as an invariant/physical-coordinate pair. * Keep this closed until a state-generating rotation-law * contract is designed and implemented end to end. */ return kind == GeneratedStateKind::none; } return false; } } // namespace detail template concept CompatibleSpecificationRoleAndGeneratedState = detail::isKnownSpecificationRole(Role) && detail::isKnownGeneratedStateKind(StateKind) && detail::specificationRoleAcceptsGeneratedStateKind(Role, StateKind); template struct CoordinateNormalization final { static constexpr RieszTopology topology = Topology; static constexpr PhysicalScaleLaw scale = Scale; static constexpr bool available = topology != RieszTopology::unavailable && scale != PhysicalScaleLaw::unavailable; }; using UnavailableCoordinateNormalization = CoordinateNormalization; template < typename ValueNormalization = UnavailableCoordinateNormalization, typename ResidualNormalization = UnavailableCoordinateNormalization> struct GeneratedNormalization final { using Value = ValueNormalization; using Residual = ResidualNormalization; static constexpr bool available = detail::declaredCoordinateNormalizationIsAvailable() && detail::declaredCoordinateNormalizationIsAvailable(); }; using UnavailableGeneratedNormalization = GeneratedNormalization<>; template using GlobalScalarNormalization = GeneratedNormalization< CoordinateNormalization, CoordinateNormalization>; template < FixedString ValueStableId = "", FixedString ValueSymbol = "", FixedString ResidualStableId = "", FixedString ResidualSymbol = "", FixedString TargetUnits = "", FixedString ResidualUnits = ""> struct GeneratedManifest final { private: inline static constexpr auto valueStableIdStorage = ValueStableId; inline static constexpr auto valueSymbolStorage = ValueSymbol; inline static constexpr auto residualStableIdStorage = ResidualStableId; inline static constexpr auto residualSymbolStorage = ResidualSymbol; inline static constexpr auto targetUnitsStorage = TargetUnits; inline static constexpr auto residualUnitsStorage = ResidualUnits; public: static constexpr std::string_view valueStableId = valueStableIdStorage.view(); static constexpr std::string_view valueSymbol = valueSymbolStorage.view(); static constexpr std::string_view residualStableId = residualStableIdStorage.view(); static constexpr std::string_view residualSymbol = residualSymbolStorage.view(); static constexpr std::string_view targetUnits = targetUnitsStorage.view(); static constexpr std::string_view residualUnits = residualUnitsStorage.view(); static constexpr bool available = !valueStableId.empty() && !valueSymbol.empty() && !residualStableId.empty() && !residualSymbol.empty() && !targetUnits.empty() && !residualUnits.empty(); }; using UnavailableGeneratedManifest = GeneratedManifest<>; /* * A scalar constraint has three independent dimensional statements: * * - the physical quantity supplied as its target; * - the generated Newton coordinate; and * - the appended scalar residual. * * They are deliberately not equated. FixedCentralDensity, for example, * has a density target but a specific-enthalpy phase residual. The * quantity types below generate both numerical scale laws and diagnostic * unit labels, making the strings presentation rather than authority. */ template < PhysicalScaleRepresentedQuantity TargetQuantityT, PhysicalScaleRepresentedQuantity GeneratedCoordinateQuantityT, PhysicalScaleRepresentedQuantity ConstraintResidualQuantityT, FixedString ValueStableId, FixedString ValueSymbol, FixedString ResidualStableId, FixedString ResidualSymbol> struct DimensionalScalarConstraint final { using TargetQuantity = TargetQuantityT; using GeneratedCoordinateQuantity = GeneratedCoordinateQuantityT; using ConstraintResidualQuantity = ConstraintResidualQuantityT; using TargetValue = dimensions::QuantityValue; static constexpr PhysicalScaleLaw targetScale = physicalScaleForQuantity; struct Normalization final { using TargetQuantity = TargetQuantityT; using GeneratedCoordinateQuantity = GeneratedCoordinateQuantityT; using ConstraintResidualQuantity = ConstraintResidualQuantityT; using TargetValue = dimensions::QuantityValue; static constexpr PhysicalScaleLaw targetScale = physicalScaleForQuantity; using Value = CoordinateNormalization< RieszTopology::global_scalar, physicalScaleForQuantity>; using Residual = CoordinateNormalization< RieszTopology::global_scalar, physicalScaleForQuantity>; static constexpr bool available = Value::available && Residual::available; }; struct Manifest final { using TargetQuantity = TargetQuantityT; using GeneratedCoordinateQuantity = GeneratedCoordinateQuantityT; using ConstraintResidualQuantity = ConstraintResidualQuantityT; private: inline static constexpr auto valueStableIdStorage = ValueStableId; inline static constexpr auto valueSymbolStorage = ValueSymbol; inline static constexpr auto residualStableIdStorage = ResidualStableId; inline static constexpr auto residualSymbolStorage = ResidualSymbol; public: static constexpr std::string_view valueStableId = valueStableIdStorage.view(); static constexpr std::string_view valueSymbol = valueSymbolStorage.view(); static constexpr std::string_view residualStableId = residualStableIdStorage.view(); static constexpr std::string_view residualSymbol = residualSymbolStorage.view(); static constexpr std::string_view targetUnits = TargetQuantity::identifier; static constexpr std::string_view residualUnits = ConstraintResidualQuantity::identifier; static constexpr bool available = !valueStableId.empty() && !valueSymbol.empty() && !residualStableId.empty() && !residualSymbol.empty() && !targetUnits.empty() && !residualUnits.empty(); }; static constexpr bool dimensionallyTyped = true; }; template < typename Specification, FixedString StableName, SpecificationRole Role, GeneratedStateKind StateKind = GeneratedStateKind::none, typename DependsOnBlocks = ModelTypeList<>, typename AffectedResidualBlocks = ModelTypeList<>, typename NormalizationDefinition = UnavailableGeneratedNormalization, typename ManifestDefinition = UnavailableGeneratedManifest> struct ModelDefinition final { using SpecificationType = Specification; using DependsOn = DependsOnBlocks; using Affects = AffectedResidualBlocks; using Normalization = NormalizationDefinition; using Manifest = ManifestDefinition; private: inline static constexpr auto stableNameStorage = StableName; public: static constexpr std::string_view name = stableNameStorage.view(); static constexpr SpecificationRole role = Role; static constexpr GeneratedStateKind generatedStateKind = StateKind; static constexpr std::size_t generatedValueArity = StateKind == GeneratedStateKind::none ? 0U : 1U; static constexpr std::size_t generatedResidualArity = StateKind == GeneratedStateKind::none ? 0U : 1U; static constexpr bool structurallyAvailable = !name.empty(); }; template using ConstitutiveLaw = ModelDefinition; template using BoundaryCondition = ModelDefinition; template < typename Specification, FixedString Name, typename DependsOn = ModelTypeList<>, typename Affects = ModelTypeList<>, typename Normalization = UnavailableGeneratedNormalization, typename Manifest = UnavailableGeneratedManifest> using FixedIntegralWithMultiplier = ModelDefinition< Specification, Name, SpecificationRole::invariant, GeneratedStateKind::multiplier, DependsOn, Affects, Normalization, Manifest>; template < typename Specification, FixedString Name, typename DependsOn = ModelTypeList<>, typename Affects = ModelTypeList<>, typename Normalization = UnavailableGeneratedNormalization, typename Manifest = UnavailableGeneratedManifest> using FixedIntegralWithPhysicalCoordinate = ModelDefinition< Specification, Name, SpecificationRole::invariant, GeneratedStateKind::physical_coordinate, DependsOn, Affects, Normalization, Manifest>; template < typename Specification, FixedString Name, typename DependsOn = ModelTypeList<>, typename Affects = ModelTypeList<>, typename Normalization = UnavailableGeneratedNormalization, typename Manifest = UnavailableGeneratedManifest> using PhaseCondition = ModelDefinition< Specification, Name, SpecificationRole::phase_condition, GeneratedStateKind::solver_border, DependsOn, Affects, Normalization, Manifest>; struct SpecificationKey final { SpecificationRole role; GeneratedStateKind generatedStateKind; std::string_view stableName; constexpr auto operator<=>(const SpecificationKey &) const = default; }; struct SpecificationDescriptor final { std::string_view name; SpecificationRole role; SpecificationKey key; std::size_t generatedValueArity; std::size_t generatedResidualArity; constexpr bool operator==(const SpecificationDescriptor &) const = default; }; enum class EquilibriumSystemCompilation { complete_equilibrium_system, equation_contributions_only, // Transitional spellings retained while internal solver code is // migrated to physics-facing equilibrium-system terminology. isolated_root = complete_equilibrium_system, assembly_only = equation_contributions_only }; using ModelCompilationClass = EquilibriumSystemCompilation; struct RuntimeSpecificationDescriptor final { SpecificationDescriptor specification; std::size_t canonicalIndex; // This reports only the self-owned physics declaration. Numerical // support is queried from the operator compiler for the complete model. bool hasDeclarativeDefinition; constexpr bool operator==(const RuntimeSpecificationDescriptor &) const = default; }; namespace detail { template struct IsModelTypeList : std::false_type { }; template struct IsModelTypeList> : std::true_type { }; template struct IsModelDefinition : std::false_type { }; template < typename Specification, FixedString StableName, SpecificationRole Role, GeneratedStateKind StateKind, typename DependsOn, typename Affects, typename Normalization, typename Manifest> struct IsModelDefinition< ModelDefinition> : std::bool_constant< (StableName.view().size() > 0) && CompatibleSpecificationRoleAndGeneratedState && IsModelTypeList::value && IsModelTypeList::value> { }; template ::value> struct DefinitionDescribesCandidate : std::false_type { }; template struct DefinitionDescribesCandidate : std::bool_constant> { }; template struct SpecificationDefinitionFor { static constexpr bool available = false; }; template struct SpecificationDefinitionFor< Candidate, std::void_t::ModelDefinition>> { using Type = typename std::remove_cvref_t::ModelDefinition; static constexpr bool available = DefinitionDescribesCandidate>::value; }; // Compatibility projections for the two physical types that predate // the self-describing front end. New types use only ModelDefinition. template <> struct SpecificationDefinitionFor { using Type = ConstitutiveLaw; static constexpr bool available = true; }; template <> struct SpecificationDefinitionFor { using Type = BoundaryCondition; static constexpr bool available = true; }; } // namespace detail template concept SelfDescribingModelSpecification = requires { typename std::remove_cvref_t::ModelDefinition; } && detail::SpecificationDefinitionFor>::available; template requires detail::SpecificationDefinitionFor>::available using ModelDefinitionForT = typename detail::SpecificationDefinitionFor>::Type; template struct SpecificationTraits; template requires detail::SpecificationDefinitionFor>::available struct SpecificationTraits { using Definition = ModelDefinitionForT; static constexpr std::string_view name = Definition::name; static constexpr SpecificationRole role = Definition::role; static constexpr SpecificationKey key{role, Definition::generatedStateKind, name}; }; template concept ModelSpecification = detail::SpecificationDefinitionFor>::available && requires { typename std::remove_cvref_t::Parameters; { SpecificationTraits>::name } -> std::convertible_to; { SpecificationTraits>::role } -> std::convertible_to; { SpecificationTraits>::key } -> std::convertible_to; } && std::constructible_from, typename std::remove_cvref_t::Parameters>; class FixedTotalMass final { public: struct Parameters final { dimensions::MassValue Mtotal; }; using ScalarDescription = DimensionalScalarConstraint< dimensions::quantity::Mass, dimensions::quantity::SpecificEnergy, dimensions::quantity::Mass, "fixed_total_mass.multiplier", "C", "fixed_total_mass.residual", "R_M">; using TargetValue = typename ScalarDescription::TargetValue; using ModelDefinition = FixedIntegralWithMultiplier< FixedTotalMass, "FixedTotalMass", DependsOn, Affects, typename ScalarDescription::Normalization, typename ScalarDescription::Manifest>; explicit FixedTotalMass(const Parameters parameters) : FixedTotalMass(parameters.Mtotal) { } explicit FixedTotalMass(const TargetValue targetMass) : m_targetMass(targetMass) { if (!std::isfinite(targetMass.value()) || targetMass.value() <= 0.0) { throw std::invalid_argument( std::format( "The fixed total mass must be finite and positive. " "Instead M = {} was provided.", targetMass.value() ) ); } } [[nodiscard]] TargetValue targetMass() const noexcept { return m_targetMass; } [[nodiscard]] TargetValue target() const noexcept { return m_targetMass; } private: TargetValue m_targetMass; }; class FixedAngularMomentum final { public: struct Parameters final { dimensions::AngularMomentumValue Jtotal; std::array axis{0.0, 0.0, 1.0}; std::array center{0.0, 0.0, 0.0}; }; using ScalarDescription = DimensionalScalarConstraint< dimensions::quantity::AngularMomentum, dimensions::quantity::AngularVelocity, dimensions::quantity::AngularMomentum, "fixed_angular_momentum.angular_velocity", "Omega", "fixed_angular_momentum.residual", "R_J">; using TargetValue = typename ScalarDescription::TargetValue; using ModelDefinition = FixedIntegralWithPhysicalCoordinate< FixedAngularMomentum, "FixedAngularMomentum", DependsOn, Affects, typename ScalarDescription::Normalization, typename ScalarDescription::Manifest>; explicit FixedAngularMomentum(const Parameters parameters) : m_targetAngularMomentum(parameters.Jtotal), m_axis(parameters.axis), m_center(parameters.center) { if (!std::isfinite(m_targetAngularMomentum.value()) || m_targetAngularMomentum.value() < 0.0) { throw std::invalid_argument( std::format( "The fixed total angular momentum must be finite and " "nonnegative. Instead J = {} was " "provided.", m_targetAngularMomentum.value() ) ); } double axisNormSquared = 0.0; for (std::size_t component = 0; component < m_axis.size(); ++component) { if (!std::isfinite(m_axis[component]) || !std::isfinite(m_center[component])) { throw std::invalid_argument( "A fixed-angular-momentum rotation axis and center must contain " "only finite values." ); } axisNormSquared += m_axis[component] * m_axis[component]; } if (!std::isfinite(axisNormSquared) || axisNormSquared <= 0.0) { throw std::invalid_argument("A fixed-angular-momentum rotation axis must be nonzero."); } const double inverseAxisNorm = 1.0 / std::sqrt(axisNormSquared); for (double &component : m_axis) { component *= inverseAxisNorm; } } explicit FixedAngularMomentum(const TargetValue targetAngularMomentum) : FixedAngularMomentum(Parameters{.Jtotal = targetAngularMomentum}) { } [[nodiscard]] TargetValue targetAngularMomentum() const noexcept { return m_targetAngularMomentum; } [[nodiscard]] TargetValue target() const noexcept { return m_targetAngularMomentum; } [[nodiscard]] const std::array< double, 3> & axis() const noexcept { return m_axis; } [[nodiscard]] const std::array< double, 3> & center() const noexcept { return m_center; } private: TargetValue m_targetAngularMomentum; std::array m_axis; std::array m_center; }; class FixedCentralDensity final { public: struct Parameters final { dimensions::DensityValue RhoC; }; using ScalarDescription = DimensionalScalarConstraint< dimensions::quantity::Density, dimensions::quantity::SpecificEnthalpy, dimensions::quantity::SpecificEnthalpy, "fixed_central_density.border", "lambda_rho_c", "fixed_central_density.residual", "R_rho_c">; using TargetValue = typename ScalarDescription::TargetValue; using ModelDefinition = PhaseCondition< FixedCentralDensity, "FixedCentralDensity", DependsOn, Affects, typename ScalarDescription::Normalization, typename ScalarDescription::Manifest>; explicit FixedCentralDensity(const Parameters parameters) : FixedCentralDensity(parameters.RhoC) { } explicit FixedCentralDensity(const TargetValue targetDensity) : m_targetDensity(targetDensity) { if (!std::isfinite(targetDensity.value()) || targetDensity.value() <= 0.0) { throw std::invalid_argument( std::format( "The fixed central density must be finite and positive. " "Instead rho_c = {} was provided.", targetDensity.value() ) ); } } [[nodiscard]] TargetValue targetDensity() const noexcept { return m_targetDensity; } [[nodiscard]] TargetValue target() const noexcept { return m_targetDensity; } private: TargetValue m_targetDensity; }; template struct ModelTypeListContains; template struct ModelTypeListContains> : std::bool_constant<(std::same_as || ...)> { }; template inline constexpr bool modelTypeListContains = ModelTypeListContains::value; template struct ResidualFor final { using SpecificationType = Specification; static constexpr std::size_t scalarArity = 1; }; template struct MultiplierFor final { using SpecificationType = Specification; static constexpr std::size_t scalarArity = 1; }; // A generated state variable that participates directly in the physical // equations, rather than serving only as a Lagrange multiplier or border. template struct PhysicalCoordinateFor final { using SpecificationType = Specification; static constexpr std::size_t scalarArity = 1; }; template struct BorderFor final { using SpecificationType = Specification; static constexpr std::size_t scalarArity = 1; }; template concept CoordinateNormalizationDefinition = requires { { Candidate::topology } -> std::convertible_to; { Candidate::scale } -> std::convertible_to; { Candidate::available } -> std::convertible_to; }; template concept GeneratedNormalizationDefinition = requires { typename Candidate::Value; typename Candidate::Residual; requires CoordinateNormalizationDefinition; requires CoordinateNormalizationDefinition; { Candidate::available } -> std::convertible_to; }; template concept GeneratedManifestDefinition = requires { { Candidate::valueStableId } -> std::convertible_to; { Candidate::valueSymbol } -> std::convertible_to; { Candidate::residualStableId } -> std::convertible_to; { Candidate::residualSymbol } -> std::convertible_to; { Candidate::targetUnits } -> std::convertible_to; { Candidate::residualUnits } -> std::convertible_to; { Candidate::available } -> std::convertible_to; }; namespace detail { template struct GeneratedSignatureFor; template struct GeneratedSignatureFor { using Values = ModelTypeList<>; using Residuals = ModelTypeList<>; }; template struct GeneratedSignatureFor { using Values = ModelTypeList>; using Residuals = ModelTypeList>; }; template struct GeneratedSignatureFor { using Values = ModelTypeList>; using Residuals = ModelTypeList>; }; template struct GeneratedSignatureFor { using Values = ModelTypeList>; using Residuals = ModelTypeList>; }; template struct SafeGeneratedNormalization { using Type = UnavailableGeneratedNormalization; }; template struct SafeGeneratedNormalization { using Type = Candidate; }; template struct SafeGeneratedManifest { using Type = UnavailableGeneratedManifest; }; template struct SafeGeneratedManifest { using Type = Candidate; }; } // namespace detail template struct SpecificationContribution { private: using CanonicalSpecification = std::remove_cvref_t; using Definition = ModelDefinitionForT; using Signature = detail::GeneratedSignatureFor; public: using SpecificationType = CanonicalSpecification; using ModelDefinition = Definition; using GeneratedValues = typename Signature::Values; using GeneratedResiduals = typename Signature::Residuals; using DependsOn = typename Definition::DependsOn; using Affects = typename Definition::Affects; using Normalization = typename detail::SafeGeneratedNormalization::Type; using Manifest = typename detail::SafeGeneratedManifest::Type; static constexpr GeneratedStateKind generatedStateKind = Definition::generatedStateKind; static constexpr std::size_t generatedValueArity = Definition::generatedValueArity; static constexpr std::size_t generatedResidualArity = Definition::generatedResidualArity; static constexpr bool isDefined = Definition::structurallyAvailable; static constexpr bool hasDeclarativeDefinition = Definition::structurallyAvailable; }; namespace detail { template [[nodiscard]] consteval bool generatedScalarDimensionsAreCoherent() { using Contribution = SpecificationContribution; using Normalization = typename Contribution::Normalization; using Manifest = typename Contribution::Manifest; if constexpr (Contribution::generatedValueArity == 0) { return true; } else { constexpr bool normalizationIsTyped = requires { typename Normalization::TargetQuantity; typename Normalization::GeneratedCoordinateQuantity; typename Normalization::ConstraintResidualQuantity; typename Normalization::TargetValue; { Normalization::targetScale } -> std::convertible_to; }; constexpr bool manifestIsTyped = requires { typename Manifest::TargetQuantity; typename Manifest::GeneratedCoordinateQuantity; typename Manifest::ConstraintResidualQuantity; }; /* The lower-level declaration API remains a deliberate * compatibility escape hatch. Once either half opts into the * dimensional protocol, however, the complete typed contract * is mandatory and cannot be mixed with free-form metadata. */ if constexpr (!normalizationIsTyped && !manifestIsTyped) { return true; } else if constexpr (!normalizationIsTyped || !manifestIsTyped) { return false; } else { using TargetQuantity = typename Normalization::TargetQuantity; using GeneratedCoordinateQuantity = typename Normalization::GeneratedCoordinateQuantity; using ConstraintResidualQuantity = typename Normalization::ConstraintResidualQuantity; using ValueNormalization = typename Normalization::Value; using ResidualNormalization = typename Normalization::Residual; if constexpr ( !PhysicalScaleRepresentedQuantity || !PhysicalScaleRepresentedQuantity || !PhysicalScaleRepresentedQuantity ) { return false; } else if constexpr ( !requires { typename std::integral_constant< PhysicalScaleLaw, static_cast(Normalization::targetScale)>; typename std::integral_constant< PhysicalScaleLaw, static_cast(ValueNormalization::scale)>; typename std::integral_constant< PhysicalScaleLaw, static_cast(ResidualNormalization::scale)>; typename std::bool_constant< static_cast(Manifest::targetUnits) == TargetQuantity::identifier>; typename std::bool_constant< static_cast(Manifest::residualUnits) == ConstraintResidualQuantity::identifier>; } ) { return false; } else if constexpr (!requires(const Specification &specification) { specification.target(); }) { return false; } else { return std::same_as< typename Normalization::TargetValue, dimensions::QuantityValue> && Normalization::targetScale == physicalScaleForQuantity && std::same_as && std::same_as< GeneratedCoordinateQuantity, typename Manifest::GeneratedCoordinateQuantity> && std::same_as< ConstraintResidualQuantity, typename Manifest::ConstraintResidualQuantity> && ValueNormalization::scale == physicalScaleForQuantity && ResidualNormalization::scale == physicalScaleForQuantity && static_cast(Manifest::targetUnits) == TargetQuantity::identifier && static_cast(Manifest::residualUnits) == ConstraintResidualQuantity::identifier && std::same_as< std::remove_cvref_t().target())>, typename Normalization::TargetValue>; } } } } } // namespace detail template concept CompleteGeneratedScalarDimensionsFor = ModelSpecification && detail::generatedScalarDimensionsAreCoherent>(); template concept CompleteGeneratedNormalizationFor = ModelSpecification && CompleteGeneratedScalarDimensionsFor && (SpecificationContribution>::generatedValueArity == 0 || SpecificationContribution>::Normalization::available); template concept CompleteGeneratedManifestFor = ModelSpecification && CompleteGeneratedScalarDimensionsFor && (SpecificationContribution>::generatedValueArity == 0 || SpecificationContribution>::Manifest::available); template concept ResolvedModelSpecification = ModelSpecification && SpecificationContribution>::isDefined; namespace detail { template struct SpecificationSetStorage final { static constexpr std::size_t size = sizeof...(Specifications); }; template struct ConcatenateModelTypeLists; template <> struct ConcatenateModelTypeLists<> { using Type = ModelTypeList<>; }; template struct ConcatenateModelTypeLists> { using Type = ModelTypeList; }; template struct ConcatenateModelTypeLists, ModelTypeList, Remaining...> { using Type = typename ConcatenateModelTypeLists, Remaining...>::Type; }; template struct SpecificationsForRole { using Type = ModelTypeList<>; static constexpr bool available = false; static constexpr std::size_t count = 0; }; template struct SpecificationsForRole> { using Type = typename ConcatenateModelTypeLists::role == Role, ModelTypeList, ModelTypeList<>>...>::Type; static constexpr bool available = true; static constexpr std::size_t count = Type::size; }; template struct UniqueModelType; template struct UniqueModelType> { using TypeValue = Type; }; template struct InsertSpecification; template struct InsertSpecification> { using Type = SpecificationSetStorage; }; template struct InsertSpecification> { private: using InsertedTail = typename InsertSpecification>::Type; template struct PrependSpecification; template struct PrependSpecification> { using Type = SpecificationSetStorage; }; public: using Type = std::conditional_t< (SpecificationTraits::key < SpecificationTraits::key), SpecificationSetStorage, typename PrependSpecification::Type>; }; template struct CanonicalizeSpecifications; template struct CanonicalizeSpecifications { using Type = Set; }; template struct CanonicalizeSpecifications { using Inserted = typename InsertSpecification::Type; using Type = typename CanonicalizeSpecifications::Type; }; template using CanonicalSpecificationSet = typename CanonicalizeSpecifications, Specifications...>::Type; template < ModelSpecification Head, ModelSpecification... Tail> consteval bool specificationKeyIsUnique() { constexpr auto headKey = SpecificationTraits::key; return ( (headKey.role != SpecificationTraits::key.role || headKey.stableName != SpecificationTraits::key.stableName) && ... ); } template struct SpecificationKeysAreUnique; template <> struct SpecificationKeysAreUnique<> : std::true_type { }; template struct SpecificationKeysAreUnique : std::bool_constant< specificationKeyIsUnique() && SpecificationKeysAreUnique::value> { }; template inline constexpr std::size_t specificationRoleCount = (std::size_t{0} + ... + (SpecificationTraits::role == Role ? 1 : 0)); template struct ModelTypeListScalarArity; template struct ModelTypeListScalarArity> : std::integral_constant { }; template inline constexpr bool isOneOf = (std::same_as || ...); template inline constexpr std::size_t typeCount = (std::size_t{0} + ... + (std::same_as> ? std::size_t{1} : std::size_t{0})); template struct ArgumentsMatchCanonicalSpecifications; template struct ArgumentsMatchCanonicalSpecifications, Arguments...> : std::bool_constant< sizeof...(CanonicalSpecifications) == sizeof...(Arguments) && (isOneOf, CanonicalSpecifications...> && ...) && ((typeCount == 1) && ...)> { }; } // namespace detail template requires(ModelSpecification> && ...) inline constexpr bool specificationKeysAreUnique = detail::SpecificationKeysAreUnique...>::value; template concept ValidModelSpecificationPack = (ResolvedModelSpecification> && ...) && specificationKeysAreUnique...> && detail::specificationRoleCount...> == 1; template requires(ModelSpecification> && ...) && specificationKeysAreUnique...> using SpecificationSet = detail::CanonicalSpecificationSet...>; template using SpecificationsForRoleT = typename detail::SpecificationsForRole>::Type; template inline constexpr std::size_t specificationRoleCount = detail::SpecificationsForRole>::count; template concept HasSpecificationsForRole = detail::SpecificationsForRole>::available && specificationRoleCount > 0; template concept HasUniqueSpecificationForRole = detail::SpecificationsForRole>::available && specificationRoleCount == 1; template requires HasUniqueSpecificationForRole using SpecificationForRoleT = typename detail::UniqueModelType>::TypeValue; template struct SpecificationOperatorSignature; template struct SpecificationOperatorSignature> final { using GeneratedValues = typename detail::ConcatenateModelTypeLists< typename SpecificationContribution::GeneratedValues...>::Type; using GeneratedResiduals = typename detail::ConcatenateModelTypeLists< typename SpecificationContribution::GeneratedResiduals...>::Type; static constexpr std::size_t generatedValueArity = detail::ModelTypeListScalarArity::value; static constexpr std::size_t generatedResidualArity = detail::ModelTypeListScalarArity::value; static constexpr bool symbolicallySquare = generatedValueArity == generatedResidualArity; }; template [[nodiscard]] consteval SpecificationDescriptor specificationDescriptor() { using Contribution = SpecificationContribution; return { .name = SpecificationTraits::name, .role = SpecificationTraits::role, .key = SpecificationTraits::key, .generatedValueArity = detail::ModelTypeListScalarArity::value, .generatedResidualArity = detail::ModelTypeListScalarArity::value }; } namespace detail { template class SpecifiedModel; template class SpecifiedModel> final { private: struct CanonicalArgumentsTag final { }; /* * Capture the user-spelled pack once, then move each exact type * into its canonical slot. Reconstructing an argument tuple in * the pack expansion would forward every argument once per * specification and silently consume move-sensitive physics * objects multiple times. */ template explicit SpecifiedModel( std::tuple &&arguments, CanonicalArgumentsTag ) : m_specifications(std::get(std::move(arguments))...) { } public: using SpecificationTypes = SpecificationSetStorage; using OperatorSignature = SpecificationOperatorSignature; static constexpr bool symbolicallySquare = OperatorSignature::symbolicallySquare; // A declaration can be complete before every physics name has a // backend block mapping. Keep this deliberately separate from // operators::StellarEquilibriumSystemCompilable. static constexpr bool hasCompleteEquilibriumDeclaration = symbolicallySquare && (SpecificationContribution::hasDeclarativeDefinition && ...); template requires ArgumentsMatchCanonicalSpecifications< SpecificationTypes, Arguments...>::value && std::constructible_from< std::tuple...>, Arguments...> && (std::constructible_from< Specifications, Specifications &&> && ...) explicit SpecifiedModel(Arguments &&...arguments) : SpecifiedModel( std::tuple...>{std::forward(arguments)...}, CanonicalArgumentsTag{} ) { } template requires isOneOf< Specification, Specifications...> [[nodiscard]] const Specification &specification() const noexcept { return std::get(m_specifications); } template static constexpr bool containsSpecification = isOneOf; [[nodiscard]] static constexpr std::span runtimeSpecificationDescriptors() noexcept { return runtimeDescriptors; } private: inline static constexpr std::array runtimeDescriptors = [] { std::array descriptors{}; std::size_t index = 0; ((descriptors[index] = {.specification = specificationDescriptor(), .canonicalIndex = index, .hasDeclarativeDefinition = SpecificationContribution::hasDeclarativeDefinition}, ++index), ...); return descriptors; }(); std::tuple m_specifications; }; } // namespace detail template requires ValidModelSpecificationPack && SpecificationOperatorSignature>::symbolicallySquare using Model = detail::SpecifiedModel>; template concept SpecifiedModelType = requires { typename std::remove_cvref_t::SpecificationTypes; typename std::remove_cvref_t::OperatorSignature; requires std::remove_cvref_t::symbolicallySquare; { std::remove_cvref_t::hasCompleteEquilibriumDeclaration } -> std::convertible_to; { std::remove_cvref_t::runtimeSpecificationDescriptors() } -> std::same_as>; }; static_assert(ModelSpecification); static_assert(ModelSpecification); static_assert(ModelSpecification); static_assert(ModelSpecification); static_assert(ModelSpecification); static_assert(ResolvedModelSpecification); static_assert(ResolvedModelSpecification); static_assert(ResolvedModelSpecification); static_assert(ResolvedModelSpecification); static_assert(ResolvedModelSpecification); static_assert(CompleteGeneratedScalarDimensionsFor); static_assert(CompleteGeneratedScalarDimensionsFor); static_assert(CompleteGeneratedScalarDimensionsFor); } // namespace mean_field::models /* * Astronomer-facing declaration vocabulary. These aliases deliberately * package the backend model lists, normalization topology, and manifest pair * without duplicating any compiler logic. Advanced code may still use the * models:: spellings directly; both paths produce exactly the same types. */ export namespace mean_field::stellar { namespace state { using Density = models::stellar::state::Density; using SurfaceShape = models::stellar::state::SurfaceShape; using GravityGradient = models::stellar::state::GravityGradient; using GravitationalPotential = models::stellar::state::GravitationalPotential; using SpecificEnthalpy = models::stellar::state::SpecificEnthalpy; using OwnGeneratedCoordinate = models::stellar::state::OwnGeneratedCoordinate; template using GeneratedCoordinateOf = models::stellar::state::GeneratedCoordinateOf; } // namespace state namespace equation { using GravityGradientDefinition = models::stellar::equation::GravityGradientDefinition; using PoissonEquation = models::stellar::equation::PoissonEquation; using DensityClosure = models::stellar::equation::DensityClosure; using SurfaceShapeBalance = models::stellar::equation::SurfaceShapeBalance; using HydrostaticBalance = models::stellar::equation::HydrostaticBalance; using OwnConstraint = models::stellar::equation::OwnConstraint; template using ConstraintOf = models::stellar::equation::ConstraintOf; } // namespace equation template using Derivative = models::stellar::Derivative; template using Reads = models::DependsOn; template using Changes = models::Affects; using PhysicalScale = models::PhysicalScaleLaw; template < models::PhysicalScaleRepresentedQuantity TargetQuantity, models::PhysicalScaleRepresentedQuantity GeneratedCoordinateQuantity, models::PhysicalScaleRepresentedQuantity ConstraintResidualQuantity, models::FixedString ValueStableId, models::FixedString ValueSymbol, models::FixedString ResidualStableId, models::FixedString ResidualSymbol> using ScalarConstraint = models::DimensionalScalarConstraint< TargetQuantity, GeneratedCoordinateQuantity, ConstraintResidualQuantity, ValueStableId, ValueSymbol, ResidualStableId, ResidualSymbol>; template concept ScalarConstraintDescription = requires { typename std::remove_cvref_t::Normalization; typename std::remove_cvref_t::Manifest; typename std::remove_cvref_t::TargetQuantity; typename std::remove_cvref_t::GeneratedCoordinateQuantity; typename std::remove_cvref_t::ConstraintResidualQuantity; typename std::remove_cvref_t::TargetValue; requires models::GeneratedNormalizationDefinition::Normalization>; requires models::GeneratedManifestDefinition::Manifest>; requires std::remove_cvref_t::Normalization::available; requires std::remove_cvref_t::Manifest::available; requires std::remove_cvref_t::dimensionallyTyped; }; } // namespace mean_field::stellar export namespace mean_field::integral { using FixedTotalMass = models::FixedTotalMass; using FixedAngularMomentum = models::FixedAngularMomentum; template < typename Specification, models::FixedString Name, typename DependsOn = models::ModelTypeList<>, typename Affects = models::ModelTypeList<>, typename Normalization = models::UnavailableGeneratedNormalization, typename Manifest = models::UnavailableGeneratedManifest> using FixedIntegralWithMultiplier = models::FixedIntegralWithMultiplier; template < typename Specification, models::FixedString Name, typename DependsOn = models::ModelTypeList<>, typename Affects = models::ModelTypeList<>, typename Normalization = models::UnavailableGeneratedNormalization, typename Manifest = models::UnavailableGeneratedManifest> using FixedWithMultiplier = FixedIntegralWithMultiplier; template < typename Specification, models::FixedString Name, typename DependsOn = models::ModelTypeList<>, typename Affects = models::ModelTypeList<>, typename Normalization = models::UnavailableGeneratedNormalization, typename Manifest = models::UnavailableGeneratedManifest> using FixedIntegralWithPhysicalCoordinate = models::FixedIntegralWithPhysicalCoordinate; template < typename Specification, models::FixedString Name, typename DependsOn = models::ModelTypeList<>, typename Affects = models::ModelTypeList<>, typename Normalization = models::UnavailableGeneratedNormalization, typename Manifest = models::UnavailableGeneratedManifest> using FixedWithPhysicalCoordinate = FixedIntegralWithPhysicalCoordinate; template < typename Specification, models::FixedString Name, typename Reads, typename Changes, stellar::ScalarConstraintDescription Description> using FixedScalarWithMultiplier = models::FixedIntegralWithMultiplier< Specification, Name, Reads, Changes, typename Description::Normalization, typename Description::Manifest>; template < typename Specification, models::FixedString Name, typename Reads, typename Changes, stellar::ScalarConstraintDescription Description> using FixedScalarWithPhysicalCoordinate = models::FixedIntegralWithPhysicalCoordinate< Specification, Name, Reads, Changes, typename Description::Normalization, typename Description::Manifest>; } // namespace mean_field::integral export namespace mean_field::constraint { using FixedCentralDensity = models::FixedCentralDensity; template < typename Specification, models::FixedString Name, typename DependsOn = models::ModelTypeList<>, typename Affects = models::ModelTypeList<>, typename Normalization = models::UnavailableGeneratedNormalization, typename Manifest = models::UnavailableGeneratedManifest> using PhaseCondition = models::PhaseCondition; template < typename Specification, models::FixedString Name, typename Reads, typename Changes, stellar::ScalarConstraintDescription Description> using ScalarPhaseCondition = models::PhaseCondition< Specification, Name, Reads, Changes, typename Description::Normalization, typename Description::Manifest>; } // namespace mean_field::constraint export namespace mean_field::eos { template using ConstitutiveLaw = models::ConstitutiveLaw; } export namespace mean_field::surface { template using BoundaryCondition = models::BoundaryCondition; }