module; #include #include #include #include #include #include #include #include #include #include #include #include export module mean_field:preconditioning.specification_border; export import :operators.stellar_equilibrium_compiler; export import :preconditioning.stellar_equilibrium; export import :preconditioning.stellar_structure; export namespace mean_field::preconditioning { namespace detail { template struct ToPreconditionerCouplings; template struct ToPreconditionerCouplings> { using Type = utils::blocks::type_list...>; }; template struct CollectUniqueCouplings; template struct CollectUniqueCouplings, Accumulated> { using Type = Accumulated; }; template struct CollectUniqueCouplings< utils::blocks::type_list, Accumulated> { using Type = typename CollectUniqueCouplings< utils::blocks::type_list, AppendUniqueT>::Type; }; template using UniqueConcatenatedCouplingsT = typename CollectUniqueCouplings< ConcatenateT, utils::blocks::type_list<>>::Type; } // namespace detail /* * Preconditioner topology is a projection of the authoritative operator * compilation. It therefore cannot silently drift from the residual or * Jacobian when a new specification is added. */ template struct SpecificationBorderContribution { private: using OperatorCompilation = operators::StellarEquilibriumSpecificationCompilation; public: using CorrectionBlocks = typename OperatorCompilation::GeneratedCorrectionBlocks; using ResidualBlocks = typename OperatorCompilation::GeneratedResidualBlocks; using RequiredCouplings = typename detail::ToPreconditionerCouplings< typename OperatorCompilation::IncidentJacobianCouplings>::Type; static constexpr bool registered = operators::stellarEquilibriumSpecificationCompilationComplete; }; namespace detail { template inline constexpr std::size_t generatedBorderValueArity = models::specificationDescriptor().generatedValueArity; template inline constexpr std::size_t generatedBorderResidualArity = models::specificationDescriptor().generatedResidualArity; template inline constexpr bool specificationGeneratesBorder = generatedBorderValueArity != 0 || generatedBorderResidualArity != 0; template inline constexpr bool specificationBorderContributionIsComplete = !specificationGeneratesBorder || (SpecificationBorderContribution::registered && generatedBorderValueArity == generatedBorderResidualArity && SpecificationBorderContribution::CorrectionBlocks::size == 1 && SpecificationBorderContribution::ResidualBlocks::size == 1); template struct CompiledSpecificationBorder; template struct CompiledSpecificationBorder> { static_assert( (specificationBorderContributionIsComplete && ...), "Every specification-generated border requires a registered preconditioning contribution with " "balanced value and residual arity." ); using SpecificationTypes = models::detail::SpecificationSetStorage; using CorrectionBlocks = preconditioning::detail::ConcatenateT< typename SpecificationBorderContribution::CorrectionBlocks...>; using ResidualBlocks = preconditioning::detail::ConcatenateT< typename SpecificationBorderContribution::ResidualBlocks...>; using RequiredCouplings = preconditioning::detail::UniqueConcatenatedCouplingsT< typename SpecificationBorderContribution::RequiredCouplings...>; static constexpr std::size_t valueArity = (std::size_t{0} + ... + generatedBorderValueArity); static constexpr std::size_t residualArity = (std::size_t{0} + ... + generatedBorderResidualArity); static constexpr std::size_t specificationCount = (std::size_t{0} + ... + (specificationGeneratesBorder ? 1U : 0U)); static constexpr bool symbolicallySquare = valueArity == residualArity; }; template struct SpecificationBorderValueOffset; template struct SpecificationBorderValueOffset> { static constexpr std::size_t value = [] { if constexpr (std::same_as) { return std::size_t{0}; } else { static_assert(sizeof...(Tail) > 0, "The requested border specification is not in the model."); return generatedBorderValueArity + SpecificationBorderValueOffset< Query, models::detail::SpecificationSetStorage>::value; } }(); }; template struct SpecificationBorderResidualOffset; template struct SpecificationBorderResidualOffset> { static constexpr std::size_t value = [] { if constexpr (std::same_as) { return std::size_t{0}; } else { static_assert(sizeof...(Tail) > 0, "The requested border specification is not in the model."); return generatedBorderResidualArity + SpecificationBorderResidualOffset< Query, models::detail::SpecificationSetStorage>::value; } }(); }; } // namespace detail template using CompiledSpecificationBorderFor = detail::CompiledSpecificationBorder::SpecificationTypes>; template inline constexpr std::size_t specificationBorderValueOffset = detail:: SpecificationBorderValueOffset::SpecificationTypes>::value; template inline constexpr std::size_t specificationBorderResidualOffset = detail::SpecificationBorderResidualOffset< Specification, typename std::remove_cvref_t::SpecificationTypes>::value; using SpecificationBorderCharacteristics = OperatorCharacteristics< OperatorCategory::dense_border, OperatorValueStructure::block, OperatorSymmetry::nonsymmetric, OperatorDefiniteness::indefinite, OperatorRepresentation::assembled_dense, OperatorDistribution::local>; using BorderedStellarStructureCharacteristics = OperatorCharacteristics< OperatorCategory::mixed, OperatorValueStructure::block, OperatorSymmetry::nonsymmetric, OperatorDefiniteness::unspecified, OperatorRepresentation::matrix_free, OperatorDistribution::distributed_true_dof, OperatorFESpace::product>; namespace backend { template struct BorderedStellarStructure final { using StructureBackendType = StructureBackend; using BorderBackendType = BorderBackend; }; template struct Traits> { static constexpr bool registered = true; static constexpr ApplicationContract applicationContract = ::mean_field::preconditioning::backend::applicationContract == ApplicationContract::stationary_linear && ::mean_field::preconditioning::backend::applicationContract == ApplicationContract::stationary_linear ? ApplicationContract::stationary_linear : ApplicationContract::flexible; static constexpr bool supportsSerialExecution = Traits::supportsSerialExecution; static constexpr bool supportsDistributedExecution = Traits::supportsDistributedExecution && Traits::supportsSerialExecution; static constexpr SymmetryRequirement symmetryRequirement = SymmetryRequirement::none; static constexpr NullspaceRequirement nullspaceRequirement = NullspaceRequirement::constant_mode_supported; static constexpr SurrogateRequirement surrogateRequirement = SurrogateRequirement::assembled_sparse; static constexpr bool requiresAssembledSparseSurrogate = Traits::requiresAssembledSparseSurrogate; using PreparationDependencies = preconditioning::PreparationDependencies< PreparationDependency::discretization, PreparationDependency::geometry, PreparationDependency::equation_of_state, PreparationDependency::linearization>; template static constexpr bool supports = Characteristics::category == OperatorCategory::mixed && Characteristics::valueStructure == OperatorValueStructure::block && Characteristics::symmetry == OperatorSymmetry::nonsymmetric && Characteristics::representation == OperatorRepresentation::matrix_free && Characteristics::distribution == OperatorDistribution::distributed_true_dof && Characteristics::finiteElementSpace == OperatorFESpace::product; }; } // namespace backend template < PreconditionerComponent StructureComponentT, model::StellarModelType ModelT, typename FormT, typename JacobianFormT> requires utils::blocks::valid_jacobian_form class SpecificationBorderBlock final { private: using CompiledBorder = CompiledSpecificationBorderFor; public: using StructureComponent = StructureComponentT; using Model = ModelT; using Form = FormT; using JacobianForm = JacobianFormT; using CorrectionBlocks = preconditioning::detail:: ConcatenateT; using ResidualBlocks = preconditioning::detail:: ConcatenateT; using RequiredCouplings = preconditioning::detail::UniqueConcatenatedCouplingsT< typename StructureComponent::RequiredCouplings, typename CompiledBorder::RequiredCouplings>; using OperatorDescription = BorderedStellarStructureCharacteristics; using BackendType = backend::BorderedStellarStructure; using PreparationDependencies = typename backend::Traits::PreparationDependencies; static constexpr std::size_t borderValueArity = CompiledBorder::valueArity; static constexpr std::size_t borderResidualArity = CompiledBorder::residualArity; constexpr explicit SpecificationBorderBlock( StructureComponent structureComponent, backend::DenseDirect borderBackend = {} ) : m_structureComponent(std::move(structureComponent)), m_borderBackend(std::move(borderBackend)) { static_assert(CompiledBorder::symbolicallySquare); } [[nodiscard]] constexpr const StructureComponent &structureComponent() const noexcept { return m_structureComponent; } [[nodiscard]] constexpr const backend::DenseDirect &borderBackend() const noexcept { return m_borderBackend; } private: StructureComponent m_structureComponent; backend::DenseDirect m_borderBackend; }; template struct IsSpecificationBorderBlock : std::false_type { }; template < PreconditionerComponent StructureComponent, model::StellarModelType Model, typename Form, typename JacobianForm> struct IsSpecificationBorderBlock> : std::true_type { }; template concept SpecificationBorderBlockType = IsSpecificationBorderBlock>::value; struct StellarStructureDirectionView final { const mfem::Vector &density; const mfem::Vector &surface; const mfem::Vector &enthalpy; const mfem::Vector &gravityGradient; const mfem::Vector &gravityPotential; }; struct StellarStructureActionView final { mfem::Vector &density; mfem::Vector &surface; mfem::Vector &enthalpy; mfem::Vector &gravityGradient; mfem::Vector &gravityPotential; }; namespace detail { template struct SingleSpecificationBorderBlock; template struct SingleSpecificationBorderBlock> { using Type = Block; }; template using GeneratedSpecificationValueBlock = typename SingleSpecificationBorderBlock< typename SpecificationBorderContribution::CorrectionBlocks>::Type; template using GeneratedSpecificationResidualBlock = typename SingleSpecificationBorderBlock< typename SpecificationBorderContribution::ResidualBlocks>::Type; template struct GeneratedBorderValueOwner { static constexpr bool available = false; }; template requires requires { typename Generated::SpecificationType; } struct GeneratedBorderValueOwner> { using Specification = typename Generated::SpecificationType; static constexpr bool available = models::ModelSpecification; }; template struct GeneratedBorderResidualOwner { static constexpr bool available = false; }; template requires requires { typename Generated::SpecificationType; } struct GeneratedBorderResidualOwner> { using Specification = typename Generated::SpecificationType; static constexpr bool available = models::ModelSpecification; }; template inline constexpr bool isGeneratedBorderValue = GeneratedBorderValueOwner>::available; template inline constexpr bool isGeneratedBorderResidual = GeneratedBorderResidualOwner>::available; template inline constexpr bool isStellarStructureValue = std::same_as, utils::blocks::density::mass::value> || std::same_as, utils::blocks::surface_deformation::parameters::value> || std::same_as, utils::blocks::enthalpy::specific::value> || std::same_as, utils::blocks::gravity::gradient::value> || std::same_as, utils::blocks::gravity::poisson::value>; template inline constexpr bool isStellarStructureResidual = std::same_as, utils::blocks::density::mass::residual> || std::same_as< std::remove_cvref_t, utils::blocks::surface_deformation::shape_equilibrium::residual> || std::same_as, utils::blocks::enthalpy::specific::residual> || std::same_as, utils::blocks::gravity::gradient::residual> || std::same_as, utils::blocks::gravity::poisson::residual>; enum class SpecificationBorderOperation { structure_to_border, border_to_structure, border_to_border }; template [[nodiscard]] consteval bool directionParticipatesInCoupling() { if constexpr (!std::same_as, typename Coupling::Correction>) { return false; } else if constexpr (Operation == SpecificationBorderOperation::structure_to_border) { return isStellarStructureValue && isGeneratedBorderResidual; } else if constexpr (Operation == SpecificationBorderOperation::border_to_structure) { return isGeneratedBorderValue && isStellarStructureResidual; } else { return isGeneratedBorderValue && isGeneratedBorderResidual; } } template [[nodiscard]] consteval bool actionParticipatesInCoupling() { if constexpr (!std::same_as, typename Coupling::Residual>) { return false; } else if constexpr (Operation == SpecificationBorderOperation::structure_to_border) { return isGeneratedBorderResidual && isStellarStructureValue; } else if constexpr (Operation == SpecificationBorderOperation::border_to_structure) { return isStellarStructureResidual && isGeneratedBorderValue; } else { return isGeneratedBorderResidual && isGeneratedBorderValue; } } template [[nodiscard]] const mfem::Vector &structureDirectionBlock( const StellarStructureDirectionView &view ) { using BlockType = std::remove_cvref_t; static_assert(isStellarStructureValue); if constexpr (std::same_as) { return view.density; } else if constexpr ( std::same_as) { return view.surface; } else if constexpr (std::same_as) { return view.enthalpy; } else if constexpr (std::same_as) { return view.gravityGradient; } else { return view.gravityPotential; } } template [[nodiscard]] mfem::Vector &structureActionBlock(StellarStructureActionView &view) { using BlockType = std::remove_cvref_t; static_assert(isStellarStructureResidual); if constexpr (std::same_as) { return view.density; } else if constexpr ( std::same_as) { return view.surface; } else if constexpr (std::same_as) { return view.enthalpy; } else if constexpr (std::same_as) { return view.gravityGradient; } else { return view.gravityPotential; } } template concept SpecificationBelongsToProblem = models::ModelSpecification> && equilibrium::DiscretizedStellarEquilibriumProblem> && std::remove_cvref_t::ModelType::template containsSpecification< std::remove_cvref_t>; } // namespace detail /* A single callback sees one compiled Jacobian edge, never the union of all * legal sources and rows for its specification. Binding both block types * into these two tiny views prevents a callback from reading one direction * block while claiming that its contribution differentiates another. */ template < models::ModelSpecification Specification, equilibrium::DiscretizedStellarEquilibriumProblem Problem, detail::SpecificationBorderOperation Operation, typename ResidualBlock, typename ValueBlock> requires detail::SpecificationBelongsToProblem class SpecificationBorderCouplingDirectionView final { private: using ProblemType = std::remove_cvref_t; using Model = typename ProblemType::ModelType; using CouplingType = Coupling< std::remove_cvref_t, std::remove_cvref_t>; using Couplings = typename SpecificationBorderContribution::RequiredCouplings; static constexpr bool permitted = utils::blocks::contains_type_v< CouplingType, Couplings> && detail::directionParticipatesInCoupling< std::remove_cvref_t, CouplingType, Operation>() && detail::actionParticipatesInCoupling< std::remove_cvref_t, CouplingType, Operation>(); public: explicit SpecificationBorderCouplingDirectionView( const StellarStructureDirectionView &structure ) noexcept requires( permitted && Operation == detail::SpecificationBorderOperation::structure_to_border ) : m_structure(std::addressof(structure)) { } explicit SpecificationBorderCouplingDirectionView( const mfem::Vector &border ) noexcept requires( permitted && Operation != detail::SpecificationBorderOperation::structure_to_border ) : m_border(std::addressof(border)) { } [[nodiscard]] decltype(auto) values() const { if constexpr (detail::isStellarStructureValue) { return detail::structureDirectionBlock(*m_structure); } else { using Owner = typename detail::GeneratedBorderValueOwner::Specification; constexpr int offset = static_cast( specificationBorderValueOffset ); return operators::ReadOnlyVectorView{ *m_border, offset, ValueBlock::static_block_size }; } } template requires requires { typename std::remove_cvref_t::value; } && std::same_as< ValueBlock, typename std::remove_cvref_t::value> [[nodiscard]] decltype(auto) block(const Term &) const { return values(); } [[nodiscard]] int Size() const { return values().Size(); } [[nodiscard]] int size() const { return Size(); } [[nodiscard]] double operator()(const int index) const { return values()(index); } [[nodiscard]] decltype(auto) density() const requires std::same_as { return values(); } [[nodiscard]] decltype(auto) surfaceShape() const requires std::same_as< ValueBlock, utils::blocks::surface_deformation::parameters::value> { return values(); } [[nodiscard]] decltype(auto) specificEnthalpy() const requires std::same_as { return values(); } [[nodiscard]] decltype(auto) gravityGradient() const requires std::same_as { return values(); } [[nodiscard]] decltype(auto) gravitationalPotential() const requires std::same_as { return values(); } [[nodiscard]] decltype(auto) gravityPotential() const requires std::same_as { return gravitationalPotential(); } template requires std::same_as< ValueBlock, detail::GeneratedSpecificationValueBlock> [[nodiscard]] decltype(auto) generatedCoordinate() const { return values(); } private: const StellarStructureDirectionView *m_structure{nullptr}; const mfem::Vector *m_border{nullptr}; }; template < models::ModelSpecification Specification, equilibrium::DiscretizedStellarEquilibriumProblem Problem, detail::SpecificationBorderOperation Operation, typename ResidualBlock, typename ValueBlock> requires detail::SpecificationBelongsToProblem class SpecificationBorderCouplingRowAction final { private: using ProblemType = std::remove_cvref_t; using Model = typename ProblemType::ModelType; using CouplingType = Coupling< std::remove_cvref_t, std::remove_cvref_t>; using Couplings = typename SpecificationBorderContribution::RequiredCouplings; static constexpr bool permitted = utils::blocks::contains_type_v && detail::directionParticipatesInCoupling< std::remove_cvref_t, CouplingType, Operation>() && detail::actionParticipatesInCoupling< std::remove_cvref_t, CouplingType, Operation>(); public: SpecificationBorderCouplingRowAction( StellarStructureActionView &structure, const field::FieldBoundaryDofMap &surfaceRows ) noexcept requires( permitted && Operation == detail::SpecificationBorderOperation::border_to_structure ) : m_structure(std::addressof(structure)), m_surfaceRows(std::addressof(surfaceRows)) { } explicit SpecificationBorderCouplingRowAction(mfem::Vector &border) noexcept requires( permitted && Operation != detail::SpecificationBorderOperation::border_to_structure ) : m_border(std::addressof(border)) { } [[nodiscard]] stellar::ContributionAdded add( const double contribution ) { RequireUnused(); decltype(auto) target = block(); target += contribution; RestoreReplacedRows(target, contribution); SynchronizeGeneratedBlock(target); m_addCount = 1; return {}; } [[nodiscard]] stellar::ContributionAdded add( const mfem::Vector &contribution ) { RequireUnused(); decltype(auto) target = block(); if (target.Size() != contribution.Size()) { throw std::invalid_argument( "A specification-border physics contribution has the wrong block size." ); } target += contribution; RestoreReplacedRows(target, contribution); SynchronizeGeneratedBlock(target); 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 specification-border provider returned ContributionAdded without adding exactly once." ); } } else if (m_addCount != 0) { throw std::logic_error( "A specification-border provider returned StructuralZero after adding to its row." ); } } private: void RequireUnused() const { if (m_addCount != 0) { throw std::logic_error( "A compiler-enumerated specification-border edge may be assembled only once." ); } } [[nodiscard]] decltype(auto) block() const { if constexpr (detail::isStellarStructureResidual) { return detail::structureActionBlock(*m_structure); } else { using Owner = typename detail::GeneratedBorderResidualOwner::Specification; constexpr int offset = static_cast( specificationBorderResidualOffset ); return mfem::Vector( m_border->GetData() + offset, ResidualBlock::static_block_size ); } } void RestoreReplacedRows( mfem::Vector &target, const double contribution ) const { if constexpr ( Operation == detail::SpecificationBorderOperation::border_to_structure && std::same_as) { for (const int row : m_surfaceRows->reduced_dofs()) { target(row) -= contribution; } } } void RestoreReplacedRows( mfem::Vector &target, const mfem::Vector &contribution ) const { if constexpr ( Operation == detail::SpecificationBorderOperation::border_to_structure && std::same_as) { for (const int row : m_surfaceRows->reduced_dofs()) { target(row) -= contribution(row); } } } void SynchronizeGeneratedBlock(mfem::Vector &target) const { if constexpr (detail::isGeneratedBorderResidual) { target.SyncAliasMemory(*m_border); } } StellarStructureActionView *m_structure{nullptr}; mfem::Vector *m_border{nullptr}; const field::FieldBoundaryDofMap *m_surfaceRows{nullptr}; int m_addCount{0}; }; /* This is the only object delivered to extension physics for an operation. * A callback is invoked only after an exact compiled (row, source) edge has * been selected. Its direction argument contains that source alone and * its row argument supports additive updates to that row alone. */ template < models::ModelSpecification Specification, equilibrium::DiscretizedStellarEquilibriumProblem Problem, detail::SpecificationBorderOperation Operation> requires detail::SpecificationBelongsToProblem class SpecificationBorderActionView final { private: using Couplings = typename SpecificationBorderContribution::RequiredCouplings; template using Direction = SpecificationBorderCouplingDirectionView< Specification, Problem, Operation, std::remove_cvref_t, std::remove_cvref_t>; template using RowAction = SpecificationBorderCouplingRowAction< Specification, Problem, Operation, std::remove_cvref_t, std::remove_cvref_t>; template static constexpr bool permitsCoupling = utils::blocks::contains_type_v< Coupling< std::remove_cvref_t, std::remove_cvref_t>, Couplings> && detail::directionParticipatesInCoupling< std::remove_cvref_t, Coupling< std::remove_cvref_t, std::remove_cvref_t>, Operation>() && detail::actionParticipatesInCoupling< std::remove_cvref_t, Coupling< std::remove_cvref_t, std::remove_cvref_t>, Operation>(); template static constexpr bool completesCoupling = requires( Callback &&callback, const Direction &direction, RowAction &row ) { { std::forward(callback)(direction, row) } -> stellar::ContributionResult; }; public: SpecificationBorderActionView( const StellarStructureDirectionView &direction, mfem::Vector &action ) noexcept requires(Operation == detail::SpecificationBorderOperation::structure_to_border) : m_structureDirection(std::addressof(direction)), m_borderAction(std::addressof(action)) { } SpecificationBorderActionView( const mfem::Vector &direction, StellarStructureActionView &action, const field::FieldBoundaryDofMap &surfaceRows ) noexcept requires(Operation == detail::SpecificationBorderOperation::border_to_structure) : m_borderDirection(std::addressof(direction)), m_structureAction(std::addressof(action)), m_surfaceRows(std::addressof(surfaceRows)) { } SpecificationBorderActionView( const mfem::Vector &direction, mfem::Vector &action ) noexcept requires(Operation == detail::SpecificationBorderOperation::border_to_border) : m_borderDirection(std::addressof(direction)), m_borderAction(std::addressof(action)) { } template requires requires { typename std::remove_cvref_t::residual; typename std::remove_cvref_t::value; } && permitsCoupling< typename std::remove_cvref_t::residual, typename std::remove_cvref_t::value> && completesCoupling< typename std::remove_cvref_t::residual, typename std::remove_cvref_t::value, Callback> void add( const ResidualTerm &, const ValueTerm &, Callback &&callback ) const { using Residual = typename std::remove_cvref_t::residual; using Value = typename std::remove_cvref_t::value; const Direction direction = makeDirection(); RowAction row = makeRowAction(); decltype(auto) result = std::forward(callback)(direction, row); row.Verify(result); } template void addDensityFrom(const ValueTerm &valueTerm, Callback &&callback) const requires requires { typename std::remove_cvref_t::value; } && permitsCoupling< utils::blocks::density::mass::residual, typename std::remove_cvref_t::value> && completesCoupling< utils::blocks::density::mass::residual, typename std::remove_cvref_t::value, Callback> { add( utils::blocks::density_field.mass_term, valueTerm, std::forward(callback) ); } template void addSurfaceShapeFrom(const ValueTerm &valueTerm, Callback &&callback) const requires requires { typename std::remove_cvref_t::value; } && permitsCoupling< utils::blocks::surface_deformation::shape_equilibrium::residual, typename std::remove_cvref_t::value> && completesCoupling< utils::blocks::surface_deformation::shape_equilibrium::residual, typename std::remove_cvref_t::value, Callback> { add( utils::blocks::surface_deformation_field.shape_equilibrium_term, valueTerm, std::forward(callback) ); } template void addSpecificEnthalpyFrom(const ValueTerm &valueTerm, Callback &&callback) const requires requires { typename std::remove_cvref_t::value; } && permitsCoupling< utils::blocks::enthalpy::specific::residual, typename std::remove_cvref_t::value> && completesCoupling< utils::blocks::enthalpy::specific::residual, typename std::remove_cvref_t::value, Callback> { add( utils::blocks::enthalpy_field.specific_term, valueTerm, std::forward(callback) ); } template void addGravityGradientFrom(const ValueTerm &valueTerm, Callback &&callback) const requires requires { typename std::remove_cvref_t::value; } && permitsCoupling< utils::blocks::gravity::gradient::residual, typename std::remove_cvref_t::value> && completesCoupling< utils::blocks::gravity::gradient::residual, typename std::remove_cvref_t::value, Callback> { add( utils::blocks::gravity_field.gradient_term, valueTerm, std::forward(callback) ); } template void addGravityPotentialFrom(const ValueTerm &valueTerm, Callback &&callback) const requires requires { typename std::remove_cvref_t::value; } && permitsCoupling< utils::blocks::gravity::poisson::residual, typename std::remove_cvref_t::value> && completesCoupling< utils::blocks::gravity::poisson::residual, typename std::remove_cvref_t::value, Callback> { add( utils::blocks::gravity_field.poisson_term, valueTerm, std::forward(callback) ); } template void addConstraintResidualFrom( const ValueTerm &valueTerm, Callback &&callback ) const requires requires { typename std::remove_cvref_t::value; } && permitsCoupling< detail::GeneratedSpecificationResidualBlock, typename std::remove_cvref_t::value> && completesCoupling< detail::GeneratedSpecificationResidualBlock, typename std::remove_cvref_t::value, Callback> { struct GeneratedResidualTerm final { using residual = detail::GeneratedSpecificationResidualBlock; }; add(GeneratedResidualTerm{}, valueTerm, std::forward(callback)); } private: template [[nodiscard]] Direction makeDirection() const { if constexpr (Operation == detail::SpecificationBorderOperation::structure_to_border) { return Direction{*m_structureDirection}; } else { return Direction{*m_borderDirection}; } } template [[nodiscard]] RowAction makeRowAction() const { if constexpr (Operation == detail::SpecificationBorderOperation::border_to_structure) { return RowAction{*m_structureAction, *m_surfaceRows}; } else { return RowAction{*m_borderAction}; } } const StellarStructureDirectionView *m_structureDirection{nullptr}; const mfem::Vector *m_borderDirection{nullptr}; StellarStructureActionView *m_structureAction{nullptr}; mfem::Vector *m_borderAction{nullptr}; const field::FieldBoundaryDofMap *m_surfaceRows{nullptr}; }; template using SpecificationStructureToBorderActionView = SpecificationBorderActionView< Specification, Problem, detail::SpecificationBorderOperation::structure_to_border>; template using SpecificationBorderToStructureActionView = SpecificationBorderActionView< Specification, Problem, detail::SpecificationBorderOperation::border_to_structure>; template using SpecificationBorderToBorderActionView = SpecificationBorderActionView< Specification, Problem, detail::SpecificationBorderOperation::border_to_border>; /** The exact prepared equilibrium-physics object owned by one * specification slot in a discretized problem. Physics-facing border * actions may snapshot coefficients from this object, but do not receive * the enclosing Problem or its backend facilities. */ template requires detail::SpecificationBelongsToProblem using PreparedSpecificationEquilibriumPhysicsT = std::remove_cvref_t &>() .GetPreparedOperator() .template GetPreparedContribution>() )>; namespace detail { template struct PhysicsFacingBorderResidualTerm final { using residual = ResidualBlock; }; template struct PhysicsFacingBorderValueTerm final { using value = ValueBlock; }; template struct PhysicsDerivativeTraits; template struct PhysicsDerivativeTraits< models::stellar::Derivative> final { using EquationTag = Equation; using StateTag = State; }; template < models::ModelSpecification Specification, typename Problem, SpecificationBorderOperation Operation, typename Derivative> struct SpecificationBorderDerivative final { using Traits = PhysicsDerivativeTraits; using ResidualBlock = typename operators::detail::StellarDependencyBlock< Specification, typename Traits::EquationTag>::Type; using ValueBlock = typename operators::detail::StellarDependencyBlock< Specification, typename Traits::StateTag>::Type; using CouplingType = Coupling; using Direction = SpecificationBorderCouplingDirectionView< Specification, Problem, Operation, ResidualBlock, ValueBlock>; using Row = SpecificationBorderCouplingRowAction< Specification, Problem, Operation, ResidualBlock, ValueBlock>; static constexpr bool participates = utils::blocks::contains_type_v< CouplingType, typename SpecificationBorderContribution< Specification>::RequiredCouplings> && directionParticipatesInCoupling< ValueBlock, CouplingType, Operation>() && actionParticipatesInCoupling< ResidualBlock, CouplingType, Operation>(); }; template < typename Physics, models::ModelSpecification Specification, typename Problem, SpecificationBorderOperation Operation, typename Derivatives> struct ExactSpecificationBorderProviderSet; template < typename Physics, models::ModelSpecification Specification, typename Problem, SpecificationBorderOperation Operation, typename... Derivatives> struct ExactSpecificationBorderProviderSet< Physics, Specification, Problem, Operation, utils::blocks::type_list> final { private: template [[nodiscard]] static consteval bool ProviderIsComplete() { using Edge = SpecificationBorderDerivative< Specification, Problem, Operation, Derivative>; if constexpr (!Edge::participates) { return true; } else { return requires( const Physics &physics, const typename Edge::Direction &direction, typename Edge::Row &row ) { { physics.ApplyJacobianAction( Derivative{}, direction, row ) } -> stellar::ContributionResult; }; } } public: static constexpr bool complete = (ProviderIsComplete() && ...); static void Apply( const Physics &physics, SpecificationBorderActionView< Specification, Problem, Operation> action ) requires complete { (ApplyOne(physics, action), ...); } private: template static void ApplyOne( const Physics &physics, const SpecificationBorderActionView< Specification, Problem, Operation> &action ) { using Edge = SpecificationBorderDerivative< Specification, Problem, Operation, Derivative>; if constexpr (Edge::participates) { action.add( PhysicsFacingBorderResidualTerm< typename Edge::ResidualBlock>{}, PhysicsFacingBorderValueTerm< typename Edge::ValueBlock>{}, [&](const auto &direction, auto &row) -> decltype(auto) { return physics.ApplyJacobianAction( Derivative{}, direction, row ); } ); } } }; template < typename Physics, models::ModelSpecification Specification, equilibrium::DiscretizedStellarEquilibriumProblem Problem> struct ExactSpecificationBorderProviders final { using Model = typename Problem::ModelType; using Derivatives = typename operators::StellarEquilibriumContributionTopology< Specification, Model>::Derivatives; using StructureToBorder = ExactSpecificationBorderProviderSet< Physics, Specification, Problem, SpecificationBorderOperation::structure_to_border, Derivatives>; using BorderToStructure = ExactSpecificationBorderProviderSet< Physics, Specification, Problem, SpecificationBorderOperation::border_to_structure, Derivatives>; using BorderToBorder = ExactSpecificationBorderProviderSet< Physics, Specification, Problem, SpecificationBorderOperation::border_to_border, Derivatives>; static constexpr bool complete = StructureToBorder::complete && BorderToStructure::complete && BorderToBorder::complete; }; } // namespace detail /* * Public protocol check for one prepared numerical constraint action. * The topology itself is inferred from ModelDefinition; this concept makes * the remaining physics implementation fail at the contribution boundary * instead of deep inside the assembled preconditioner. */ template concept PreparedSpecificationBorderActionFor = std::constructible_from && std::move_constructible && requires( const Candidate &action, const StellarStructureDirectionView &structureDirection, const mfem::Vector &borderDirection, StellarStructureActionView structureAction, mfem::Vector &borderAction ) { { Candidate::registered } -> std::convertible_to; requires Candidate::registered; action.ApplyStructureToBorder(structureDirection, borderAction); action.ApplyBorderToStructure(borderDirection, structureAction); action.ApplyBorderToBorder(borderDirection, borderAction); }; /* * Physics-facing opt-in for a new generated constraint. The specification * may name this wrapper as * * using SpecificationBorderPhysics = * preconditioning::SpecificationBorderPhysics; * * where MyPreparedAction implements one overload for each border-incident * derivative inferred from Reads/Changes, for example * * auto ApplyJacobianAction( * stellar::Derivative, * const auto &direction, * auto &row) const { * return row.add(coefficient * direction.specificEnthalpy()(0)); * } * * The adapter enumerates the exact inferred set. Missing overloads fail at * compile time; identically absent terms return stellar::zeroDerivative. * Its constructor receives only the exact specification's prepared * equilibrium physics, never the full Problem. The wrapper owns the * generic construction plumbing; extension authors neither specialize a * detail:: class nor reproduce pack traversal. * LocalSpecificationBorderPhysics below removes even the class-template * spelling for the common concrete-class case. */ template concept CompleteSpecificationBorderPhysicsProvider = models::ModelSpecification> && detail::SpecificationBelongsToProblem && equilibrium::DiscretizedStellarEquilibriumProblem< std::remove_cvref_t> && detail::ExactSpecificationBorderProviders< std::remove_cvref_t, std::remove_cvref_t, std::remove_cvref_t>::complete; template concept PreparedSpecificationBorderPhysicsActionFor = CompleteSpecificationBorderPhysicsProvider< Candidate, Specification, Problem> && std::constructible_from< Candidate, const PreparedSpecificationEquilibriumPhysicsT< std::remove_cvref_t, std::remove_cvref_t> &> && std::move_constructible; template