module; #include #include #include #include #include #include #include #include #include #include #include #include export module mean_field:operators.prepared_variadic_stellar_equilibrium; export import :operators.prepared_angular_momentum; export import :operators.prepared_central_density; export import :operators.prepared_stellar_equilibrium; export import :operators.stellar_equilibrium_compiler; /* * A physics-authored residual or derivative provider must make one of two * explicit statements for every row/edge inferred from Reads/Changes: * * - return the token produced by row.add(...); or * - return structuralZero when the declared edge is identically zero. * * The outer runtime, rather than the extension, enumerates the compiler's * complete incidence set. These tiny result types let that enumeration * distinguish an intentional mathematical zero from an accidentally empty * hook without exposing any backend block machinery to a physics author. */ export namespace mean_field::stellar { struct ContributionAdded final { }; struct StructuralZero final { }; inline constexpr StructuralZero structuralZero{}; inline constexpr StructuralZero zeroDerivative{}; template concept ContributionResult = std::same_as, ContributionAdded> || std::same_as, StructuralZero>; } // namespace mean_field::stellar export namespace mean_field::operators { /** * Capability boundary for the coupled finite-element physics core. * Describing an EOS is intentionally easier than implementing its * finite-element runtime core. Surface equations are compiled and * prepared by their own specification contribution, so this backend is * selected solely by the constitutive law. */ template struct StellarEquilibriumCoreRuntime { static constexpr bool registered = false; }; template <> struct StellarEquilibriumCoreRuntime { static constexpr bool registered = true; using CoreType = PreparedStellarEquilibriumOperator; [[nodiscard]] static std::unique_ptr Make( fem::FEM &finiteElements, const mapping::DomainMapper &domainMapper, const eos::Polytrope &equationOfState, const models::CompiledFixedMass &fixedMass, PressureSurfaceConstraintView surfaceConstraint, deformation::PreparedDomainDeformationRuntime domainDeformation ) { return std::make_unique( finiteElements, domainMapper, equationOfState, fixedMass, surfaceConstraint, std::move(domainDeformation) ); } [[nodiscard]] static int SurfaceEquationCount(const CoreType &core) noexcept { return static_cast(core.GetSurfaceConstraintOperator().GetSurfaceRows().size()); } }; /** * Minimal common protocol consumed by the variadic outer root. * * EOS backends may use different concrete core types. They only need to * implement this numerical protocol and expose that type as * StellarEquilibriumCoreRuntime::CoreType. Specification runtimes * are audited separately against the selected concrete core, so a * constraint that needs additional physical facilities is rejected at its * own compile-time boundary. */ template concept PreparedStellarEquilibriumPhysicalCore = std::derived_from, mfem::Operator> && requires( std::remove_cvref_t &core, const std::remove_cvref_t &constantCore, const mfem::Vector &state, mfem::Vector &residual, const StellarEquilibriumDependencies &dependencies, const physics::RigidRotation &rotation ) { { constantCore.GetLayout() } -> std::same_as; { core.Prepare(state, dependencies, rotation) } -> std::same_as; { constantCore.BuildResidual(residual) } -> std::same_as; { constantCore.IsPrepared() } -> std::convertible_to; { constantCore.GetFixedMassReport() } -> std::same_as; { constantCore.GetDependencies() } -> std::same_as; { constantCore.GetGeneratedDisplacementDependency() } -> std::same_as; { constantCore.GetSurfaceConstraintOperator() } -> std::same_as; }; namespace detail { template struct BackendSpecificationListTraits final { static constexpr bool valid = false; template static constexpr bool contains = false; }; template struct BackendSpecificationListTraits< models::ModelTypeList> final { static constexpr bool valid = (models::ModelSpecification && ...) && utils::blocks::types_are_unique_v< utils::blocks::type_list>; template static constexpr bool contains = (std::same_as, Specifications> || ...); }; template struct CoreRuntimeInterfaceAudit { using CoreType = void; static constexpr bool complete = false; }; template struct CoreRuntimeInterfaceAudit< Model, std::void_t< typename StellarEquilibriumCoreRuntime< typename std::remove_cvref_t::EquationOfStateType>::CoreType, typename StellarEquilibriumCoreRuntime< typename std::remove_cvref_t::EquationOfStateType>:: CoreType::BackendSpecifications, std::bool_constant( StellarEquilibriumCoreRuntime< typename std::remove_cvref_t::EquationOfStateType>::registered)>>> { private: using ModelType = std::remove_cvref_t; using EquationOfState = typename ModelType::EquationOfStateType; using Runtime = StellarEquilibriumCoreRuntime; public: using CoreType = typename Runtime::CoreType; static constexpr bool complete = BackendSpecificationListTraits< typename CoreType::BackendSpecifications>::valid && PreparedStellarEquilibriumPhysicalCore && requires( fem::FEM &finiteElements, const mapping::DomainMapper &domainMapper, const EquationOfState &equationOfState, const models::CompiledFixedMass &fixedMass, PressureSurfaceConstraintView surfaceConstraint, deformation::PreparedDomainDeformationRuntime domainDeformation, const CoreType &core ) { requires Runtime::registered; { Runtime::Make( finiteElements, domainMapper, equationOfState, fixedMass, surfaceConstraint, std::move(domainDeformation) ) } -> std::same_as>; { Runtime::SurfaceEquationCount(core) } -> std::convertible_to; }; }; } // namespace detail /** * Structural contract for the privileged specification list owned by an * EOS/core backend. Ordinary EOS, surface, and constraint authors do not * use this facility; their nested EquilibriumPhysics package remains the * restricted, astronomy-facing extension path. */ template concept StellarEquilibriumBackendSpecificationList = detail::BackendSpecificationListTraits< std::remove_cvref_t>::valid; /** * Backend runtime extension point for one physical model specification. * * The prepared stellar root is assembled by folding this trait over the * model's canonical specification list. A new specification therefore * contributes one prepared slot; no specialization for a *combination* of * specifications is ever required. New physics-facing specifications * should prefer their nested EquilibriumPhysics package below; explicit * specializations remain the library/backend registry mechanism, but are * selected only when the concrete core owner lists that exact * specification in CoreType::BackendSpecifications. */ template struct StellarEquilibriumRuntimeContribution { static constexpr bool registered = false; static constexpr std::size_t rotationProviders = 0; }; template < template typename PreparedImplementation, std::size_t RotationProviderCount = 0> struct PreparedStellarEquilibriumContribution { static constexpr bool registered = true; static constexpr std::size_t rotationProviders = RotationProviderCount; template using Prepared = PreparedImplementation; }; /* Physics-facing declaration for a specification's residual/Jacobian * runtime. A constraint may expose * * using EquilibriumPhysics = * operators::SpecificationEquilibriumPhysics; * * inside its own class. Unlike an explicit * StellarEquilibriumRuntimeContribution specialization, this declaration * is adapted through restricted physics-facing views. The implementation * receives its exact specification at construction and never receives the * full model, FEM backend, domain mapper, dependency set, or physical core. * Explicit registry specializations remain a candidate backend extension * point for built-in physics which must coordinate core internals. The * selected concrete core's non-extendable BackendSpecifications member is what * grants that candidate privileged access. */ template < template typename PreparedImplementation, std::size_t RotationProviderCount = 0> struct SpecificationEquilibriumPhysics final { static_assert( RotationProviderCount <= 1, "One specification runtime can provide at most one rigid-rotation control." ); static constexpr bool registered = true; static constexpr std::size_t rotationProviders = RotationProviderCount; template using Physics = PreparedImplementation; }; namespace detail { template struct BindLocalSpecificationEquilibriumPhysics final { template using Physics = LocalPhysics; }; } // namespace detail /** * Convenience spelling for ordinary specification-local physics. * * Most constraint implementations depend only on their exact * specification and the restricted block views supplied by the adapter; * they do not need the complete Model type. This alias binds such a * concrete class into SpecificationEquilibriumPhysics without introducing * a second runtime or duplicating any adapter logic. */ template using LocalSpecificationEquilibriumPhysics = SpecificationEquilibriumPhysics< detail::BindLocalSpecificationEquilibriumPhysics::template Physics>; namespace detail { template concept HasNestedStellarEquilibriumPhysics = requires { typename std::remove_cvref_t::EquilibriumPhysics; }; template struct BackendRuntimeContributionAuthorization : std::false_type { }; template < models::ModelSpecification Specification, model::StellarModelType Model> struct BackendRuntimeContributionAuthorization< Specification, Model, std::void_t::EquationOfStateType>:: CoreType::BackendSpecifications>> final : std::bool_constant< CoreRuntimeInterfaceAudit>::complete && std::remove_cvref_t::template containsSpecification< std::remove_cvref_t> && BackendSpecificationListTraits::EquationOfStateType>:: CoreType::BackendSpecifications>::template contains< std::remove_cvref_t>> { }; template < typename Specification, typename Model, typename = void> struct BackendRuntimeContributionCandidate final { static constexpr bool available = false; static constexpr bool registered = false; static constexpr std::size_t rotationProviders = 0; }; template < models::ModelSpecification Specification, model::StellarModelType Model> struct BackendRuntimeContributionCandidate< Specification, Model, std::void_t< std::enable_if_t::value>, typename StellarEquilibriumRuntimeContribution< Specification>::template Prepared, std::bool_constant( StellarEquilibriumRuntimeContribution< Specification>::registered)>, std::integral_constant< std::size_t, static_cast( StellarEquilibriumRuntimeContribution< Specification>::rotationProviders)>>> final { using Contribution = StellarEquilibriumRuntimeContribution; using Prepared = typename Contribution::template Prepared; static constexpr bool available = true; static constexpr bool registered = Contribution::registered; static constexpr std::size_t rotationProviders = Contribution::rotationProviders; }; template < models::ModelSpecification Specification, model::StellarModelType Model, typename Physics> class PhysicsFacingSpecificationRuntime; template < models::ModelSpecification Specification, model::StellarModelType Model, bool HasNestedPhysics = HasNestedStellarEquilibriumPhysics, typename = void> struct RuntimeContributionSelection { static constexpr bool available = false; static constexpr bool ambiguous = false; static constexpr std::size_t rotationProviders = 0; }; /* A core-authorized explicit registry specialization is trusted * backend code. Its established protocol deliberately retains direct * access to the FEM, mapper, physical core, model, and dependency * stamps. A specialization alone is ignored, so it cannot confer that * privilege on an external specification paired with an existing * core. */ template struct RuntimeContributionSelection< Specification, Model, false, std::void_t< typename BackendRuntimeContributionCandidate< Specification, Model>::Prepared>> { using Candidate = BackendRuntimeContributionCandidate; using Contribution = typename Candidate::Contribution; using Prepared = typename Candidate::Prepared; static constexpr bool available = Candidate::available; static constexpr bool registered = Candidate::registered; static constexpr bool ambiguous = false; static constexpr std::size_t rotationProviders = Candidate::rotationProviders; }; /* A nested package is the safe physics-author path. The adapter owns * all interaction with backend objects and forwards only restricted * views to the implementation. Malformed packages remain detection * safe through this partial specialization. */ template struct RuntimeContributionSelection< Specification, Model, true, std::void_t< typename Specification::EquilibriumPhysics, std::bool_constant( Specification::EquilibriumPhysics::registered)>, std::integral_constant< std::size_t, static_cast( Specification::EquilibriumPhysics::rotationProviders)>, typename Specification::EquilibriumPhysics::template Physics>> { using Contribution = typename Specification::EquilibriumPhysics; using Physics = typename Contribution::template Physics; using Prepared = PhysicsFacingSpecificationRuntime; static constexpr bool available = true; static constexpr bool registered = Contribution::registered; static constexpr bool ambiguous = BackendRuntimeContributionCandidate< Specification, Model>::registered; static constexpr std::size_t rotationProviders = Contribution::rotationProviders; }; template using PreparedRuntimeContribution = typename RuntimeContributionSelection::Prepared; } // namespace detail /** * Whether the selected concrete core explicitly permits one specification * to use the privileged aggregate runtime registry. This is intentionally * false for an otherwise valid external specification paired with the * built-in core; such a specification must use its restricted nested * EquilibriumPhysics package. */ template inline constexpr bool stellarEquilibriumBackendRuntimeAuthorized = detail::BackendRuntimeContributionAuthorization< std::remove_cvref_t, std::remove_cvref_t>::value; struct EmptySpecificationPreparationReport final { [[nodiscard]] constexpr bool DidAnyWork() const noexcept { return false; } }; namespace detail { struct StellarEquilibriumControlContext final { StellarEquilibriumDependencies dependencies; std::optional rotation; std::size_t rotationProviderCount{0}; bool generatedPhysicalControl{false}; }; /* * Additional facilities used specifically by FixedAngularMomentum. * This is deliberately a constraint-local protocol: an EOS core can * support the base root without implementing these operations, and is * rejected only when this physical constraint is selected. */ template concept FixedAngularMomentumPhysicalCore = PreparedStellarEquilibriumPhysicalCore && requires(const std::remove_cvref_t &core) { { core.GetGravityContext() } -> std::same_as; { core.GetDomainDeformation() } -> std::same_as; { core.GetBarotropicClosureOperator() } -> std::same_as; { core.GetHydrostaticOperator() } -> std::same_as; { core.GetSurfaceConstraintOperator() } -> std::same_as; { core.GetDisplacementOperator() } -> std::same_as; { core.GetSurfaceDeformationParameters() } -> std::same_as; { core.GetGeneratedDisplacementDependency() } -> std::same_as; }; template class EmbeddedSpecificationRuntime final { public: using Report = EmptySpecificationPreparationReport; template EmbeddedSpecificationRuntime( fem::FEM &, const mapping::DomainMapper &, PhysicalCore &, const Model & ) noexcept { } template void ReadPhysicalControls(const StateView &, StellarEquilibriumControlContext &) noexcept { } template [[nodiscard]] Report PrepareAfterPhysical( const StateView &, const StellarEquilibriumDependencies &, const PhysicalCore & ) noexcept { return {}; } template void AddResidual(const ResidualView &) const noexcept { } template < typename DirectionView, typename ActionView, PreparedStellarEquilibriumPhysicalCore PhysicalCore> void AddJacobianAction( const DirectionView &, const ActionView &, const PhysicalCore & ) const noexcept { } [[nodiscard]] constexpr bool IsPrepared() const noexcept { return true; } }; struct FixedAngularMomentumPreparationReport final { PreparedAngularMomentumReport constraint; bool generatedRotation{false}; [[nodiscard]] bool DidAnyWork() const noexcept { return constraint.DidAnyWork() || generatedRotation; } }; template class FixedAngularMomentumRuntime final { public: using Report = FixedAngularMomentumPreparationReport; template FixedAngularMomentumRuntime( fem::FEM &finiteElements, const mapping::DomainMapper &domainMapper, PhysicalCore &physical, const Model &model ) : m_constraint( finiteElements, domainMapper, physical.GetGravityContext(), models::compileConstraint( model.template specification() ) ), m_volumeDisplacementDirection(physical.GetDomainDeformation().volumeDisplacementSize()), m_rotationalAngularVelocityAction(physical.GetDomainDeformation().volumeDisplacementSize()), m_surfaceAngularVelocityAction(physical.GetDomainDeformation().parameterCount()), m_hydrostaticAngularVelocityAction(physical.GetBarotropicClosureOperator().GetEnthalpySize()), m_zeroEnthalpy(physical.GetBarotropicClosureOperator().GetEnthalpySize()) { m_generatedRotationDependency.identity = static_cast(reinterpret_cast(this)); m_zeroEnthalpy = 0.0; } template void ReadPhysicalControls( const StateView &state, StellarEquilibriumControlContext &context ) { const auto angularVelocity = state.block(utils::blocks::fixed_angular_momentum_constraint.angular_velocity_term); MFEM_VERIFY( angularVelocity.Size() == 1 && std::isfinite(angularVelocity(0)), "FixedAngularMomentum must generate one finite angular-velocity coordinate." ); m_generatedRotationChanged = !m_isPrepared || angularVelocity(0) != m_angularVelocity; if (m_generatedRotationChanged) { m_angularVelocity = angularVelocity(0); ++m_generatedRotationDependency.revision; } context.dependencies.rotation = m_generatedRotationDependency; context.rotation = m_constraint.GetCompiledConstraint().makeRotation(m_angularVelocity); ++context.rotationProviderCount; context.generatedPhysicalControl = true; } template [[nodiscard]] Report PrepareAfterPhysical( const StateView &, const StellarEquilibriumDependencies &dependencies, const PhysicalCore &physical ) { const StellarEquilibriumDependencyStamp &displacement = physical.GetGeneratedDisplacementDependency(); const auto constraintReport = m_constraint.Prepare( m_angularVelocity, {.discretization = { .identity = dependencies.discretization.identity, .revision = dependencies.discretization.revision }, .density = { .identity = dependencies.density.identity, .revision = dependencies.density.revision }, .displacement = { .identity = displacement.identity, .revision = displacement.revision }, .rotation = { .identity = dependencies.rotation.identity, .revision = dependencies.rotation.revision }} ); m_isPrepared = true; return {.constraint = constraintReport, .generatedRotation = m_generatedRotationChanged}; } template void AddResidual(const ResidualView &residual) const { mfem::Vector constraintResidual; m_constraint.BuildResidual(constraintResidual); residual.add( utils::blocks::fixed_angular_momentum_constraint.angular_velocity_term, constraintResidual ); } template < typename DirectionView, typename ActionView, FixedAngularMomentumPhysicalCore PhysicalCore> void AddJacobianAction( const DirectionView &direction, const ActionView &action, const PhysicalCore &physical ) const { const auto densityDirection = direction.block(utils::blocks::density_field.mass_term); const auto surfaceDirection = direction.block(utils::blocks::surface_deformation_field.parameters_term); const auto angularVelocityDirection = direction.block(utils::blocks::fixed_angular_momentum_constraint.angular_velocity_term); MFEM_VERIFY( angularVelocityDirection.Size() == 1 && std::isfinite(angularVelocityDirection(0)), "The angular-velocity direction must be finite." ); m_angularMomentumAction.SetSize(1); m_angularMomentumAction = 0.0; physical.GetDomainDeformation().applyJacobian( physical.GetSurfaceDeformationParameters(), surfaceDirection, m_volumeDisplacementDirection ); m_constraint.ApplyCompleteJacobianAction( densityDirection, m_volumeDisplacementDirection, angularVelocityDirection(0), m_angularMomentumAction ); action.add( utils::blocks::fixed_angular_momentum_constraint.angular_velocity_term, m_angularMomentumAction ); if (m_angularVelocity == 0.0 || angularVelocityDirection(0) == 0.0) { return; } const double fractionalVariation = angularVelocityDirection(0) / m_angularVelocity; physical.GetHydrostaticOperator().ApplyRotationAmplitudeJacobianAction( fractionalVariation, m_hydrostaticAngularVelocityAction ); m_zeroEnthalpy = 0.0; physical.GetSurfaceConstraintOperator().ApplyJacobianRows( m_zeroEnthalpy, m_hydrostaticAngularVelocityAction ); action.add( utils::blocks::enthalpy_field.specific_term, m_hydrostaticAngularVelocityAction ); physical.GetDisplacementOperator().GetRotationalOperator().BuildResidual( m_rotationalAngularVelocityAction ); m_rotationalAngularVelocityAction *= 2.0 * fractionalVariation; physical.GetDomainDeformation().applyJacobianTranspose( physical.GetSurfaceDeformationParameters(), m_rotationalAngularVelocityAction, m_surfaceAngularVelocityAction ); action.add( utils::blocks::surface_deformation_field.shape_equilibrium_term, m_surfaceAngularVelocityAction ); } [[nodiscard]] bool IsPrepared() const noexcept { return m_isPrepared && m_constraint.IsPrepared(); } [[nodiscard]] const PreparedAngularMomentumOperator &constraint() const noexcept { return m_constraint; } private: PreparedAngularMomentumOperator m_constraint; StellarEquilibriumDependencyStamp m_generatedRotationDependency; double m_angularVelocity{0.0}; bool m_generatedRotationChanged{false}; bool m_isPrepared{false}; mutable mfem::Vector m_volumeDisplacementDirection; mutable mfem::Vector m_rotationalAngularVelocityAction; mutable mfem::Vector m_surfaceAngularVelocityAction; mutable mfem::Vector m_hydrostaticAngularVelocityAction; mutable mfem::Vector m_angularMomentumAction; mutable mfem::Vector m_zeroEnthalpy; }; struct FixedCentralDensityPreparationReport final { PreparedCentralDensityReport constraint; [[nodiscard]] bool DidAnyWork() const noexcept { return constraint.DidAnyWork(); } }; template class FixedCentralDensityRuntime final { public: using Report = FixedCentralDensityPreparationReport; template FixedCentralDensityRuntime( fem::FEM &finiteElements, const mapping::DomainMapper &, PhysicalCore &, const Model &model ) : m_compiled(models::compileConstraint( model.template specification(), model.equationOfState() )), m_constraint(MakeCenterDofMap(finiteElements), finiteElements.mesh->GetComm()), m_enthalpyResidual(m_constraint.GetCenterDof().field_size()), m_phaseResidual(1), m_enthalpyAction(m_constraint.GetCenterDof().field_size()), m_phaseAction(1) { } template void ReadPhysicalControls(const StateView &, StellarEquilibriumControlContext &) noexcept { } template [[nodiscard]] Report PrepareAfterPhysical( const StateView &state, const StellarEquilibriumDependencies &dependencies, const PhysicalCore & ) { const auto enthalpy = state.block(utils::blocks::enthalpy_field.specific_term); const auto border = state.block(utils::blocks::fixed_central_density_phase.central_value_term); MFEM_VERIFY( border.Size() == 1 && std::isfinite(border(0)), "The central-density phase border must be finite." ); auto report = m_constraint.Prepare( m_compiled, enthalpy, border(0), {.enthalpy = { .identity = dependencies.enthalpy.identity, .revision = dependencies.enthalpy.revision }} ); m_isPrepared = true; return {.constraint = report}; } template void AddResidual(const ResidualView &residual) const { m_enthalpyResidual = 0.0; m_phaseResidual = 0.0; m_constraint.AddResidual(m_enthalpyResidual, m_phaseResidual); residual.add(utils::blocks::enthalpy_field.specific_term, m_enthalpyResidual); residual.add( utils::blocks::fixed_central_density_phase.central_value_term, m_phaseResidual ); } template < typename DirectionView, typename ActionView, PreparedStellarEquilibriumPhysicalCore PhysicalCore> void AddJacobianAction( const DirectionView &direction, const ActionView &action, const PhysicalCore & ) const { const auto enthalpyDirection = direction.block(utils::blocks::enthalpy_field.specific_term); const auto borderDirection = direction.block(utils::blocks::fixed_central_density_phase.central_value_term); MFEM_VERIFY( borderDirection.Size() == 1 && std::isfinite(borderDirection(0)), "The central-density phase direction must be finite." ); m_enthalpyAction = 0.0; m_phaseAction = 0.0; m_constraint.ApplyJacobian( {.enthalpyVariation = enthalpyDirection, .borderVariation = borderDirection(0)}, {.enthalpyAction = m_enthalpyAction, .phaseAction = m_phaseAction} ); action.add(utils::blocks::enthalpy_field.specific_term, m_enthalpyAction); action.add( utils::blocks::fixed_central_density_phase.central_value_term, m_phaseAction ); } [[nodiscard]] bool IsPrepared() const noexcept { return m_isPrepared && m_constraint.IsPrepared(); } [[nodiscard]] const PreparedCentralDensityConstraint &constraint() const noexcept { return m_constraint; } [[nodiscard]] const models::CompiledFixedCentralDensity &compiled() const noexcept { return m_compiled; } private: [[nodiscard]] static field::FieldPointDofMap MakeCenterDofMap(const fem::FEM &finiteElements) { using DomainSchema = utils::domain::CoreEnvelopeVacuumDomainSchema; MFEM_VERIFY( finiteElements.mesh != nullptr && finiteElements.enthalpyFes != nullptr, "The central-density phase requires a mesh and enthalpy finite-element space." ); const field::FieldDofMap enthalpyMap = field::make_field_dof_map(*finiteElements.enthalpyFes); mfem::Vector origin(finiteElements.mesh->SpaceDimension()); origin = 0.0; return field::make_field_point_dof_map( *finiteElements.enthalpyFes, enthalpyMap, origin, 1.0e-12 ); } models::CompiledFixedCentralDensity m_compiled; PreparedCentralDensityConstraint m_constraint; mutable mfem::Vector m_enthalpyResidual; mutable mfem::Vector m_phaseResidual; mutable mfem::Vector m_enthalpyAction; mutable mfem::Vector m_phaseAction; bool m_isPrepared{false}; }; } // namespace detail template <> struct StellarEquilibriumRuntimeContribution { static constexpr bool registered = true; static constexpr std::size_t rotationProviders = 0; template using Prepared = detail::EmbeddedSpecificationRuntime; }; template <> struct StellarEquilibriumRuntimeContribution { static constexpr bool registered = true; static constexpr std::size_t rotationProviders = 0; template using Prepared = detail::EmbeddedSpecificationRuntime; }; template <> struct StellarEquilibriumRuntimeContribution { static constexpr bool registered = true; static constexpr std::size_t rotationProviders = 0; template using Prepared = detail::EmbeddedSpecificationRuntime; }; template <> struct StellarEquilibriumRuntimeContribution : PreparedStellarEquilibriumContribution { }; template <> struct StellarEquilibriumRuntimeContribution : PreparedStellarEquilibriumContribution { }; namespace detail { template struct BlockListIsSubset : std::false_type { }; template struct BlockListIsSubset, Superset> : std::bool_constant< (utils::blocks::contains_type_v && ...)> { }; template struct SinglePhysicsBlock; template struct SinglePhysicsBlock> final { using Type = Block; }; template struct PhysicsFacingValueTerm final { using value = ValueBlock; }; template struct PhysicsFacingResidualTerm final { using residual = ResidualBlock; }; template < typename Form, typename AllowedValueBlocks, models::ModelSpecification Specification> class RestrictedSpecificationStateView final { public: explicit RestrictedSpecificationStateView(const RootStateView
&state) noexcept : m_state(state) { } template requires BlockListIsSubset< NarrowedValueBlocks, AllowedValueBlocks>::value [[nodiscard]] auto narrow() const noexcept { return RestrictedSpecificationStateView< Form, NarrowedValueBlocks, Specification>{m_state}; } template requires requires { typename std::remove_cvref_t::value; } && utils::blocks::contains_type_v< typename std::remove_cvref_t::value, AllowedValueBlocks> [[nodiscard]] auto block(const Term &term) const { return m_state.block(term); } /* Every compiler-enumerated derivative receives a view containing * exactly one source. value() is the backend-agnostic spelling * for advanced physics vocabulary that does not yet have a named * convenience accessor. */ [[nodiscard]] auto value() const requires(AllowedValueBlocks::size == 1) { using ValueBlock = typename SinglePhysicsBlock< AllowedValueBlocks>::Type; return m_state.block(PhysicsFacingValueTerm{}); } [[nodiscard]] auto density() const requires StellarDependencyBlock< Specification, models::stellar::state::Density>::mapped && utils::blocks::contains_type_v< typename StellarDependencyBlock< Specification, models::stellar::state::Density>::Type, AllowedValueBlocks> { return physicsBlock(); } [[nodiscard]] auto surfaceShape() const requires StellarDependencyBlock< Specification, models::stellar::state::SurfaceShape>::mapped && utils::blocks::contains_type_v< typename StellarDependencyBlock< Specification, models::stellar::state::SurfaceShape>::Type, AllowedValueBlocks> { return physicsBlock(); } [[nodiscard]] auto gravityGradient() const requires StellarDependencyBlock< Specification, models::stellar::state::GravityGradient>::mapped && utils::blocks::contains_type_v< typename StellarDependencyBlock< Specification, models::stellar::state::GravityGradient>::Type, AllowedValueBlocks> { return physicsBlock(); } [[nodiscard]] auto gravitationalPotential() const requires StellarDependencyBlock< Specification, models::stellar::state::GravitationalPotential>::mapped && utils::blocks::contains_type_v< typename StellarDependencyBlock< Specification, models::stellar::state::GravitationalPotential>::Type, AllowedValueBlocks> { return physicsBlock< models::stellar::state::GravitationalPotential>(); } [[nodiscard]] auto specificEnthalpy() const requires StellarDependencyBlock< Specification, models::stellar::state::SpecificEnthalpy>::mapped && utils::blocks::contains_type_v< typename StellarDependencyBlock< Specification, models::stellar::state::SpecificEnthalpy>::Type, AllowedValueBlocks> { return physicsBlock(); } [[nodiscard]] auto generatedCoordinate() const requires StellarDependencyBlock< Specification, models::stellar::state::OwnGeneratedCoordinate>::mapped && utils::blocks::contains_type_v< typename StellarDependencyBlock< Specification, models::stellar::state::OwnGeneratedCoordinate>::Type, AllowedValueBlocks> { return physicsBlock< models::stellar::state::OwnGeneratedCoordinate>(); } template [[nodiscard]] auto generatedCoordinate() const requires StellarDependencyBlock< Specification, models::stellar::state::GeneratedCoordinateOf>::mapped && utils::blocks::contains_type_v< typename StellarDependencyBlock< Specification, models::stellar::state::GeneratedCoordinateOf>::Type, AllowedValueBlocks> { return physicsBlock< models::stellar::state::GeneratedCoordinateOf>(); } private: template [[nodiscard]] auto physicsBlock() const { using ValueBlock = typename StellarDependencyBlock< Specification, PhysicsQuantity>::Type; return m_state.block(PhysicsFacingValueTerm{}); } RootStateView m_state; }; template class RestrictedSpecificationResidualView final { public: RestrictedSpecificationResidualView( const ResidualView &residual, const PreparedPressureSurfaceConstraint &surfaceConstraint ) noexcept : m_residual(residual), m_surfaceConstraint(std::addressof(surfaceConstraint)) { } template requires BlockListIsSubset< AllowedResidualBlocks, OtherAllowedResidualBlocks>::value explicit RestrictedSpecificationResidualView( const RestrictedSpecificationResidualView< Form, OtherAllowedResidualBlocks> &residual ) noexcept : m_residual(residual.m_residual), m_surfaceConstraint(residual.m_surfaceConstraint) { } /* Physics-facing assembly is intentionally additive-only. A * specification cannot erase the physical core or an earlier * contribution. Hydrostatic additions are also projected away * from rows owned by the surface condition, so extension authors * do not need to understand the backend row-replacement policy. */ template requires requires { typename std::remove_cvref_t::residual; } && utils::blocks::contains_type_v< typename std::remove_cvref_t::residual, AllowedResidualBlocks> void add(const Term &term, const double contribution) const { mfem::Vector destination = m_residual.block(term); destination += contribution; RestoreReplacedRows::residual>( destination, contribution ); destination.SyncAliasMemory(m_residual.vector()); } template requires requires { typename std::remove_cvref_t::residual; } && utils::blocks::contains_type_v< typename std::remove_cvref_t::residual, AllowedResidualBlocks> void add(const Term &term, const mfem::Vector &contribution) const { mfem::Vector destination = m_residual.block(term); if (destination.Size() != contribution.Size()) { throw std::invalid_argument( "A specification runtime contribution has the wrong residual block size." ); } destination += contribution; RestoreReplacedRows::residual>( destination, contribution ); destination.SyncAliasMemory(m_residual.vector()); } template requires requires { typename std::remove_cvref_t::residual; } && utils::blocks::contains_type_v< typename std::remove_cvref_t::residual, AllowedResidualBlocks> void addEntry( const Term &term, const int index, const double contribution ) const { mfem::Vector destination = m_residual.block(term); if (index < 0 || index >= destination.Size()) { throw std::out_of_range( "A specification runtime contribution selected an invalid residual entry." ); } if (!IsReplacedRow::residual>(index)) { destination(index) += contribution; } destination.SyncAliasMemory(m_residual.vector()); } private: template friend class RestrictedSpecificationResidualView; template [[nodiscard]] bool IsReplacedRow(const int index) const noexcept { if constexpr (!std::same_as< ResidualBlock, utils::blocks::enthalpy::specific::residual>) { return false; } else { for (const int row : m_surfaceConstraint->GetSurfaceRows().reduced_dofs()) { if (row == index) { return true; } } return false; } } template void RestoreReplacedRows( mfem::Vector &destination, const double contribution ) const { if constexpr (std::same_as< ResidualBlock, utils::blocks::enthalpy::specific::residual>) { for (const int row : m_surfaceConstraint->GetSurfaceRows().reduced_dofs()) { destination(row) -= contribution; } } } template void RestoreReplacedRows( mfem::Vector &destination, const mfem::Vector &contribution ) const { if constexpr (std::same_as< ResidualBlock, utils::blocks::enthalpy::specific::residual>) { for (const int row : m_surfaceConstraint->GetSurfaceRows().reduced_dofs()) { destination(row) -= contribution(row); } } } ResidualView m_residual; const PreparedPressureSurfaceConstraint *m_surfaceConstraint; }; /* * Single-use row handed to one compiler-enumerated physics provider. * It deliberately has no row selector: the equation tag selected the * row before the extension was called. A provider therefore cannot * redirect a legal source into a different legal residual. Runtime * accounting additionally rejects double assembly or a contribution * token inconsistent with what the provider actually did. */ template class ExactSpecificationResidualRow final { public: using View = RestrictedSpecificationResidualView< Form, utils::blocks::type_list>; explicit ExactSpecificationResidualRow(const View &row) noexcept : m_row(row) { } ExactSpecificationResidualRow( const ExactSpecificationResidualRow & ) = delete; ExactSpecificationResidualRow &operator=( const ExactSpecificationResidualRow & ) = delete; ExactSpecificationResidualRow( ExactSpecificationResidualRow && ) = delete; ExactSpecificationResidualRow &operator=( ExactSpecificationResidualRow && ) = delete; [[nodiscard]] stellar::ContributionAdded add( const double contribution ) { RequireUnused(); m_row.add( PhysicsFacingResidualTerm{}, contribution ); m_addCount = 1; return {}; } [[nodiscard]] stellar::ContributionAdded add( const mfem::Vector &contribution ) { RequireUnused(); m_row.add( PhysicsFacingResidualTerm{}, contribution ); m_addCount = 1; return {}; } [[nodiscard]] stellar::ContributionAdded addEntry( const int index, const double contribution ) { RequireUnused(); m_row.addEntry( PhysicsFacingResidualTerm{}, index, contribution ); m_addCount = 1; return {}; } template void Verify(const Result &) const { if constexpr (std::same_as< std::remove_cvref_t, stellar::ContributionAdded>) { if (m_addCount != 1) { throw std::logic_error( "A stellar physics provider returned ContributionAdded without adding exactly once." ); } } else { if (m_addCount != 0) { throw std::logic_error( "A stellar physics provider returned StructuralZero after adding to its row." ); } } } private: void RequireUnused() const { if (m_addCount != 0) { throw std::logic_error( "A compiler-enumerated stellar residual/Jacobian edge may be assembled only once." ); } } View m_row; int m_addCount{0}; }; template struct PhysicsTagList; template struct PhysicsTagList> final { using Type = utils::blocks::type_list; }; template struct DerivativesOfEquation; template struct DerivativesOfEquation< Equation, utils::blocks::type_list> final { using Type = utils::blocks::type_list< models::stellar::Derivative...>; }; template struct CartesianPhysicsDerivatives; template struct CartesianPhysicsDerivatives< utils::blocks::type_list, States> final { using Type = ConcatenateBlockListsT< typename DerivativesOfEquation::Type...>; }; /* The topology consumed by physics-facing providers is generated * directly from the same Reads/Changes declaration used by the block * compiler. No implementation-owned provider list can get out of * sync with the model declaration. */ template struct SpecificationPhysicsTopology final { private: using Contribution = models::SpecificationContribution; public: using ReadStates = typename PhysicsTagList< typename Contribution::DependsOn>::Type; using ChangedEquations = typename PhysicsTagList< typename Contribution::Affects>::Type; using OwnGeneratedState = std::conditional_t< Contribution::generatedValueArity == 0, utils::blocks::type_list<>, utils::blocks::type_list< models::stellar::state::OwnGeneratedCoordinate>>; using OwnConstraintEquation = std::conditional_t< Contribution::generatedResidualArity == 0, utils::blocks::type_list<>, utils::blocks::type_list< models::stellar::equation::OwnConstraint>>; using ResidualEquations = UniqueConcatenateBlockListsT< OwnConstraintEquation, ChangedEquations>; using ChangedEquationInputs = UniqueConcatenateBlockListsT< ReadStates, OwnGeneratedState>; using ConstraintDerivatives = typename CartesianPhysicsDerivatives< OwnConstraintEquation, ReadStates>::Type; using ChangedEquationDerivatives = typename CartesianPhysicsDerivatives< ChangedEquations, ChangedEquationInputs>::Type; using Derivatives = UniqueConcatenateBlockListsT< ConstraintDerivatives, ChangedEquationDerivatives>; }; template < typename Specification, typename State, bool IsSpecification = models::ModelSpecification< std::remove_cvref_t>> struct SpecificationReadsState : std::false_type { }; template struct SpecificationReadsState : std::bool_constant>::ReadStates>> { }; } // namespace detail /** * Astronomy-facing access to the current core's physical-volume density * integral, * * M[rho] = integral_{Omega_star} rho dV. * * The context is available only to a specification which declares both * stellar::state::Density and stellar::state::SurfaceShape in Reads. That * is a mathematical requirement rather than an implementation detail: * the mapped physical-volume integral depends on both rho and the domain * geometry. Requiring both declarations prevents a residual from using * this service while omitting its geometry column from the inferred * Jacobian. The context is a small, copyable, non-owning service handle: * it exposes neither FEM objects nor the physical core, owns no backend * object, and uses no allocating type erasure. The selected core remains * responsible for quadrature, mapped physical volume, distributed * reduction, scratch storage, and exact directional actions. */ template concept DensityVolumeIntegralSpecification = detail::SpecificationReadsState< Specification, models::stellar::state::Density>::value && detail::SpecificationReadsState< Specification, models::stellar::state::SurfaceShape>::value; template class DensityVolumeIntegralContext final { public: [[nodiscard]] dimensions::MassValue integrateDensity( const mfem::Vector &density ) const { return dimensions::MassValue{m_densityAction(m_core, density)}; } [[nodiscard]] dimensions::MassValue linearizeDensityIntegral( const mfem::Vector &densityDirection ) const { return dimensions::MassValue{ m_densityAction(m_core, densityDirection)}; } [[nodiscard]] dimensions::MassValue linearizeSurfaceShapeIntegral( const mfem::Vector &surfaceShapeDirection ) const { return dimensions::MassValue{ m_surfaceShapeAction(m_core, surfaceShapeDirection)}; } private: template < models::ModelSpecification OtherSpecification, model::StellarModelType OtherModel, typename OtherPhysics> friend class detail::PhysicsFacingSpecificationRuntime; using Action = double (*)(const void *, const mfem::Vector &); public: /* Public construction is intentionally backend-facing: it accepts a * core which already owns the prepared integration service, but the * resulting physics-facing handle has no route back to that core. This * also permits direct distributed contract tests of the service. */ template requires requires( const PhysicalCore &core, const mfem::Vector &direction ) { { core.ApplyDensityVolumeIntegralDensityAction(direction) } -> std::convertible_to; { core.ApplyDensityVolumeIntegralSurfaceShapeAction(direction) } -> std::convertible_to; } explicit DensityVolumeIntegralContext( const PhysicalCore &core ) noexcept : m_core(std::addressof(core)), m_densityAction(&ApplyDensityAction), m_surfaceShapeAction(&ApplySurfaceShapeAction) { } private: template [[nodiscard]] static double ApplyDensityAction( const void *untypedCore, const mfem::Vector &densityDirection ) { const auto &core = *static_cast(untypedCore); return core.ApplyDensityVolumeIntegralDensityAction( densityDirection ); } template [[nodiscard]] static double ApplySurfaceShapeAction( const void *untypedCore, const mfem::Vector &surfaceShapeDirection ) { const auto &core = *static_cast(untypedCore); return core.ApplyDensityVolumeIntegralSurfaceShapeAction( surfaceShapeDirection ); } const void *m_core{nullptr}; Action m_densityAction{nullptr}; Action m_surfaceShapeAction{nullptr}; }; namespace detail { template < models::ModelSpecification Specification, typename Physics, bool ContextAvailable = DensityVolumeIntegralSpecification> struct DensityVolumeIntegralPhysicsConstruction final { using Context = void; static constexpr bool constructible = false; }; template struct DensityVolumeIntegralPhysicsConstruction< Specification, Physics, true> final { using Context = DensityVolumeIntegralContext; static constexpr bool constructible = std::constructible_from< Physics, const Specification &, Context>; }; template concept DensityVolumeIntegralCore = requires( const PhysicalCore &core, const mfem::Vector &direction ) { { core.ApplyDensityVolumeIntegralDensityAction(direction) } -> std::convertible_to; { core.ApplyDensityVolumeIntegralSurfaceShapeAction(direction) } -> std::convertible_to; }; /* * A Jacobian callback is stricter than residual assembly. Its * row/source pair is checked against the compiler output, then the * callback receives a direction view containing only that source and * an additive action view containing only that row. Consequently, * independently legal endpoints cannot accidentally be recombined * into an undeclared edge inside one callback. As with any assembly * API, this structural contract does not attempt to prove that the * callback's arithmetic is the mathematical derivative it claims. */ template < typename Form, typename AllowedCouplings, typename AllowedValueBlocks, models::ModelSpecification Specification, typename AllowedResidualBlocks> class RestrictedSpecificationJacobianView final { public: RestrictedSpecificationJacobianView( const RestrictedSpecificationStateView< Form, AllowedValueBlocks, Specification> &direction, const RestrictedSpecificationResidualView< Form, AllowedResidualBlocks> &action ) noexcept : m_direction(direction), m_action(action) { } template requires requires { typename std::remove_cvref_t::residual; typename std::remove_cvref_t::value; } && utils::blocks::contains_type_v< typename std::remove_cvref_t::value, AllowedValueBlocks> && utils::blocks::contains_type_v< typename std::remove_cvref_t::residual, AllowedResidualBlocks> && utils::blocks::contains_type_v< StellarEquilibriumJacobianCoupling< typename std::remove_cvref_t::residual, typename std::remove_cvref_t::value>, AllowedCouplings> && requires( Callback &&callback, RestrictedSpecificationStateView< Form, utils::blocks::type_list< typename std::remove_cvref_t::value>, Specification> &direction, RestrictedSpecificationResidualView< Form, utils::blocks::type_list< typename std::remove_cvref_t::residual>> &action ) { { std::forward(callback)(direction, action) } -> std::same_as; } void add( const ResidualTerm &, const ValueTerm &, Callback &&callback ) const { using DirectionView = RestrictedSpecificationStateView< Form, utils::blocks::type_list< typename std::remove_cvref_t::value>, Specification>; using ActionView = RestrictedSpecificationResidualView< Form, utils::blocks::type_list< typename std::remove_cvref_t::residual>>; DirectionView direction = m_direction.template narrow< utils::blocks::type_list< typename std::remove_cvref_t::value>>(); ActionView action{m_action}; std::forward(callback)(direction, action); } private: RestrictedSpecificationStateView< Form, AllowedValueBlocks, Specification> m_direction; RestrictedSpecificationResidualView m_action; }; template struct CompilePhysicsTags; template struct CompilePhysicsTags< Specification, utils::blocks::type_list> final { using Type = utils::blocks::type_list< typename StellarDependencyBlock::Type...>; static constexpr bool complete = (StellarDependencyBlock::mapped && ...); }; template struct CompilePhysicsDerivatives; template struct CompilePhysicsDerivative; template < models::ModelSpecification Specification, typename Equation, typename State> struct CompilePhysicsDerivative< Specification, models::stellar::Derivative> final { using Type = StellarEquilibriumJacobianCoupling< typename StellarDependencyBlock< Specification, Equation>::Type, typename StellarDependencyBlock< Specification, State>::Type>; static constexpr bool complete = StellarDependencyBlock::mapped && StellarDependencyBlock::mapped; }; template struct CompilePhysicsDerivatives< Specification, utils::blocks::type_list> final { using Type = utils::blocks::type_list< typename CompilePhysicsDerivative< Specification, Derivatives>::Type...>; static constexpr bool complete = (CompilePhysicsDerivative< Specification, Derivatives>::complete && ...); }; template struct SpecificationRuntimeAccess final { using SpecificationType = Specification; using Compilation = StellarEquilibriumSpecificationCompilation; using Topology = SpecificationPhysicsTopology; using Form = CompiledStellarEquilibriumForm; using ValueBlocks = UniqueConcatenateBlockListsT< typename Compilation::GeneratedValueBlocks, typename Compilation::DependsOnValueBlocks>; using ResidualBlocks = UniqueConcatenateBlockListsT< typename Compilation::GeneratedResidualBlocks, typename Compilation::AffectedResidualBlocks>; using StateView = RestrictedSpecificationStateView< Form, ValueBlocks, Specification>; using ResidualView = RestrictedSpecificationResidualView; template using ValueBlockFor = typename StellarDependencyBlock< Specification, StateTag>::Type; template using ResidualBlockFor = typename StellarDependencyBlock< Specification, EquationTag>::Type; template using DirectionView = RestrictedSpecificationStateView< Form, utils::blocks::type_list>, Specification>; template using RowView = RestrictedSpecificationResidualView< Form, utils::blocks::type_list>>; template using Row = ExactSpecificationResidualRow< Form, ResidualBlockFor>; using JacobianView = RestrictedSpecificationJacobianView< Form, typename Compilation::JacobianCouplings, ValueBlocks, Specification, ResidualBlocks>; using ProviderResidualBlocks = typename CompilePhysicsTags< Specification, typename Topology::ResidualEquations>::Type; using ProviderJacobianCouplings = typename CompilePhysicsDerivatives< Specification, typename Topology::Derivatives>::Type; static_assert(CompilePhysicsTags< Specification, typename Topology::ResidualEquations>::complete); static_assert(CompilePhysicsDerivatives< Specification, typename Topology::Derivatives>::complete); static_assert(std::same_as< ProviderResidualBlocks, ResidualBlocks>); static_assert(std::same_as< ProviderJacobianCouplings, typename Compilation::JacobianCouplings>); }; template struct ExactResidualProviderSet; template struct ExactResidualProviderSet< Physics, Access, utils::blocks::type_list> final { static constexpr bool complete = (requires( const Physics &physics, typename Access::template Row &row ) { { physics.AddResidual(Equations{}, row) } -> stellar::ContributionResult; } && ...); static void Apply( const Physics &physics, const typename Access::ResidualView &residual ) requires complete { (ApplyOne(physics, residual), ...); } private: template static void ApplyOne( const Physics &physics, const typename Access::ResidualView &residual ) { typename Access::template RowView rowView{residual}; typename Access::template Row row{rowView}; decltype(auto) result = physics.AddResidual( Equation{}, row ); row.Verify(result); } }; template struct ExactJacobianProviderSet; template struct ExactJacobianProviderSet< Physics, Access, utils::blocks::type_list> final { private: template struct Traits; template struct Traits> final { using EquationTag = Equation; using StateTag = State; }; template [[nodiscard]] static consteval bool ProviderIsComplete() { using Equation = typename Traits::EquationTag; using State = typename Traits::StateTag; return requires( const Physics &physics, const typename Access::template DirectionView &direction, typename Access::template Row &row ) { { physics.AddJacobianAction( Derivative{}, direction, row ) } -> stellar::ContributionResult; }; } public: static constexpr bool complete = (ProviderIsComplete() && ...); static void Apply( const Physics &physics, const typename Access::StateView &direction, const typename Access::ResidualView &action ) requires complete { (ApplyOne(physics, direction, action), ...); } private: template static void ApplyOne( const Physics &physics, const typename Access::StateView &direction, const typename Access::ResidualView &action ) { using Equation = typename Traits::EquationTag; using State = typename Traits::StateTag; typename Access::template DirectionView source = direction.template narrow>>(); typename Access::template RowView rowView{action}; typename Access::template Row row{rowView}; decltype(auto) result = physics.AddJacobianAction( Derivative{}, source, row ); row.Verify(result); } }; template inline constexpr bool exactSpecificationPhysicsProvidersComplete = ExactResidualProviderSet< Physics, Access, typename Access::Topology::ResidualEquations>::complete && ExactJacobianProviderSet< Physics, Access, typename Access::Topology::Derivatives>::complete; /* Classify one exact nested derivative provider by its return type. * This information is useful outside residual assembly as well: a * preconditioner may omit a compiler-declared core-to-core edge only * when the physics implementation itself proves that the edge is the * identically zero map. The primary remains well formed so capability * queries for malformed providers fail normally rather than producing * diagnostics deep in a factory body. */ template < typename Physics, typename Access, typename Derivative, typename = void> struct ExactJacobianProviderResult final { using Coupling = void; using Result = void; static constexpr bool complete = false; static constexpr bool structuralZero = false; }; template < typename Physics, typename Access, typename Equation, typename State> struct ExactJacobianProviderResult< Physics, Access, models::stellar::Derivative, std::void_t().AddJacobianAction( models::stellar::Derivative{}, std::declval &>(), std::declval &>() ))>> final { using Derivative = models::stellar::Derivative; using Coupling = typename CompilePhysicsDerivative< typename Access::SpecificationType, Derivative>::Type; using Result = decltype(std::declval().AddJacobianAction( Derivative{}, std::declval &>(), std::declval &>() )); static constexpr bool complete = stellar::ContributionResult; static constexpr bool structuralZero = complete && std::same_as< std::remove_cvref_t, stellar::StructuralZero>; }; template < typename Physics, typename Access, typename Coupling, typename Derivatives> struct ExactCouplingProvidersAreStructuralZero; template < typename Physics, typename Access, typename Coupling, typename... Derivatives> struct ExactCouplingProvidersAreStructuralZero< Physics, Access, Coupling, utils::blocks::type_list> final { private: template using Provider = ExactJacobianProviderResult< Physics, Access, Derivative>; static constexpr bool hasMatchingProvider = (false || ... || std::same_as< Coupling, typename Provider::Coupling>); static constexpr bool everyMatchingProviderIsZero = (true && ... && (!std::same_as< Coupling, typename Provider::Coupling> || Provider::structuralZero)); public: static constexpr bool value = hasMatchingProvider && everyMatchingProviderIsZero; }; template < typename Specification, typename Model, typename Coupling, typename = void> struct NestedSpecificationCouplingIsStructuralZero : std::false_type { }; template < models::ModelSpecification Specification, model::StellarModelType Model, typename Coupling> requires Model::template containsSpecification struct NestedSpecificationCouplingIsStructuralZero< Specification, Model, Coupling, std::void_t< typename RuntimeContributionSelection< Specification, Model>::Physics>> final : std::bool_constant< RuntimeContributionSelection::available && RuntimeContributionSelection::registered && !RuntimeContributionSelection::ambiguous && ExactCouplingProvidersAreStructuralZero< typename RuntimeContributionSelection< Specification, Model>::Physics, SpecificationRuntimeAccess, Coupling, typename SpecificationPhysicsTopology< Specification>::Derivatives>::value> { }; /* * Adapter for the physics-facing nested extension protocol. * * The outer runtime constructs this object through the same internal * slot interface as trusted backend contributions. The authored * Physics object behind it sees a deliberately smaller interface: * * Physics(const Specification &) * // or, only with Reads and a supporting core: * Physics(const Specification &, DensityVolumeIntegralContext) * Report PrepareAfterPhysical(const StateView &) * ContributionResult AddResidual(EquationTag, Row &) const * ContributionResult AddJacobianAction( * stellar::Derivative, * const one-source DirectionView &, * Row &) const * * The adapter invokes those overloads once for every row/edge inferred * from Reads/Changes. row.add(...) returns the required success token; * an identically absent term must return stellar::structuralZero. * Missing overloads fail the capability query at compile time, while * double assembly and inconsistent result tokens fail immediately at * runtime. Physics authors never enumerate a backend provider list or * see an aggregate direction/action object. * * bool IsPrepared() const * * A nested contribution which owns rotation additionally supplies * `RigidRotation GenerateRotation(const StateView &)`. The adapter, * rather than the extension, owns the dependency stamp and compares * successive rotations. In particular, no nested implementation is * ever handed the model, FEM/mapper objects, dependency set, control * context, or physical core. The optional integral context is a * read-only service handle and does not expose any of those objects. */ template < models::ModelSpecification Specification, model::StellarModelType Model, typename Physics> class PhysicsFacingSpecificationRuntime final { private: using Access = SpecificationRuntimeAccess; using IntegralConstruction = DensityVolumeIntegralPhysicsConstruction; using ResidualProviders = ExactResidualProviderSet< Physics, Access, typename Access::Topology::ResidualEquations>; using JacobianProviders = ExactJacobianProviderSet< Physics, Access, typename Access::Topology::Derivatives>; static constexpr std::size_t rotationProviders = RuntimeContributionSelection::rotationProviders; public: using Report = typename Physics::Report; template requires( IntegralConstruction::constructible && DensityVolumeIntegralCore) PhysicsFacingSpecificationRuntime( fem::FEM &, const mapping::DomainMapper &, PhysicalCore &physical, const Model &model ) : m_physics( model.template specification(), typename IntegralConstruction::Context{physical}) { m_generatedRotationDependency.identity = static_cast(reinterpret_cast(this)); } template requires( std::constructible_from && (!IntegralConstruction::constructible || !DensityVolumeIntegralCore)) PhysicsFacingSpecificationRuntime( fem::FEM &, const mapping::DomainMapper &, PhysicalCore &, const Model &model ) : m_physics(model.template specification()) { m_generatedRotationDependency.identity = static_cast(reinterpret_cast(this)); } template requires( rotationProviders == 0 || requires(Physics &implementation, const StateView &state) { { implementation.GenerateRotation(state) } -> std::same_as; }) void ReadPhysicalControls( const StateView &state, StellarEquilibriumControlContext &context ) { if constexpr (rotationProviders == 1) { physics::RigidRotation rotation = m_physics.GenerateRotation(state); const bool changed = !m_generatedRotation.has_value() || !SameRotation(*m_generatedRotation, rotation); if (changed) { m_generatedRotation = rotation; ++m_generatedRotationDependency.revision; } context.dependencies.rotation = m_generatedRotationDependency; context.rotation = std::move(rotation); ++context.rotationProviderCount; context.generatedPhysicalControl = context.generatedPhysicalControl || changed; } } template requires requires(Physics &implementation, const StateView &state) { { implementation.PrepareAfterPhysical(state) } -> std::same_as; } [[nodiscard]] Report PrepareAfterPhysical( const StateView &state, const StellarEquilibriumDependencies &, const PhysicalCore & ) { return m_physics.PrepareAfterPhysical(state); } void AddResidual(const typename Access::ResidualView &residual) const requires ResidualProviders::complete { ResidualProviders::Apply(m_physics, residual); } template requires JacobianProviders::complete void AddJacobianAction( const typename Access::StateView &direction, const typename Access::ResidualView &action, const PhysicalCore & ) const { JacobianProviders::Apply(m_physics, direction, action); } [[nodiscard]] bool IsPrepared() const noexcept requires requires(const Physics &implementation) { { implementation.IsPrepared() } -> std::convertible_to; } { return static_cast(m_physics.IsPrepared()); } [[nodiscard]] Physics &physics() noexcept { return m_physics; } [[nodiscard]] const Physics &physics() const noexcept { return m_physics; } private: [[nodiscard]] static bool SameVector( const mfem::Vector &left, const mfem::Vector &right ) noexcept { if (left.Size() != right.Size()) { return false; } for (int component = 0; component < left.Size(); ++component) { if (left(component) != right(component)) { return false; } } return true; } [[nodiscard]] static bool SameRotation( const physics::RigidRotation &left, const physics::RigidRotation &right ) noexcept { return SameVector(left.angular_velocity(), right.angular_velocity()) && SameVector(left.center(), right.center()); } Physics m_physics; std::optional m_generatedRotation; StellarEquilibriumDependencyStamp m_generatedRotationDependency{}; }; template < model::StellarModelType Model, PreparedStellarEquilibriumPhysicalCore PhysicalCore, typename SpecificationSet> class PreparedSpecificationSet; template class PreparedSpecificationSet< Model, PhysicalCore, models::detail::SpecificationSetStorage<>> final { public: PreparedSpecificationSet( fem::FEM &, const mapping::DomainMapper &, PhysicalCore &, const Model & ) noexcept { } template void ReadPhysicalControls(const StateView &, StellarEquilibriumControlContext &) noexcept { } template void PrepareAfterPhysical( const StateView &, const StellarEquilibriumDependencies &, const PhysicalCore &, Reports & ) noexcept { } template void AddResidual(const ResidualView &, const PhysicalCore &) const noexcept { } template void AddJacobianAction( const DirectionView &, const ActionView &, const PhysicalCore & ) const noexcept { } [[nodiscard]] constexpr bool IsPrepared() const noexcept { return true; } }; template < model::StellarModelType Model, PreparedStellarEquilibriumPhysicalCore PhysicalCore, models::ModelSpecification Head, models::ModelSpecification... Tail> class PreparedSpecificationSet< Model, PhysicalCore, models::detail::SpecificationSetStorage> final { private: using HeadSlot = PreparedRuntimeContribution; using HeadAccess = SpecificationRuntimeAccess; using TailSlots = PreparedSpecificationSet< Model, PhysicalCore, models::detail::SpecificationSetStorage>; public: PreparedSpecificationSet( fem::FEM &finiteElements, const mapping::DomainMapper &domainMapper, PhysicalCore &physical, const Model &model ) : m_head(finiteElements, domainMapper, physical, model), m_tail(finiteElements, domainMapper, physical, model) { } template void ReadPhysicalControls( const StateView &state, StellarEquilibriumControlContext &context ) { m_head.ReadPhysicalControls(typename HeadAccess::StateView{state}, context); m_tail.ReadPhysicalControls(state, context); } template void PrepareAfterPhysical( const StateView &state, const StellarEquilibriumDependencies &dependencies, const PhysicalCore &physical, Reports &reports ) { std::get(reports) = m_head.PrepareAfterPhysical( typename HeadAccess::StateView{state}, dependencies, physical ); m_tail.template PrepareAfterPhysical(state, dependencies, physical, reports); } template void AddResidual( const ResidualView &residual, const PhysicalCore &physical ) const { m_head.AddResidual(typename HeadAccess::ResidualView{ residual, physical.GetSurfaceConstraintOperator() }); m_tail.AddResidual(residual, physical); } template void AddJacobianAction( const DirectionView &direction, const ActionView &action, const PhysicalCore &physical ) const { m_head.AddJacobianAction( typename HeadAccess::StateView{direction}, typename HeadAccess::ResidualView{ action, physical.GetSurfaceConstraintOperator() }, physical ); m_tail.AddJacobianAction(direction, action, physical); } [[nodiscard]] bool IsPrepared() const noexcept { return m_head.IsPrepared() && m_tail.IsPrepared(); } template [[nodiscard]] auto &Get() noexcept { if constexpr (std::same_as) { if constexpr (HasNestedStellarEquilibriumPhysics) { return m_head.physics(); } else { return m_head; } } else { return m_tail.template Get(); } } template [[nodiscard]] const auto &Get() const noexcept { if constexpr (std::same_as) { if constexpr (HasNestedStellarEquilibriumPhysics) { return m_head.physics(); } else { return m_head; } } else { return m_tail.template Get(); } } private: HeadSlot m_head; TailSlots m_tail; }; template < models::ModelSpecification Specification, model::StellarModelType Model, bool SymbolicallyCompilable = StellarEquilibriumSystemCompilable && CoreRuntimeInterfaceAudit>::complete, typename = void> struct RuntimeContributionInterfaceAudit { static constexpr bool complete = false; static constexpr std::size_t rotationProviders = 0; }; template struct RuntimeContributionInterfaceAudit< Specification, Model, true, std::void_t< PreparedRuntimeContribution, typename PreparedRuntimeContribution::Report, std::bool_constant( RuntimeContributionSelection::registered)>, std::integral_constant< std::size_t, static_cast( RuntimeContributionSelection::rotationProviders)>>> { private: using Selection = RuntimeContributionSelection; using Prepared = PreparedRuntimeContribution; using PhysicalCore = typename CoreRuntimeInterfaceAudit::CoreType; using Access = SpecificationRuntimeAccess; using StateView = typename Access::StateView; using ResidualViewType = typename Access::ResidualView; public: using Report = typename Prepared::Report; static constexpr bool complete = requires( fem::FEM &finiteElements, const mapping::DomainMapper &domainMapper, PhysicalCore &physical, const PhysicalCore &constantPhysical, const Model &model, Prepared &prepared, const Prepared &constantPrepared, const StateView &state, const ResidualViewType &residual, StellarEquilibriumControlContext &controls, const StellarEquilibriumDependencies &dependencies ) { requires Selection::registered; requires !Selection::ambiguous; requires std::default_initializable; requires std::assignable_from; requires std::constructible_from< Prepared, fem::FEM &, const mapping::DomainMapper &, PhysicalCore &, const Model &>; { prepared.ReadPhysicalControls(state, controls) } -> std::same_as; { prepared.PrepareAfterPhysical(state, dependencies, constantPhysical) } -> std::same_as; { constantPrepared.AddResidual(residual) } -> std::same_as; { constantPrepared.AddJacobianAction(state, residual, constantPhysical) } -> std::same_as; { constantPrepared.IsPrepared() } -> std::convertible_to; }; static constexpr std::size_t rotationProviders = complete ? Selection::rotationProviders : 0; }; template struct RuntimeContributionAuditImpl; template struct RuntimeContributionAuditImpl< Model, models::detail::SpecificationSetStorage, false> { static constexpr bool complete = false; static constexpr std::size_t rotationProviders = 0; using ReportTuple = std::tuple<>; }; template struct RuntimeContributionAuditImpl< Model, models::detail::SpecificationSetStorage, true> { static constexpr bool complete = true; static constexpr std::size_t rotationProviders = (std::size_t{0} + ... + RuntimeContributionInterfaceAudit::rotationProviders); using ReportTuple = std::tuple< typename RuntimeContributionInterfaceAudit::Report...>; }; template struct RuntimeContributionAudit; template struct RuntimeContributionAudit> : RuntimeContributionAuditImpl< Model, models::detail::SpecificationSetStorage, (RuntimeContributionInterfaceAudit::complete && ...)> { }; template struct MakeValueSizes; template struct MakeValueSizes> { template [[nodiscard]] static std::array Apply( const utils::blocks::form_layout &physicalLayout ) { return {BlockSize(physicalLayout)...}; } private: template [[nodiscard]] static int BlockSize(const utils::blocks::form_layout &physicalLayout) { if constexpr (utils::blocks::contains_type_v) { constexpr int index = utils::blocks::type_index_v; return physicalLayout.value_offsets()[index + 1] - physicalLayout.value_offsets()[index]; } else { static_assert( Block::static_block_size != utils::blocks::dynamic_block_size, "A generated stellar-equilibrium value block must have a compile-time size." ); return Block::static_block_size; } } }; template struct MakeResidualSizes; template struct MakeResidualSizes> { template [[nodiscard]] static std::array Apply( const utils::blocks::form_layout &physicalLayout ) { return {BlockSize(physicalLayout)...}; } private: template [[nodiscard]] static int BlockSize(const utils::blocks::form_layout &physicalLayout) { if constexpr (utils::blocks::contains_type_v) { constexpr int index = utils::blocks::type_index_v; return physicalLayout.residual_offsets()[index + 1] - physicalLayout.residual_offsets()[index]; } else { static_assert( Block::static_block_size != utils::blocks::dynamic_block_size, "A generated stellar-equilibrium residual block must have a compile-time size." ); return Block::static_block_size; } } }; template struct AllBlocksBelongToList : std::false_type { }; template struct AllBlocksBelongToList< utils::blocks::type_list, AvailableBlocks> : std::bool_constant< (utils::blocks::contains_type_v && ...)> { }; template < model::StellarModelType Model, bool HasCompiledPhysicalRoot = StellarEquilibriumSystemCompilable && std::remove_cvref_t::template containsSpecification> struct PhysicalRootCompatibilityAudit { static constexpr bool complete = false; }; template struct PhysicalRootCompatibilityAudit { private: using ModelType = std::remove_cvref_t; using RootForm = CompiledStellarEquilibriumForm; using PhysicalForm = utils::blocks::surface_deformed_stellar_equilibrium_form; public: static constexpr bool complete = AllBlocksBelongToList< typename PhysicalForm::value_blocks, typename RootForm::value_blocks>::value && AllBlocksBelongToList< typename PhysicalForm::residual_blocks, typename RootForm::residual_blocks>::value; }; template struct SpecificationIndex; template struct SpecificationIndex< Specification, models::detail::SpecificationSetStorage> : std::integral_constant { }; template struct SpecificationIndex< Specification, models::detail::SpecificationSetStorage> : std::integral_constant< std::size_t, 1 + SpecificationIndex< Specification, models::detail::SpecificationSetStorage>::value> { }; template struct SpecificationIndex>; } // namespace detail /* * Physics-extension views expose exactly the blocks declared by one * specification. Generated coordinates/rows are included automatically; * no extension author needs to spell out backend block lists twice. */ template concept StellarEquilibriumSpecificationBelongsToModel = models::ModelSpecification> && model::StellarModelType> && requires { requires std::remove_cvref_t::template containsSpecification< std::remove_cvref_t>; }; template requires StellarEquilibriumSpecificationBelongsToModel using StellarEquilibriumContributionStateView = typename detail::SpecificationRuntimeAccess< std::remove_cvref_t, std::remove_cvref_t>::StateView; template requires StellarEquilibriumSpecificationBelongsToModel using StellarEquilibriumContributionResidualView = typename detail::SpecificationRuntimeAccess< std::remove_cvref_t, std::remove_cvref_t>::ResidualView; /* Low-level topology-inspection view retained for compiler tests and * backend adapters. Physics-facing nested runtimes are not handed this * imperative object; they use the exact provider protocol below. */ template requires StellarEquilibriumSpecificationBelongsToModel using StellarEquilibriumContributionJacobianView = typename detail::SpecificationRuntimeAccess< std::remove_cvref_t, std::remove_cvref_t>::JacobianView; template requires StellarEquilibriumSpecificationBelongsToModel using StellarEquilibriumContributionTopology = typename detail::SpecificationRuntimeAccess< std::remove_cvref_t, std::remove_cvref_t>::Topology; template requires StellarEquilibriumSpecificationBelongsToModel using StellarEquilibriumContributionDirection = typename detail::SpecificationRuntimeAccess< std::remove_cvref_t, std::remove_cvref_t>::template DirectionView; template requires StellarEquilibriumSpecificationBelongsToModel using StellarEquilibriumContributionRow = typename detail::SpecificationRuntimeAccess< std::remove_cvref_t, std::remove_cvref_t>::template Row; /* A focused diagnostic concept for extension authors. It answers the * useful question directly: does this prepared physics class implement * every residual row and derivative inferred from my declaration? */ template concept CompleteStellarEquilibriumPhysicsProvider = StellarEquilibriumSpecificationBelongsToModel && detail::exactSpecificationPhysicsProvidersComplete< std::remove_cvref_t, detail::SpecificationRuntimeAccess< std::remove_cvref_t, std::remove_cvref_t>>; /* True only when the exact nested physics provider corresponding to this * compiled coupling returns StructuralZero. This is intentionally a * proof about the provider's type, not a second author-written metadata * flag. Preconditioners use it to distinguish an intentional zero from a * nonzero contribution that their selected structure backend must either * implement or reject. */ template inline constexpr bool stellarEquilibriumSpecificationCouplingIsStructuralZero = StellarEquilibriumSpecificationBelongsToModel< Specification, Model> && detail::NestedSpecificationCouplingIsStructuralZero< std::remove_cvref_t, std::remove_cvref_t, std::remove_cvref_t>::value; template concept StellarEquilibriumPhysicsAvailableFor = StellarEquilibriumSpecificationBelongsToModel && detail::RuntimeContributionInterfaceAudit< std::remove_cvref_t, std::remove_cvref_t>::complete; template inline constexpr bool hasCompleteStellarEquilibriumRuntime = detail::RuntimeContributionAudit< std::remove_cvref_t, typename std::remove_cvref_t::SpecificationTypes>::complete; template inline constexpr bool hasStellarEquilibriumCoreRuntime = detail::CoreRuntimeInterfaceAudit>::complete; template requires hasStellarEquilibriumCoreRuntime using StellarEquilibriumPhysicalCoreType = typename detail::CoreRuntimeInterfaceAudit>::CoreType; template inline constexpr std::size_t stellarEquilibriumRotationProviderCount = detail::RuntimeContributionAudit< std::remove_cvref_t, typename std::remove_cvref_t::SpecificationTypes>::rotationProviders; template inline constexpr bool hasCompatibleStellarEquilibriumPhysicalRoot = detail::PhysicalRootCompatibilityAudit>::complete; template requires hasCompleteStellarEquilibriumRuntime struct PreparedVariadicStellarEquilibriumReport final { using ModelType = std::remove_cvref_t; using SpecificationTypes = typename ModelType::SpecificationTypes; using SpecificationReports = typename detail::RuntimeContributionAudit::ReportTuple; PreparedStellarEquilibriumReport physical; SpecificationReports specifications; bool generatedPhysicalControl{false}; bool assembledResidual{false}; template requires ModelType::template containsSpecification [[nodiscard]] const auto &specification() const noexcept { constexpr std::size_t index = detail::SpecificationIndex::value; return std::get(specifications); } [[nodiscard]] bool DidAnyWork() const noexcept { return physical.DidAnyWork() || generatedPhysicalControl || assembledResidual; } }; /** * One runtime root for every fully supported specification pack. * * The class template itself is the inferred type. Its slot set is a * recursive, statically dispatched fold, so adding a specification never * creates a new hand-written combination class or a runtime registry. */ template requires hasCompatibleStellarEquilibriumPhysicalRoot && hasCompleteStellarEquilibriumRuntime && hasStellarEquilibriumCoreRuntime && CompilableRootManifestFor< std::remove_cvref_t, CompiledStellarEquilibriumForm>> class PreparedVariadicStellarEquilibriumOperator final : public mfem::Operator { public: using ModelType = std::remove_cvref_t; using EquationOfStateType = model::EquationOfStateType; using SurfaceConditionType = model::SurfaceConditionType; using CoreRuntime = StellarEquilibriumCoreRuntime; using PhysicalCoreType = StellarEquilibriumPhysicalCoreType; using PhysicalCoreOwner = std::unique_ptr; using SpecificationTypes = typename ModelType::SpecificationTypes; using FormType = CompiledStellarEquilibriumForm; using JacobianFormType = CompiledStellarEquilibriumJacobianForm; using Layout = utils::blocks::form_layout; using Manifest = EquilibriumSystemManifest; using Report = PreparedVariadicStellarEquilibriumReport; static constexpr std::size_t rotationProviderCount = stellarEquilibriumRotationProviderCount; /* * The physical core may retain references to constitutive data, so a * raw ModelType reference is intentionally not a construction option. * Shared ownership keeps every EOS backend safe for the lifetime of * this prepared root. StellarEquilibriumProblem owns and supplies the * same handle on the normal user-facing path. */ PreparedVariadicStellarEquilibriumOperator( fem::FEM &finiteElements, const mapping::DomainMapper &domainMapper, std::shared_ptr model, PressureSurfaceConstraintView surfaceConstraint, deformation::PreparedDomainDeformationRuntime domainDeformation ) requires(rotationProviderCount <= 1) : PreparedVariadicStellarEquilibriumOperator( finiteElements, domainMapper, model, MakePhysical( finiteElements, domainMapper, RequireModel(model), surfaceConstraint, std::move(domainDeformation) ) ) { } PreparedVariadicStellarEquilibriumOperator(const PreparedVariadicStellarEquilibriumOperator &) = delete; PreparedVariadicStellarEquilibriumOperator &operator=(const PreparedVariadicStellarEquilibriumOperator &) = delete; PreparedVariadicStellarEquilibriumOperator(PreparedVariadicStellarEquilibriumOperator &&) = delete; PreparedVariadicStellarEquilibriumOperator &operator=(PreparedVariadicStellarEquilibriumOperator &&) = delete; [[nodiscard]] Report Prepare( const mfem::Vector &state, const StellarEquilibriumDependencies &dependencies, const physics::RigidRotation &rotation ) requires(rotationProviderCount == 0) { detail::StellarEquilibriumControlContext controls{ .dependencies = dependencies, .rotation = rotation, .rotationProviderCount = 1, .generatedPhysicalControl = false }; return PrepareWithControls(state, std::move(controls)); } [[nodiscard]] Report Prepare( const mfem::Vector &state, const StellarEquilibriumDependencies &dependencies ) requires(rotationProviderCount == 1) { detail::StellarEquilibriumControlContext controls{ .dependencies = dependencies, .rotation = std::nullopt, .rotationProviderCount = 0, .generatedPhysicalControl = false }; return PrepareWithControls(state, std::move(controls)); } void BuildResidual(mfem::Vector &residual) const { VerifyPrepared(); residual = m_cachedResidual; } void Mult(const mfem::Vector &direction, mfem::Vector &action) const override { VerifyPrepared(); MFEM_VERIFY(direction.Size() == Width(), "The variadic stellar root received a wrong-sized direction."); GatherPhysicalValues( direction, m_physicalDirection, typename PhysicalForm::value_blocks{} ); m_physical->Mult(m_physicalDirection, m_physicalAction); action.SetSize(Height()); action = 0.0; ScatterPhysicalResiduals( m_physicalAction, action, typename PhysicalForm::residual_blocks{} ); const auto directionView = m_manifest.directionView(direction); const auto actionView = m_manifest.residualView(action); m_specifications.AddJacobianAction(directionView, actionView, *m_physical); } [[nodiscard]] bool IsPrepared() const noexcept { return m_isPrepared && m_physical->IsPrepared() && m_specifications.IsPrepared(); } [[nodiscard]] const Layout &GetLayout() const noexcept { return m_manifest.layout(); } [[nodiscard]] const Manifest &GetRootManifest() const noexcept { return m_manifest; } [[nodiscard]] const PhysicalCoreType &GetPhysicalOperator() const noexcept { return *m_physical; } template requires ModelType::template containsSpecification [[nodiscard]] const auto &GetPreparedContribution() const noexcept { return m_specifications.template Get(); } [[nodiscard]] const PreparedAngularMomentumOperator &GetAngularMomentumConstraint() const noexcept requires ModelType::template containsSpecification { return GetPreparedContribution().constraint(); } [[nodiscard]] const PreparedCentralDensityConstraint &GetCentralDensityConstraint() const noexcept requires ModelType::template containsSpecification { return GetPreparedContribution().constraint(); } [[nodiscard]] RootConstraintReport GetFixedMassReport() const { VerifyPrepared(); const RootConstraintReport physicalReport = m_physical->GetFixedMassReport(); return m_manifest.fixedMassReport(physicalReport.achieved); } [[nodiscard]] AngularMomentumConstraintReport GetAngularMomentumReport() const requires ModelType::template containsSpecification { VerifyPrepared(); return GetAngularMomentumConstraint().GetConstraintReport(); } [[nodiscard]] CentralDensityConstraintReport GetCentralDensityReport() const requires ModelType::template containsSpecification { VerifyPrepared(); return GetCentralDensityConstraint().GetConstraintReport(); } private: using PhysicalForm = utils::blocks::surface_deformed_stellar_equilibrium_form; using SpecificationSlots = detail::PreparedSpecificationSet; [[nodiscard]] static const ModelType &RequireModel( const std::shared_ptr &model ) { MFEM_VERIFY( model != nullptr, "The variadic stellar-equilibrium root requires shared ownership of its model." ); return *model; } template void GatherPhysicalValueBlock( const mfem::Vector &root, mfem::Vector &physical ) const { constexpr int rootIndex = utils::blocks::type_index_v; constexpr int physicalIndex = utils::blocks::type_index_v; const auto &rootOffsets = m_manifest.layout().value_offsets(); const auto &physicalOffsets = m_physical->GetLayout().value_offsets(); const int rootSize = rootOffsets[rootIndex + 1] - rootOffsets[rootIndex]; const int physicalSize = physicalOffsets[physicalIndex + 1] - physicalOffsets[physicalIndex]; MFEM_VERIFY(rootSize == physicalSize, "A compiled physical value block changed size in the root layout."); const mfem::Vector source( const_cast(root.GetData()) + rootOffsets[rootIndex], rootSize ); mfem::Vector destination(physical, physicalOffsets[physicalIndex], physicalSize); destination = source; destination.SyncAliasMemory(physical); } template void GatherPhysicalValues( const mfem::Vector &root, mfem::Vector &physical, utils::blocks::type_list ) const { MFEM_VERIFY(physical.Size() == m_physical->Width(), "The physical-state workspace has the wrong size."); (GatherPhysicalValueBlock(root, physical), ...); } template void ScatterPhysicalResidualBlock( const mfem::Vector &physical, mfem::Vector &root ) const { constexpr int physicalIndex = utils::blocks::type_index_v; constexpr int rootIndex = utils::blocks::type_index_v; const auto &physicalOffsets = m_physical->GetLayout().residual_offsets(); const auto &rootOffsets = m_manifest.layout().residual_offsets(); const int physicalSize = physicalOffsets[physicalIndex + 1] - physicalOffsets[physicalIndex]; const int rootSize = rootOffsets[rootIndex + 1] - rootOffsets[rootIndex]; MFEM_VERIFY(rootSize == physicalSize, "A compiled physical residual block changed size in the root layout."); const mfem::Vector source( const_cast(physical.GetData()) + physicalOffsets[physicalIndex], physicalSize ); mfem::Vector destination(root, rootOffsets[rootIndex], rootSize); destination = source; destination.SyncAliasMemory(root); } template void ScatterPhysicalResiduals( const mfem::Vector &physical, mfem::Vector &root, utils::blocks::type_list ) const { MFEM_VERIFY(physical.Size() == m_physical->Height(), "The physical-action workspace has the wrong size."); (ScatterPhysicalResidualBlock(physical, root), ...); } [[nodiscard]] static PhysicalCoreOwner MakePhysical( fem::FEM &finiteElements, const mapping::DomainMapper &domainMapper, const ModelType &model, PressureSurfaceConstraintView surfaceConstraint, deformation::PreparedDomainDeformationRuntime domainDeformation ) { return CoreRuntime::Make( finiteElements, domainMapper, model.equationOfState(), models::compileConstraint(model.template specification()), surfaceConstraint, std::move(domainDeformation) ); } [[nodiscard]] static std::array MakeValueSizes( const StellarEquilibriumLayout &physicalLayout ) { return detail::MakeValueSizes::Apply(physicalLayout); } [[nodiscard]] static std::array MakeResidualSizes( const StellarEquilibriumLayout &physicalLayout ) { return detail::MakeResidualSizes::Apply(physicalLayout); } PreparedVariadicStellarEquilibriumOperator( fem::FEM &finiteElements, const mapping::DomainMapper &domainMapper, std::shared_ptr model, PhysicalCoreOwner physical ) : mfem::Operator( Layout(MakeValueSizes(physical->GetLayout()), MakeResidualSizes(physical->GetLayout())) .residual_offsets() .Last(), Layout(MakeValueSizes(physical->GetLayout()), MakeResidualSizes(physical->GetLayout())) .value_offsets() .Last() ), m_model(std::move(model)), m_physical(std::move(physical)), m_specifications(finiteElements, domainMapper, *m_physical, *m_model), m_manifest( MakeValueSizes(m_physical->GetLayout()), MakeResidualSizes(m_physical->GetLayout()), *m_model, CoreRuntime::SurfaceEquationCount(*m_physical) ), m_physicalState(m_physical->Width()), m_physicalDirection(m_physical->Width()), m_physicalAction(m_physical->Height()) { static_assert(rotationProviderCount <= 1, "A stellar root cannot have two rigid-rotation providers."); MFEM_VERIFY( Width() == m_manifest.layout().value_offsets().Last() && Height() == m_manifest.layout().residual_offsets().Last(), "The variadic stellar root has inconsistent compiled dimensions." ); } [[nodiscard]] Report PrepareWithControls( const mfem::Vector &state, detail::StellarEquilibriumControlContext controls ) { MFEM_VERIFY(state.Size() == Width(), "The variadic stellar root received a wrong-sized state."); const auto stateView = m_manifest.stateView(state); m_isPrepared = false; m_specifications.ReadPhysicalControls(stateView, controls); MFEM_VERIFY( controls.rotationProviderCount == 1 && controls.rotation.has_value(), "Exactly one rigid-rotation value must be supplied to the stellar physics core." ); GatherPhysicalValues(state, m_physicalState, typename PhysicalForm::value_blocks{}); Report report; report.generatedPhysicalControl = controls.generatedPhysicalControl; report.physical = m_physical->Prepare(m_physicalState, controls.dependencies, *controls.rotation); m_specifications.template PrepareAfterPhysical<0>( stateView, controls.dependencies, *m_physical, report.specifications ); AssembleResidual(); report.assembledResidual = true; m_isPrepared = true; return report; } void AssembleResidual() { mfem::Vector physicalResidual; m_physical->BuildResidual(physicalResidual); m_cachedResidual.SetSize(Height()); m_cachedResidual = 0.0; ScatterPhysicalResiduals( physicalResidual, m_cachedResidual, typename PhysicalForm::residual_blocks{} ); const auto residualView = m_manifest.residualView(m_cachedResidual); m_specifications.AddResidual(residualView, *m_physical); } void VerifyPrepared() const { MFEM_VERIFY(IsPrepared(), "The variadic stellar-equilibrium root must be prepared before application."); } std::shared_ptr m_model; PhysicalCoreOwner m_physical; SpecificationSlots m_specifications; Manifest m_manifest; mfem::Vector m_physicalState; mutable mfem::Vector m_physicalDirection; mutable mfem::Vector m_physicalAction; mfem::Vector m_cachedResidual; bool m_isPrepared{false}; }; } // namespace mean_field::operators export namespace mean_field::stellar { template using DensityVolumeIntegralContext = operators::DensityVolumeIntegralContext; } // namespace mean_field::stellar