module; #include #include #include #include #include #include #include #include #include #include #include #include export module mean_field:operators.root_manifest; export import :model.compiled_fixed_mass; export import :model.compiled_fixed_angular_momentum; export import :model.compiled_fixed_central_density; export import :model.specifications; export import :operators.stellar_equilibrium_compiler; export import :utils.blocks; export namespace mean_field::operators { enum class RootBlockKind { value, residual }; enum class RootBlockProvenance { physical_operator, model_specification }; enum class RootRowInjection { physical_equation, append_global, replace_carrier_rows }; enum class RootColumnPolicy { physical_state, existing_physical_multiplier, generated_physical_coordinate, solver_border, no_column }; enum class RootScalePolicy { unscaled, target_relative }; struct RootBlockDescriptor final { std::string_view stableId; std::string_view symbol; RootBlockKind kind; RootBlockProvenance provenance; std::string_view source; RootRowInjection rowInjection; RootColumnPolicy columnPolicy; RootScalePolicy scalePolicy; int canonicalIndex; int offset; int size; double scale; }; struct RootRowReplacementDescriptor final { std::string_view stableId; std::string_view sourceSpecification; models::SpecificationRole role; int carrierResidualBlock; int replacedRowCount; }; struct RootConstraintDescriptor final { std::string_view stableId; models::SpecificationRole role; RootRowInjection rowInjection; RootColumnPolicy columnPolicy; int valueBlock; int residualBlock; int rowArity; int columnArity; double target; std::optional carrierTarget; std::string_view targetUnits; std::string_view residualUnits; double residualScale; }; struct RootConstraintReport final { RootConstraintDescriptor descriptor; double achieved; double dimensionalResidual; double scaledResidual; }; namespace detail { template concept HasUniqueModelSpecificationRole = requires { typename std::remove_cvref_t::SpecificationTypes; requires models::HasUniqueSpecificationForRole< Role, typename std::remove_cvref_t::SpecificationTypes>; }; template requires HasUniqueModelSpecificationRole using ModelSpecificationForRole = models::SpecificationForRoleT::SpecificationTypes>; template concept AccessibleModelSpecificationRole = HasUniqueModelSpecificationRole && (requires(const std::remove_cvref_t &model) { { model.template specificationForRole() } -> std::same_as &>; } || requires(const std::remove_cvref_t &model) { { model.template specification>() } -> std::same_as &>; }); /* * Model frontends may expose a convenient specificationForRole() * accessor, while the lightweight compile-time model deliberately * exposes only specification(). Keep manifest * compilation independent of that presentation choice. */ template < models::SpecificationRole Role, typename Model> requires AccessibleModelSpecificationRole< Role, Model> [[nodiscard]] const ModelSpecificationForRole< Role, Model> & modelSpecificationForRole(const Model &model) { if constexpr (requires { { model.template specificationForRole() } -> std::same_as &>; }) { return model.template specificationForRole(); } else { using Specification = ModelSpecificationForRole; return model.template specification(); } } struct ManifestTargetData final { double target; std::optional carrierTarget; double residualScale; }; template struct ManifestTargetAdapter { static constexpr bool registered = false; }; template using DeclaredSpecificationManifest = typename models::SpecificationContribution>::Manifest; template [[nodiscard]] consteval bool genericManifestTargetIsComplete() { using Manifest = DeclaredSpecificationManifest; if constexpr (!requires { typename Manifest::TargetQuantity; typename Manifest::ConstraintResidualQuantity; }) { // Preserve the lower-level, structurally declared manifest // path. It has no dimensional types from which a distinct // residual reference could be inferred. return true; } else if constexpr ( std::same_as ) { return true; } else { using ResidualReference = dimensions::QuantityValue; return requires(const Specification &specification) { { specification.residualReference() } -> std::same_as; }; } } /* * The ordinary physics-facing extension path is a strongly typed * target(). If the constraint equation has different units, the * specification also supplies a strongly typed residualReference(). * This remains local physics vocabulary and prevents a target with, * for example, density units from silently scaling an energy row. */ template requires(!std::same_as, models::FixedCentralDensity>) && requires(const Specification &specification) { { specification.target().value() } -> std::convertible_to; } struct ManifestTargetAdapter { using Manifest = DeclaredSpecificationManifest; static constexpr bool registered = genericManifestTargetIsComplete(); [[nodiscard]] static ManifestTargetData Read( const Specification &specification, const Model & ) requires registered { const double target = static_cast(specification.target().value()); const double residualReference = [&] { if constexpr (requires { typename Manifest::TargetQuantity; typename Manifest::ConstraintResidualQuantity; }) { if constexpr (!std::same_as< typename Manifest::TargetQuantity, typename Manifest::ConstraintResidualQuantity>) { return static_cast(specification.residualReference().value()); } else { return target; } } else { return target; } }(); return { .target = target, .carrierTarget = std::nullopt, .residualScale = std::max(std::abs(residualReference), 1.0e-300) }; } }; template struct ManifestTargetAdapter { static constexpr bool registered = true; [[nodiscard]] static ManifestTargetData Read( const models::FixedTotalMass &specification, const Model & ) { const double target = specification.targetMass().value(); return { .target = target, .carrierTarget = target, .residualScale = std::max(std::abs(target), 1.0e-300) }; } }; template struct ManifestTargetAdapter { static constexpr bool registered = true; [[nodiscard]] static ManifestTargetData Read( const models::FixedAngularMomentum &specification, const Model & ) { const double target = specification.targetAngularMomentum().value(); return { .target = target, .carrierTarget = std::nullopt, .residualScale = std::max(std::abs(target), 1.0e-300) }; } }; template requires requires(const models::FixedCentralDensity &specification, const Model &model) { { models::compileConstraint( specification, modelSpecificationForRole(model) ) .targetDensity() .value() } -> std::convertible_to; { models::compileConstraint( specification, modelSpecificationForRole(model) ) .targetEnthalpy() .value() } -> std::convertible_to; } struct ManifestTargetAdapter { static constexpr bool registered = true; [[nodiscard]] static ManifestTargetData Read( const models::FixedCentralDensity &specification, const Model &model ) { const auto compiled = models::compileConstraint( specification, modelSpecificationForRole(model) ); const double target = compiled.targetDensity().value(); const double carrierTarget = compiled.targetEnthalpy().value(); return { .target = target, .carrierTarget = carrierTarget, .residualScale = std::max(std::abs(carrierTarget), 1.0e-300) }; } }; struct StaticRootBlockDescriptor final { std::string_view stableId; std::string_view symbol; RootBlockProvenance provenance; std::string_view source; RootRowInjection rowInjection; RootColumnPolicy columnPolicy; RootScalePolicy scalePolicy; }; template struct RootBlockTraits; template concept DescribedRootBlock = requires { RootBlockTraits::descriptor; }; #define MEAN_FIELD_PHYSICAL_VALUE_BLOCK(BlockType, StableId, Symbol) \ template <> struct RootBlockTraits { \ static constexpr StaticRootBlockDescriptor descriptor{ \ StableId, \ Symbol, \ RootBlockProvenance::physical_operator, \ "stellar_equilibrium", \ RootRowInjection::physical_equation, \ RootColumnPolicy::physical_state, \ RootScalePolicy::unscaled \ }; \ } #define MEAN_FIELD_PHYSICAL_RESIDUAL_BLOCK(BlockType, StableId, Symbol) \ template <> struct RootBlockTraits { \ static constexpr StaticRootBlockDescriptor descriptor{ \ StableId, \ Symbol, \ RootBlockProvenance::physical_operator, \ "stellar_equilibrium", \ RootRowInjection::physical_equation, \ RootColumnPolicy::no_column, \ RootScalePolicy::unscaled \ }; \ } MEAN_FIELD_PHYSICAL_VALUE_BLOCK( utils::blocks::density::mass::value, "density", "rho" ); MEAN_FIELD_PHYSICAL_VALUE_BLOCK( utils::blocks::displacement::geometry::value, "volume_displacement", "d" ); MEAN_FIELD_PHYSICAL_VALUE_BLOCK( utils::blocks::surface_deformation::parameters::value, "surface_deformation", "q" ); MEAN_FIELD_PHYSICAL_VALUE_BLOCK( utils::blocks::gravity::gradient::value, "gravity_gradient", "g" ); MEAN_FIELD_PHYSICAL_VALUE_BLOCK( utils::blocks::gravity::poisson::value, "gravity_potential", "Phi" ); MEAN_FIELD_PHYSICAL_VALUE_BLOCK( utils::blocks::enthalpy::specific::value, "specific_enthalpy", "h" ); MEAN_FIELD_PHYSICAL_RESIDUAL_BLOCK( utils::blocks::gravity::gradient::residual, "gravity_gradient_relation", "R_g" ); MEAN_FIELD_PHYSICAL_RESIDUAL_BLOCK( utils::blocks::gravity::poisson::residual, "poisson_balance", "R_Phi" ); MEAN_FIELD_PHYSICAL_RESIDUAL_BLOCK( utils::blocks::density::mass::residual, "barotropic_closure", "R_rho" ); MEAN_FIELD_PHYSICAL_RESIDUAL_BLOCK( utils::blocks::displacement::geometry::residual, "mechanical_balance", "R_d" ); MEAN_FIELD_PHYSICAL_RESIDUAL_BLOCK( utils::blocks::surface_deformation::shape_equilibrium::residual, "surface_shape_balance", "R_q" ); MEAN_FIELD_PHYSICAL_RESIDUAL_BLOCK( utils::blocks::enthalpy::specific::residual, "hydrostatic_balance", "R_h" ); #undef MEAN_FIELD_PHYSICAL_VALUE_BLOCK #undef MEAN_FIELD_PHYSICAL_RESIDUAL_BLOCK /* * Generated block descriptors are constexpr objects. A third-party * manifest can satisfy the broad, physics-facing manifest concept while * publishing non-constant or empty metadata and merely claiming * available=true. Detect both cases before selecting RootBlockTraits: * attempting to initialize its constexpr descriptor first would turn a * capability query into a hard template error. */ template using ConstantCompleteGeneratedManifestMetadata = std::bool_constant< !static_cast(Candidate::valueStableId).empty() && !static_cast(Candidate::valueSymbol).empty() && !static_cast(Candidate::residualStableId).empty() && !static_cast(Candidate::residualSymbol).empty() && !static_cast(Candidate::targetUnits).empty() && !static_cast(Candidate::residualUnits).empty()>; template struct CompleteGeneratedManifestMetadata : std::false_type { }; template struct CompleteGeneratedManifestMetadata< Candidate, std::void_t>> : ConstantCompleteGeneratedManifestMetadata { }; template concept ManifestDescribedGeneratedCoordinate = requires { typename Generated::SpecificationType; } && models::ModelSpecification && models::CompleteGeneratedManifestFor && CompleteGeneratedManifestMetadata< typename models::SpecificationContribution::Manifest>::value; template [[nodiscard]] consteval RootColumnPolicy generatedColumnPolicy() { constexpr auto kind = models::SpecificationContribution::generatedStateKind; if constexpr (kind == models::GeneratedStateKind::multiplier) { return RootColumnPolicy::existing_physical_multiplier; } else if constexpr (kind == models::GeneratedStateKind::physical_coordinate) { return RootColumnPolicy::generated_physical_coordinate; } else if constexpr (kind == models::GeneratedStateKind::solver_border) { return RootColumnPolicy::solver_border; } else { return RootColumnPolicy::no_column; } } /* * Generated blocks are described by their owning physics * specification. This is the manifest analogue of the variadic * operator compiler: adding a specification must not require another * combination-specific block-traits specialization. */ template struct RootBlockTraits> { private: using Specification = typename Generated::SpecificationType; using Manifest = typename models::SpecificationContribution::Manifest; public: static constexpr StaticRootBlockDescriptor descriptor{ Manifest::valueStableId, Manifest::valueSymbol, RootBlockProvenance::model_specification, models::SpecificationTraits::name, RootRowInjection::physical_equation, generatedColumnPolicy(), RootScalePolicy::unscaled }; }; template struct RootBlockTraits> { private: using Specification = typename Generated::SpecificationType; using Manifest = typename models::SpecificationContribution::Manifest; public: static constexpr StaticRootBlockDescriptor descriptor{ Manifest::residualStableId, Manifest::residualSymbol, RootBlockProvenance::model_specification, models::SpecificationTraits::name, RootRowInjection::append_global, RootColumnPolicy::no_column, RootScalePolicy::target_relative }; }; template struct SingleGeneratedBlockPair; template struct SingleGeneratedBlockPair< models::ModelTypeList, models::ModelTypeList> { using Value = utils::blocks::generated_value_block; using Residual = utils::blocks::generated_residual_block; }; template struct RootManifestSpecificationContribution { private: static constexpr auto role = models::SpecificationTraits::role; static constexpr auto generatedValueArity = models::SpecificationContribution::generatedValueArity; static constexpr auto generatedResidualArity = models::SpecificationContribution::generatedResidualArity; public: static constexpr bool registered = generatedValueArity == 0 && generatedResidualArity == 0 && role != models::SpecificationRole::boundary_condition; static constexpr std::size_t constraintCount = 0; static constexpr std::size_t replacementCount = 0; template static constexpr bool completeFor = registered; template < typename Form, std::size_t Count> static void AppendConstraints( std::array< RootConstraintDescriptor, Count> &, std::size_t &, const Model & ) noexcept { } template < typename Form, std::size_t Count> static void AppendReplacements( std::array< RootRowReplacementDescriptor, Count> &, std::size_t &, const Model &, int ) noexcept { } }; template requires( models::SpecificationContribution::generatedValueArity == 1 && models::SpecificationContribution::generatedResidualArity == 1 && models::CompleteGeneratedManifestFor && ManifestTargetAdapter::registered ) struct RootManifestSpecificationContribution { private: using Contribution = models::SpecificationContribution; using GeneratedBlocks = SingleGeneratedBlockPair< typename Contribution::GeneratedValues, typename Contribution::GeneratedResiduals>; public: using ValueBlock = typename GeneratedBlocks::Value; using ResidualBlock = typename GeneratedBlocks::Residual; using Manifest = typename Contribution::Manifest; static constexpr bool registered = true; static constexpr std::size_t constraintCount = 1; static constexpr std::size_t replacementCount = 0; template static constexpr bool completeFor = utils::blocks::contains_type_v && utils::blocks::contains_type_v; template < typename Form, std::size_t Count> static void AppendConstraints( std::array< RootConstraintDescriptor, Count> &descriptors, std::size_t &next, const Model &model ) { const auto target = ManifestTargetAdapter::Read( model.template specification(), model ); descriptors[next++] = { .stableId = models::SpecificationTraits::name, .role = models::SpecificationTraits::role, .rowInjection = RootRowInjection::append_global, .columnPolicy = generatedColumnPolicy(), .valueBlock = utils::blocks::type_index_v, .residualBlock = utils::blocks::type_index_v, .rowArity = ResidualBlock::static_block_size, .columnArity = ValueBlock::static_block_size, .target = target.target, .carrierTarget = target.carrierTarget, .targetUnits = Manifest::targetUnits, .residualUnits = Manifest::residualUnits, .residualScale = target.residualScale }; } template < typename Form, std::size_t Count> static void AppendReplacements( std::array< RootRowReplacementDescriptor, Count> &, std::size_t &, const Model &, int ) noexcept { } }; /* * Boundary equations replace rows rather than append a generated * scalar. This is a per-physics-condition adapter, never a * constraint-pack adapter. Future surface families register their * carrier equation here (or through a later generalized surface * compiler) and automatically compose with every integral/phase pack. */ template requires requires(const Model &model) { { modelSpecificationForRole(model) .targetPressure() .value() } -> std::convertible_to; } struct RootManifestSpecificationContribution { static constexpr bool registered = true; static constexpr std::size_t constraintCount = 1; static constexpr std::size_t replacementCount = 1; template static constexpr bool completeFor = utils::blocks:: contains_type_v; template < typename Form, std::size_t Count> static void AppendConstraints( std::array< RootConstraintDescriptor, Count> &descriptors, std::size_t &next, const Model &model ) { descriptors[next++] = { .stableId = models::SpecificationTraits::name, .role = models::SpecificationRole::boundary_condition, .rowInjection = RootRowInjection::replace_carrier_rows, .columnPolicy = RootColumnPolicy::no_column, .valueBlock = -1, .residualBlock = utils::blocks::type_index_v< utils::blocks::enthalpy::specific::residual, typename Form::residual_blocks>, .rowArity = 0, .columnArity = 0, .target = modelSpecificationForRole(model) .targetPressure() .value(), .carrierTarget = std::nullopt, .targetUnits = "pressure", .residualUnits = "specific_enthalpy", .residualScale = 1.0 }; } template < typename Form, std::size_t Count> static void AppendReplacements( std::array< RootRowReplacementDescriptor, Count> &descriptors, std::size_t &next, const Model &, const int replacedRowCount ) { descriptors[next++] = { .stableId = "isobaric_surface.replacement", .sourceSpecification = models::SpecificationTraits::name, .role = models::SpecificationRole::boundary_condition, .carrierResidualBlock = utils::blocks::type_index_v< utils::blocks::enthalpy::specific::residual, typename Form::residual_blocks>, .replacedRowCount = replacedRowCount }; } }; template struct CompileRootManifestContributions; template struct CompileRootManifestContributions> { static constexpr bool complete = (RootManifestSpecificationContribution::registered && ...); static constexpr std::size_t constraintCount = (std::size_t{0} + ... + RootManifestSpecificationContribution::constraintCount); static constexpr std::size_t replacementCount = (std::size_t{0} + ... + RootManifestSpecificationContribution::replacementCount); template static constexpr bool completeFor = complete && (RootManifestSpecificationContribution::template completeFor
&& ...); template [[nodiscard]] static std::array< RootConstraintDescriptor, constraintCount> MakeConstraints(const Model &model) { std::array descriptors{}; std::size_t next = 0; (RootManifestSpecificationContribution::template AppendConstraints( descriptors, next, model ), ...); return descriptors; } template [[nodiscard]] static std::array< RootRowReplacementDescriptor, replacementCount> MakeReplacements( const Model &model, const int replacedRowCount ) { std::array descriptors{}; std::size_t next = 0; (RootManifestSpecificationContribution::template AppendReplacements( descriptors, next, model, replacedRowCount ), ...); return descriptors; } }; template struct AllRootBlocksAreDescribed; template struct AllRootBlocksAreDescribed> : std::bool_constant<(DescribedRootBlock && ...)> { }; template [[nodiscard]] consteval bool rootBlockStableIdsAreUnique() { constexpr std::array stableIds{ RootBlockTraits::descriptor.stableId... }; for (std::size_t first = 0; first < stableIds.size(); ++first) { for (std::size_t second = first + 1; second < stableIds.size(); ++second) { if (stableIds[first] == stableIds[second]) { return false; } } } return true; } /* * Stable IDs are machine identities within a block kind. Symbols are * deliberately excluded: they are mathematical presentation labels * and two independent constraints may reasonably use the same one. */ template struct RootBlockStableIdsAreUnique : std::false_type { }; template requires(DescribedRootBlock && ...) struct RootBlockStableIdsAreUnique> : std::bool_constant()> { }; template struct GeneratedRootBlockOwner { static constexpr bool available = false; static constexpr bool isResidual = false; }; template requires requires { typename Generated::SpecificationType; } struct GeneratedRootBlockOwner> { using Specification = typename Generated::SpecificationType; static constexpr bool available = true; static constexpr bool isResidual = false; }; template requires requires { typename Generated::SpecificationType; } struct GeneratedRootBlockOwner> { using Specification = typename Generated::SpecificationType; static constexpr bool available = true; static constexpr bool isResidual = true; }; template < typename Block, typename Model> [[nodiscard]] consteval bool rootBlockBelongsToModel() { if constexpr (!GeneratedRootBlockOwner::available) { return true; } else { using Specification = typename GeneratedRootBlockOwner::Specification; return requires { requires Model::template containsSpecification; }; } } template struct AllGeneratedRootBlocksBelongToModel; template struct AllGeneratedRootBlocksBelongToModel, Model> : std::bool_constant<(rootBlockBelongsToModel() && ...)> { }; template struct RootManifestCompilation { static constexpr bool complete = false; static constexpr std::size_t constraintCount = 0; static constexpr std::size_t replacementCount = 0; }; template struct RootManifestCompilation< Model, Form, std::void_t< typename Model::SpecificationTypes, typename Form::value_blocks, typename Form::residual_blocks>> { using Contributions = CompileRootManifestContributions; static constexpr bool complete = Contributions::template completeFor && AllRootBlocksAreDescribed::value && AllRootBlocksAreDescribed::value && RootBlockStableIdsAreUnique::value && RootBlockStableIdsAreUnique::value && AllGeneratedRootBlocksBelongToModel::value && AllGeneratedRootBlocksBelongToModel::value; static constexpr std::size_t constraintCount = Contributions::constraintCount; static constexpr std::size_t replacementCount = Contributions::replacementCount; }; template < typename Block, typename Model> [[nodiscard]] double modelBlockScale(const Model &model) { if constexpr (GeneratedRootBlockOwner::isResidual) { using Specification = typename GeneratedRootBlockOwner::Specification; static_assert( ManifestTargetAdapter::registered, "A generated residual needs a physics target adapter before it can enter the root manifest." ); return ManifestTargetAdapter::Read( model.template specification(), model ) .residualScale; } else { return 1.0; } } template < RootBlockKind Kind, typename Model, typename... Blocks> [[nodiscard]] std::array< RootBlockDescriptor, sizeof...(Blocks)> makeModelBlockDescriptors( const mfem::Array &offsets, const Model &model, utils::blocks::type_list ) { static_assert( (DescribedRootBlock && ...), "Every compiled equilibrium block requires root-manifest metadata." ); std::array descriptors{}; int index = 0; ((descriptors[index] = {.stableId = RootBlockTraits::descriptor.stableId, .symbol = RootBlockTraits::descriptor.symbol, .kind = Kind, .provenance = RootBlockTraits::descriptor.provenance, .source = RootBlockTraits::descriptor.source, .rowInjection = RootBlockTraits::descriptor.rowInjection, .columnPolicy = RootBlockTraits::descriptor.columnPolicy, .scalePolicy = RootBlockTraits::descriptor.scalePolicy, .canonicalIndex = index, .offset = offsets[index], .size = offsets[index + 1] - offsets[index], .scale = modelBlockScale(model)}, ++index), ...); return descriptors; } template inline constexpr std::size_t rootConstraintCount = CompileRootManifestContributions::constraintCount; template inline constexpr std::size_t rootReplacementCount = CompileRootManifestContributions::replacementCount; template < typename Model, typename Form, typename JacobianForm> [[nodiscard]] consteval bool manifestMatchesCompiledEquilibriumSystem() { if constexpr (StellarEquilibriumSystemCompilable) { return RootManifestCompilation, std::remove_cvref_t>::complete && std::same_as> && std::same_as>; } else { return false; } } } // namespace detail template inline constexpr bool rootManifestIsCompilable = detail::RootManifestCompilation, std::remove_cvref_t>::complete; template concept CompilableRootManifestFor = rootManifestIsCompilable; /* * A model specification is addressable through specification() only * when it contributes exactly one equation descriptor to the compiled * root manifest. Membership in the model is deliberately insufficient: * material laws such as an EOS participate in the physical operator, but * do not own a root-constraint descriptor. */ template concept RootManifestSpecificationDescriptorFor = models::ModelSpecification> && models::SpecifiedModelType> && requires { requires std::remove_cvref_t::template containsSpecification>; requires detail::RootManifestSpecificationContribution< std::remove_cvref_t, std::remove_cvref_t>::registered; requires detail::RootManifestSpecificationContribution< std::remove_cvref_t, std::remove_cvref_t>::constraintCount == 1; }; /* * A non-owning, read-only MFEM block. MFEM's aliasing Vector constructor * requires a mutable pointer even for read-only use, so the writable alias * stays private and only const operations are exposed. This prevents a * residual/Jacobian contribution from mutating solver input through a * nominally const state or direction view. */ class ReadOnlyVectorView final { public: ReadOnlyVectorView( const mfem::Vector &vector, const int offset, const int size ) : m_view( CheckedData( vector, offset, size ), size ) { } [[nodiscard]] int Size() const noexcept { return m_view.Size(); } [[nodiscard]] mfem::real_t operator()(const int index) const { return m_view(index); } [[nodiscard]] const mfem::real_t *GetData() const noexcept { return m_view.GetData(); } [[nodiscard]] double Norml2() const { return m_view.Norml2(); } [[nodiscard]] const mfem::Vector &asMFEMVector() const noexcept { return m_view; } [[nodiscard]] operator const mfem::Vector &() const noexcept { return m_view; } private: [[nodiscard]] static mfem::real_t *CheckedData( const mfem::Vector &vector, const int offset, const int size ) { if (offset < 0 || size < 0 || offset > vector.Size() - size) { throw std::invalid_argument("A read-only block view received an invalid range."); } return const_cast(vector.GetData()) + offset; } mfem::Vector m_view; }; namespace detail { template struct RootValueTerm final { using value = Block; }; template struct RootResidualTerm final { using residual = Block; }; template struct SingleRootBlock; template struct SingleRootBlock> final { using Type = Block; }; } // namespace detail template class RootStateView final { public: RootStateView( const mfem::Vector &state, const utils::blocks::form_layout &layout ) : m_state(state), m_layout(layout) { if (state.Size() != layout.value_offsets().Last()) { throw std::invalid_argument("RootStateView received a vector with the wrong size."); } } RootStateView( mfem::Vector &&, const utils::blocks::form_layout & ) = delete; RootStateView( const mfem::Vector &&, const utils::blocks::form_layout & ) = delete; RootStateView( const mfem::Vector &, utils::blocks::form_layout && ) = delete; RootStateView( const mfem::Vector &, const utils::blocks::form_layout && ) = delete; template [[nodiscard]] ReadOnlyVectorView block(const Term &term) const { constexpr auto valueBlock = utils::blocks::get_value_block(term); return {m_state, m_layout.offset(valueBlock), m_layout.size(valueBlock)}; } template requires( stellarEquilibriumSpecificationCompilationComplete && StellarEquilibriumSpecificationCompilation::GeneratedValueBlocks::size == 1 && utils::blocks::contains_type_v< typename detail::SingleRootBlock< typename StellarEquilibriumSpecificationCompilation::GeneratedValueBlocks>::Type, typename Form::value_blocks> ) [[nodiscard]] ReadOnlyVectorView generatedCoordinate() const { using Block = typename detail::SingleRootBlock< typename StellarEquilibriumSpecificationCompilation::GeneratedValueBlocks>::Type; return block(detail::RootValueTerm{}); } [[nodiscard]] const mfem::Vector &vector() const noexcept { return m_state; } private: const mfem::Vector &m_state; const utils::blocks::form_layout &m_layout; }; template class MutableRootStateView final { public: MutableRootStateView( mfem::Vector &state, const utils::blocks::form_layout &layout ) : m_state(state), m_layout(layout) { if (state.Size() != layout.value_offsets().Last()) { throw std::invalid_argument("MutableRootStateView received a vector with the wrong size."); } } MutableRootStateView( mfem::Vector &&, const utils::blocks::form_layout & ) = delete; MutableRootStateView( mfem::Vector &, utils::blocks::form_layout && ) = delete; MutableRootStateView( mfem::Vector &, const utils::blocks::form_layout && ) = delete; template [[nodiscard]] mfem::Vector block(const Term &term) const { constexpr auto valueBlock = utils::blocks::get_value_block(term); return mfem::Vector(m_state.GetData() + m_layout.offset(valueBlock), m_layout.size(valueBlock)); } template requires( stellarEquilibriumSpecificationCompilationComplete && StellarEquilibriumSpecificationCompilation::GeneratedValueBlocks::size == 1 && utils::blocks::contains_type_v< typename detail::SingleRootBlock< typename StellarEquilibriumSpecificationCompilation::GeneratedValueBlocks>::Type, typename Form::value_blocks> ) [[nodiscard]] mfem::Vector generatedCoordinate() const { using Block = typename detail::SingleRootBlock< typename StellarEquilibriumSpecificationCompilation::GeneratedValueBlocks>::Type; return block(detail::RootValueTerm{}); } [[nodiscard]] mfem::Vector &vector() const noexcept { return m_state; } private: mfem::Vector &m_state; const utils::blocks::form_layout &m_layout; }; template class ResidualView final { public: ResidualView( mfem::Vector &residual, const utils::blocks::form_layout &layout ) : m_residual(residual), m_layout(layout) { if (residual.Size() != layout.residual_offsets().Last()) { throw std::invalid_argument("ResidualView received a vector with the wrong size."); } } ResidualView( mfem::Vector &&, const utils::blocks::form_layout & ) = delete; ResidualView( mfem::Vector &, utils::blocks::form_layout && ) = delete; ResidualView( mfem::Vector &, const utils::blocks::form_layout && ) = delete; template [[nodiscard]] mfem::Vector block(const Term &term) const { constexpr auto residualBlock = utils::blocks::get_residual_block(term); return mfem::Vector(m_residual.GetData() + m_layout.offset(residualBlock), m_layout.size(residualBlock)); } template requires( stellarEquilibriumSpecificationCompilationComplete && StellarEquilibriumSpecificationCompilation::GeneratedResidualBlocks::size == 1 && utils::blocks::contains_type_v< typename detail::SingleRootBlock::GeneratedResidualBlocks>::Type, typename Form::residual_blocks> ) [[nodiscard]] mfem::Vector constraintResidual() const { using Block = typename detail::SingleRootBlock< typename StellarEquilibriumSpecificationCompilation::GeneratedResidualBlocks>::Type; return block(detail::RootResidualTerm{}); } template void assign( const Term &term, const mfem::Vector &source ) const { mfem::Vector destination = block(term); if (destination.Size() != source.Size()) { throw std::invalid_argument("ResidualView block assignment has the wrong size."); } destination = source; destination.SyncAliasMemory(m_residual); } [[nodiscard]] mfem::Vector &vector() const noexcept { return m_residual; } private: mfem::Vector &m_residual; const utils::blocks::form_layout &m_layout; }; template requires utils::blocks::valid_jacobian_form class CompiledRootManifest final { public: using ModelType = Model; using FormType = Form; using JacobianType = JacobianForm; using Layout = utils::blocks::form_layout; using StateView = RootStateView; using MutableStateView = MutableRootStateView; using DirectionView = RootStateView; using RootResidualView = ResidualView; static constexpr bool hasCompleteEquilibriumCompiler = detail::manifestMatchesCompiledEquilibriumSystem(); static constexpr models::ModelCompilationClass compilationClass = hasCompleteEquilibriumCompiler ? models::EquilibriumSystemCompilation::complete_equilibrium_system : models::EquilibriumSystemCompilation::equation_contributions_only; static constexpr bool symbolicallySquare = Model::symbolicallySquare; /* * Every descriptor and scale is folded from the model's canonical * specification pack. There is intentionally no scalar-input or * constraint-combination constructor. */ CompiledRootManifest( const std::array< int, Form::value_block_count> &valueSizes, const std::array< int, Form::residual_block_count> &residualSizes, const Model &model, const int replacedSurfaceRowCount ) requires CompilableRootManifestFor< Model, Form> : m_layout( valueSizes, residualSizes ), m_valueBlocks( detail::makeModelBlockDescriptors( m_layout.value_offsets(), model, typename Form::value_blocks{} ) ), m_residualBlocks( detail::makeModelBlockDescriptors( m_layout.residual_offsets(), model, typename Form::residual_blocks{} ) ), m_replacements( detail::CompileRootManifestContributions< Model, typename Model::SpecificationTypes>:: template MakeReplacements( model, replacedSurfaceRowCount ) ), m_constraints( detail::CompileRootManifestContributions< Model, typename Model::SpecificationTypes>::template MakeConstraints(model) ) { if (replacedSurfaceRowCount < 0) { throw std::invalid_argument( "An equilibrium-system manifest cannot contain a negative replacement-row count." ); } ValidateNumericalMetadata(); if constexpr (compilationClass == models::EquilibriumSystemCompilation::complete_equilibrium_system) { if (m_layout.value_offsets().Last() != m_layout.residual_offsets().Last()) { throw std::invalid_argument( "A complete equilibrium system must have equal state and equation dimensions." ); } } } [[nodiscard]] const Layout &layout() const noexcept { return m_layout; } [[nodiscard]] MutableStateView stateView(mfem::Vector &state) const & { return {state, m_layout}; } [[nodiscard]] StateView stateView(const mfem::Vector &state) const & { return {state, m_layout}; } [[nodiscard]] StateView stateView(mfem::Vector &&) const & = delete; [[nodiscard]] StateView stateView(const mfem::Vector &&) const & = delete; [[nodiscard]] MutableStateView stateView(mfem::Vector &) const && = delete; [[nodiscard]] StateView stateView(const mfem::Vector &) const && = delete; [[nodiscard]] StateView stateView(mfem::Vector &&) const && = delete; [[nodiscard]] StateView stateView(const mfem::Vector &&) const && = delete; [[nodiscard]] DirectionView directionView(const mfem::Vector &direction) const & { return {direction, m_layout}; } [[nodiscard]] DirectionView directionView(mfem::Vector &&) const & = delete; [[nodiscard]] DirectionView directionView(const mfem::Vector &&) const & = delete; [[nodiscard]] DirectionView directionView(const mfem::Vector &) const && = delete; [[nodiscard]] DirectionView directionView(mfem::Vector &&) const && = delete; [[nodiscard]] DirectionView directionView(const mfem::Vector &&) const && = delete; [[nodiscard]] RootResidualView residualView(mfem::Vector &residual) const & { return {residual, m_layout}; } [[nodiscard]] RootResidualView residualView(mfem::Vector &&) const & = delete; [[nodiscard]] RootResidualView residualView(const mfem::Vector &&) const & = delete; [[nodiscard]] RootResidualView residualView(mfem::Vector &) const && = delete; [[nodiscard]] RootResidualView residualView(mfem::Vector &&) const && = delete; [[nodiscard]] RootResidualView residualView(const mfem::Vector &&) const && = delete; [[nodiscard]] std::span valueBlocks() const noexcept { return m_valueBlocks; } [[nodiscard]] std::span residualBlocks() const noexcept { return m_residualBlocks; } [[nodiscard]] std::span rowReplacements() const noexcept { return m_replacements; } [[nodiscard]] std::span constraints() const noexcept { return m_constraints; } template requires RootManifestSpecificationDescriptorFor< Specification, Model> [[nodiscard]] const RootConstraintDescriptor &specification() const { constexpr auto requestedKey = models::SpecificationTraits::key; const auto descriptor = std::find_if( m_constraints.begin(), m_constraints.end(), [requestedKey](const RootConstraintDescriptor &candidate) { return candidate.role == requestedKey.role && candidate.stableId == requestedKey.stableName; } ); if (descriptor == m_constraints.end()) { throw std::logic_error("The requested model specification has no root-manifest equation descriptor."); } return *descriptor; } [[nodiscard]] static constexpr std::span specificationDescriptors() noexcept { return Model::runtimeSpecificationDescriptors(); } [[nodiscard]] RootConstraintReport fixedMassReport(const double achievedMass) const { const RootConstraintDescriptor &descriptor = specification(); const double residual = achievedMass - descriptor.target; return { .descriptor = descriptor, .achieved = achievedMass, .dimensionalResidual = residual, .scaledResidual = residual / descriptor.residualScale }; } private: void ValidateNumericalMetadata() const { for (const RootConstraintDescriptor &constraint : m_constraints) { if (!std::isfinite(constraint.target)) { throw std::invalid_argument("An equilibrium-system manifest constraint requires a finite target."); } if (constraint.carrierTarget.has_value() && !std::isfinite(*constraint.carrierTarget)) { throw std::invalid_argument( "An equilibrium-system manifest constraint requires a finite carrier target." ); } if (!std::isfinite(constraint.residualScale) || constraint.residualScale <= 0.0) { throw std::invalid_argument( "An equilibrium-system manifest constraint requires a finite, positive residual scale." ); } } for (const RootBlockDescriptor &block : m_valueBlocks) { if (!std::isfinite(block.scale) || block.scale <= 0.0) { throw std::invalid_argument( "An equilibrium-system manifest block requires a finite, positive scale." ); } } for (const RootBlockDescriptor &block : m_residualBlocks) { if (!std::isfinite(block.scale) || block.scale <= 0.0) { throw std::invalid_argument( "An equilibrium-system manifest block requires a finite, positive scale." ); } } } Layout m_layout; std::array m_valueBlocks; std::array m_residualBlocks; std::array> m_replacements; std::array> m_constraints; }; // Physics-facing names for the public equilibrium-system boundary. The // root-oriented names remain available while existing solver consumers // migrate, but new APIs should expose these aliases. using EquilibriumBlockKind = RootBlockKind; using EquilibriumBlockProvenance = RootBlockProvenance; using EquilibriumEquationInjection = RootRowInjection; using EquilibriumGeneratedVariablePolicy = RootColumnPolicy; using EquilibriumScalePolicy = RootScalePolicy; using EquilibriumBlockDescriptor = RootBlockDescriptor; using EquilibriumEquationReplacementDescriptor = RootRowReplacementDescriptor; using EquilibriumSpecificationDescriptor = RootConstraintDescriptor; using EquilibriumSpecificationReport = RootConstraintReport; template using EquilibriumStateView = RootStateView; template using MutableEquilibriumStateView = MutableRootStateView; template using EquilibriumResidualView = ResidualView; template requires utils::blocks::valid_jacobian_form using EquilibriumSystemManifest = CompiledRootManifest; } // namespace mean_field::operators