module; #include #include #include #include export module mean_field:normalization.physical_riesz; export import :dimensions.quantities; export import :field.mfem; export import :model.specifications; export import :normalization.plan; export namespace mean_field::normalization { struct Unnormalized final : NormalizationPrescriptionTag { }; struct ReferenceGeometry final { }; struct FixedMassBranchReference final { }; template concept RieszGeometryPolicy = std::same_as, ReferenceGeometry>; template concept ReferenceScalePolicy = std::same_as, FixedMassBranchReference>; template < RieszGeometryPolicy GeometryPolicy = ReferenceGeometry, ReferenceScalePolicy ScalePolicy = FixedMassBranchReference> class PhysicalRieszDiagonal final : public NormalizationPrescriptionTag { public: using Geometry = GeometryPolicy; using ScaleSource = ScalePolicy; explicit PhysicalRieszDiagonal( const dimensions::LengthValue referenceRadius, const double gravitationalConstant = 1.0 ) : m_referenceRadius(referenceRadius), m_gravitationalConstant(gravitationalConstant) { if (!std::isfinite(referenceRadius.value()) || referenceRadius.value() <= 0.0) { throw std::invalid_argument("Physical Riesz normalization requires a finite, positive branch radius."); } if (!std::isfinite(gravitationalConstant) || gravitationalConstant <= 0.0) { throw std::invalid_argument( "Physical Riesz normalization requires a finite, positive gravitational constant." ); } } [[nodiscard]] dimensions::LengthValue referenceRadius() const noexcept { return m_referenceRadius; } [[nodiscard]] double gravitationalConstant() const noexcept { return m_gravitationalConstant; } private: dimensions::LengthValue m_referenceRadius; double m_gravitationalConstant; }; PhysicalRieszDiagonal(dimensions::LengthValue, double = 1.0) -> PhysicalRieszDiagonal; template struct IsPhysicalRieszDiagonal : std::false_type { }; template struct IsPhysicalRieszDiagonal> : std::true_type { }; template concept PhysicalRieszDiagonalPrescription = IsPhysicalRieszDiagonal>::value; struct StellarCharacteristicScales final { dimensions::MassValue mass; dimensions::LengthValue radius; double gravitationalConstant; double density; double acceleration; double inverseTimeSquared; double specificEnergy; double pressure; double angularVelocity; double angularMomentum; double force; }; [[nodiscard]] inline StellarCharacteristicScales deriveStellarCharacteristicScales( const dimensions::MassValue mass, const dimensions::LengthValue radius, const double gravitationalConstant = 1.0 ) { const double massValue = mass.value(); const double radiusValue = radius.value(); if (!std::isfinite(massValue) || massValue <= 0.0) { throw std::invalid_argument("Characteristic stellar scales require a finite, positive mass."); } if (!std::isfinite(radiusValue) || radiusValue <= 0.0) { throw std::invalid_argument("Characteristic stellar scales require a finite, positive radius."); } if (!std::isfinite(gravitationalConstant) || gravitationalConstant <= 0.0) { throw std::invalid_argument( "Characteristic stellar scales require a finite, positive gravitational constant." ); } const double radiusSquared = radiusValue * radiusValue; const double radiusCubed = radiusSquared * radiusValue; const double density = massValue / radiusCubed; const double acceleration = gravitationalConstant * massValue / radiusSquared; const double inverseTimeSquared = gravitationalConstant * massValue / radiusCubed; const double specificEnergy = gravitationalConstant * massValue / radiusValue; const double pressure = gravitationalConstant * massValue * massValue / (radiusSquared * radiusSquared); const double angularVelocity = std::sqrt(inverseTimeSquared); const double angularMomentum = massValue * std::sqrt(gravitationalConstant * massValue * radiusValue); const double force = gravitationalConstant * massValue * massValue / radiusSquared; const double derived[] = { density, acceleration, inverseTimeSquared, specificEnergy, pressure, angularVelocity, angularMomentum, force }; for (const double value : derived) { if (!std::isfinite(value) || value <= 0.0) { throw std::overflow_error("A derived characteristic stellar scale is not finite and positive."); } } return { .mass = mass, .radius = radius, .gravitationalConstant = gravitationalConstant, .density = density, .acceleration = acceleration, .inverseTimeSquared = inverseTimeSquared, .specificEnergy = specificEnergy, .pressure = pressure, .angularVelocity = angularVelocity, .angularMomentum = angularMomentum, .force = force }; } template requires requires(const Model &model) { { model.template specification() } -> std::same_as; { model.template specification().targetMass() } -> std::same_as; } [[nodiscard]] StellarCharacteristicScales deriveStellarCharacteristicScales( const PhysicalRieszDiagonal &prescription, const Model &model ) { return deriveStellarCharacteristicScales( model.template specification().targetMass(), prescription.referenceRadius(), prescription.gravitationalConstant() ); } namespace detail { /* * Model definitions live below the numerical normalization layer so * that a physics component can describe its generated coordinates * without importing solver machinery. These two translations are the * deliberately small boundary between that neutral declaration and the * normalization plan used by the discretization. */ template struct DeclaredRieszTopology { static constexpr bool available = false; static constexpr RieszTopology value = RieszTopology::identity; }; #define MEAN_FIELD_DECLARED_RIESZ_TOPOLOGY(Name) \ template <> struct DeclaredRieszTopology { \ static constexpr bool available = true; \ static constexpr RieszTopology value = RieszTopology::Name; \ } MEAN_FIELD_DECLARED_RIESZ_TOPOLOGY(identity); MEAN_FIELD_DECLARED_RIESZ_TOPOLOGY(scalar_volume_l2); MEAN_FIELD_DECLARED_RIESZ_TOPOLOGY(vector_volume_l2); MEAN_FIELD_DECLARED_RIESZ_TOPOLOGY(scalar_boundary_l2); MEAN_FIELD_DECLARED_RIESZ_TOPOLOGY(hybrid_scalar_volume_point_rows); MEAN_FIELD_DECLARED_RIESZ_TOPOLOGY(global_scalar); #undef MEAN_FIELD_DECLARED_RIESZ_TOPOLOGY template struct DeclaredPhysicalScale { static constexpr bool available = false; static constexpr PhysicalScaleKind value = PhysicalScaleKind::dimensionless; }; #define MEAN_FIELD_DECLARED_PHYSICAL_SCALE(Name) \ template <> struct DeclaredPhysicalScale { \ static constexpr bool available = true; \ static constexpr PhysicalScaleKind value = PhysicalScaleKind::Name; \ } MEAN_FIELD_DECLARED_PHYSICAL_SCALE(dimensionless); MEAN_FIELD_DECLARED_PHYSICAL_SCALE(density); MEAN_FIELD_DECLARED_PHYSICAL_SCALE(length); MEAN_FIELD_DECLARED_PHYSICAL_SCALE(acceleration); MEAN_FIELD_DECLARED_PHYSICAL_SCALE(inverse_time_squared); MEAN_FIELD_DECLARED_PHYSICAL_SCALE(specific_energy); MEAN_FIELD_DECLARED_PHYSICAL_SCALE(pressure); MEAN_FIELD_DECLARED_PHYSICAL_SCALE(mass); MEAN_FIELD_DECLARED_PHYSICAL_SCALE(force); MEAN_FIELD_DECLARED_PHYSICAL_SCALE(angular_velocity); MEAN_FIELD_DECLARED_PHYSICAL_SCALE(angular_momentum); #undef MEAN_FIELD_DECLARED_PHYSICAL_SCALE template struct CompileDeclaredPhysicalRieszCoordinate { using Method = UnsupportedPhysicalRieszCoordinate; static constexpr bool registered = false; }; template struct CompileDeclaredPhysicalRieszCoordinate< Declaration, std::void_t< decltype(std::integral_constant< models::RieszTopology, static_cast(Declaration::topology)>{}), decltype(std::integral_constant< models::PhysicalScaleLaw, static_cast(Declaration::scale)>{}), decltype(std::bool_constant(Declaration::available)>{})>> { private: static constexpr models::RieszTopology declaredTopology = static_cast(Declaration::topology); static constexpr models::PhysicalScaleLaw declaredScale = static_cast(Declaration::scale); using Topology = DeclaredRieszTopology; using Scale = DeclaredPhysicalScale; public: static constexpr bool registered = static_cast(Declaration::available) && Topology::available && Scale::available; using Method = std::conditional_t< registered, PhysicalRieszCoordinate, UnsupportedPhysicalRieszCoordinate>; }; template struct DeclaredGeneratedPhysicalRieszCoordinate { using Method = UnsupportedPhysicalRieszCoordinate; static constexpr bool registered = false; }; template struct DeclaredGeneratedPhysicalRieszCoordinate< Generated, CoordinateKind::value, std::void_t< typename Generated::SpecificationType, typename models::SpecificationContribution< typename Generated::SpecificationType>::Normalization::Value>> : CompileDeclaredPhysicalRieszCoordinate< typename models::SpecificationContribution< typename Generated::SpecificationType>::Normalization::Value> { }; template struct DeclaredGeneratedPhysicalRieszCoordinate< Generated, CoordinateKind::residual, std::void_t< typename Generated::SpecificationType, typename models::SpecificationContribution< typename Generated::SpecificationType>::Normalization::Residual>> : CompileDeclaredPhysicalRieszCoordinate< typename models::SpecificationContribution< typename Generated::SpecificationType>::Normalization::Residual> { }; template struct GeneratedPhysicalRieszCoverage { static constexpr bool complete = false; }; template struct GeneratedPhysicalRieszCoverage< models::ModelTypeList, models::ModelTypeList> { static constexpr bool complete = (DeclaredGeneratedPhysicalRieszCoordinate< GeneratedValues, CoordinateKind::value>::registered && ...) && (DeclaredGeneratedPhysicalRieszCoordinate< GeneratedResiduals, CoordinateKind::residual>::registered && ...); }; template struct SpecificationPhysicalRieszCoverage { static constexpr bool complete = false; }; template struct SpecificationPhysicalRieszCoverage< Specification, std::void_t< typename models::SpecificationContribution::GeneratedValues, typename models::SpecificationContribution::GeneratedResiduals>> : GeneratedPhysicalRieszCoverage< typename models::SpecificationContribution::GeneratedValues, typename models::SpecificationContribution::GeneratedResiduals> { }; } // namespace detail /* * All generated blocks are normalized from their generating physics * specification. Adding another constraint therefore does not add a * normalization specialization: its public ModelDefinition is the single * source of both the value and residual Riesz laws. */ template struct PhysicalRieszBlockTraits> : detail::DeclaredGeneratedPhysicalRieszCoordinate { }; template struct PhysicalRieszBlockTraits> : detail::DeclaredGeneratedPhysicalRieszCoordinate { }; template concept GeneratedValuePhysicalRieszNormalizable = detail::DeclaredGeneratedPhysicalRieszCoordinate::registered; template concept GeneratedResidualPhysicalRieszNormalizable = detail::DeclaredGeneratedPhysicalRieszCoordinate::registered; template concept CompleteGeneratedPhysicalRieszNormalizationFor = detail::SpecificationPhysicalRieszCoverage>::complete; /* * Runtime Physical Riesz assembly needs more than a symbolically complete * plan: it must be able to recover the finite-element maps owned by the * selected physical core. Keep that structural capability in this low * normalization module so both problem formation and the solver-facing * adapter can consult the same authority without importing one another. */ template concept PhysicalRieszCoreRuntime = requires(const std::remove_cvref_t &core) { { core.GetGravityContext().GetDensityMap() } -> std::same_as; { core.GetGravityContext().GetGravityGradientMap() } -> std::same_as; { core.GetGravityContext().GetGravityPotentialMap() } -> std::same_as; { core.GetHydrostaticOperator().GetEnthalpyMap() } -> std::same_as; { core.GetDomainDeformation().parameterCount() } -> std::same_as; }; namespace detail { template using GeneratedPhysicalRieszMethod = typename DeclaredGeneratedPhysicalRieszCoordinate::Method; template struct GeneratedPhysicalRieszRuntimeCoordinate : std::false_type { }; template struct GeneratedPhysicalRieszRuntimeCoordinate< Generated, Kind, std::void_t::topology)>> : std::bool_constant< DeclaredGeneratedPhysicalRieszCoordinate::registered && GeneratedPhysicalRieszMethod::topology == RieszTopology::global_scalar> { }; template struct SpecificationPhysicalRieszRuntimeCoverage : std::false_type { }; template struct GeneratedPhysicalRieszRuntimeCoverage : std::false_type { }; template struct GeneratedPhysicalRieszRuntimeCoverage< models::ModelTypeList, models::ModelTypeList> : std::bool_constant< (GeneratedPhysicalRieszRuntimeCoordinate::value && ...) && (GeneratedPhysicalRieszRuntimeCoordinate::value && ...)> { }; template struct SpecificationPhysicalRieszRuntimeCoverage< Specification, std::void_t< typename models::SpecificationContribution::GeneratedValues, typename models::SpecificationContribution::GeneratedResiduals>> : GeneratedPhysicalRieszRuntimeCoverage< typename models::SpecificationContribution::GeneratedValues, typename models::SpecificationContribution::GeneratedResiduals> { }; template struct SpecificationSetPhysicalRieszRuntimeCoverage : std::false_type { }; template struct SpecificationSetPhysicalRieszRuntimeCoverage< models::detail::SpecificationSetStorage> : std::bool_constant< (SpecificationPhysicalRieszRuntimeCoverage::value && ...)> { }; } // namespace detail template concept CompleteGeneratedPhysicalRieszRuntimeNormalizationFor = detail::SpecificationPhysicalRieszRuntimeCoverage< std::remove_cvref_t>::value; #define MEAN_FIELD_PHYSICAL_RIESZ_TRAIT(BlockType, TopologyValue, ScaleValue) \ template <> struct PhysicalRieszBlockTraits { \ using Method = PhysicalRieszCoordinate; \ static constexpr bool registered = true; \ } MEAN_FIELD_PHYSICAL_RIESZ_TRAIT( utils::blocks::density::mass::value, scalar_volume_l2, density ); MEAN_FIELD_PHYSICAL_RIESZ_TRAIT( utils::blocks::surface_deformation::parameters::value, scalar_boundary_l2, length ); MEAN_FIELD_PHYSICAL_RIESZ_TRAIT( utils::blocks::gravity::gradient::value, vector_volume_l2, acceleration ); MEAN_FIELD_PHYSICAL_RIESZ_TRAIT( utils::blocks::gravity::poisson::value, scalar_volume_l2, specific_energy ); MEAN_FIELD_PHYSICAL_RIESZ_TRAIT( utils::blocks::enthalpy::specific::value, scalar_volume_l2, specific_energy ); MEAN_FIELD_PHYSICAL_RIESZ_TRAIT( utils::blocks::gravity::gradient::residual, vector_volume_l2, acceleration ); MEAN_FIELD_PHYSICAL_RIESZ_TRAIT( utils::blocks::gravity::poisson::residual, scalar_volume_l2, inverse_time_squared ); MEAN_FIELD_PHYSICAL_RIESZ_TRAIT( utils::blocks::density::mass::residual, scalar_volume_l2, density ); MEAN_FIELD_PHYSICAL_RIESZ_TRAIT( utils::blocks::surface_deformation::shape_equilibrium::residual, scalar_boundary_l2, force ); MEAN_FIELD_PHYSICAL_RIESZ_TRAIT( utils::blocks::enthalpy::specific::residual, hybrid_scalar_volume_point_rows, specific_energy ); #undef MEAN_FIELD_PHYSICAL_RIESZ_TRAIT template [[nodiscard]] double physicalScale( const StellarCharacteristicScales &scales ) { static_assert(PhysicalRieszBlockTraits::registered, "The block has no Physical Riesz normalization."); using Method = typename PhysicalRieszBlockTraits::Method; constexpr PhysicalScaleKind scale = Method::scale; if constexpr (scale == PhysicalScaleKind::dimensionless) { return 1.0; } else if constexpr (scale == PhysicalScaleKind::density) { return scales.density; } else if constexpr (scale == PhysicalScaleKind::length) { return scales.radius.value(); } else if constexpr (scale == PhysicalScaleKind::acceleration) { return scales.acceleration; } else if constexpr (scale == PhysicalScaleKind::inverse_time_squared) { return scales.inverseTimeSquared; } else if constexpr (scale == PhysicalScaleKind::specific_energy) { return scales.specificEnergy; } else if constexpr (scale == PhysicalScaleKind::pressure) { return scales.pressure; } else if constexpr (scale == PhysicalScaleKind::mass) { return scales.mass.value(); } else if constexpr (scale == PhysicalScaleKind::force) { return scales.force; } else if constexpr (scale == PhysicalScaleKind::angular_velocity) { return scales.angularVelocity; } else { static_assert(scale == PhysicalScaleKind::angular_momentum); return scales.angularMomentum; } } namespace detail { template struct MakePhysicalRieszPlan; template struct MakePhysicalRieszPlan< utils::blocks::type_list, utils::blocks::type_list> { using Type = NormalizationPlan< CoordinateComponent< CoordinateKind::value, utils::blocks::type_list, typename PhysicalRieszBlockTraits::Method>..., CoordinateComponent< CoordinateKind::residual, utils::blocks::type_list, typename PhysicalRieszBlockTraits::Method>...>; }; } // namespace detail template requires utils::blocks::block_form_is_valid_v
using PhysicalRieszNormalizationPlanFor = typename detail::MakePhysicalRieszPlan< typename Form::value_blocks, typename Form::residual_blocks>::Type; /* * Public compile-time extension point for a normalization prescription. * A specialization owns both the complete coordinate plan and the * low-level runtime compatibility predicate used before a discretized * problem type is formed. Keeping those declarations together prevents a * policy from compiling a plan which the selected stellar core cannot * actually prepare. */ template struct NormalizationCompilation { using Plan = NormalizationPlan<>; static constexpr bool registered = false; template static constexpr bool runtimeAvailableFor = false; }; /* Astronomy/numerics-facing package for a policy which prepares one * runtime diagonal over the complete inferred form and needs no private * facility of a particular stellar core. The generated plan truthfully * labels every coordinate as runtime-prepared by this exact policy. */ template requires utils::blocks::block_form_is_valid_v struct RuntimePreparedNormalizationCompilation { using Plan = RuntimePreparedNormalizationPlanFor; static constexpr bool registered = CompleteNormalizationFor; template static constexpr bool runtimeAvailableFor = registered; }; template requires utils::blocks::block_form_is_valid_v struct NormalizationCompilation { using Plan = IdentityNormalizationPlanFor; static constexpr bool registered = CompleteNormalizationFor; template static constexpr bool runtimeAvailableFor = registered; }; template requires utils::blocks::block_form_is_valid_v struct NormalizationCompilation, Form> { using Plan = PhysicalRieszNormalizationPlanFor; static constexpr bool registered = CompleteNormalizationFor; template static constexpr bool runtimeAvailableFor = registered && PhysicalRieszCoreRuntime> && detail::SpecificationSetPhysicalRieszRuntimeCoverage< std::remove_cvref_t>::value; }; namespace detail { template struct NormalizationCompilationAudit { using Plan = NormalizationPlan<>; static constexpr bool registered = false; }; template requires NormalizationPrescription> && utils::blocks::block_form_is_valid_v> struct NormalizationCompilationAudit< Prescription, Form, std::void_t< typename NormalizationCompilation< std::remove_cvref_t, std::remove_cvref_t>::Plan, decltype(std::bool_constant( NormalizationCompilation< std::remove_cvref_t, std::remove_cvref_t>::registered)>{})>> { using Compilation = NormalizationCompilation< std::remove_cvref_t, std::remove_cvref_t>; using Plan = typename Compilation::Plan; static constexpr bool registered = static_cast(Compilation::registered) && CompleteNormalizationFor>; }; } // namespace detail template using NormalizationPlanFor = typename detail::NormalizationCompilationAudit< std::remove_cvref_t, Form>::Plan; template concept CompilableNormalizationFor = detail::NormalizationCompilationAudit< std::remove_cvref_t, std::remove_cvref_t>::registered; /* The public runtime-preparation adapter is intentionally narrower than * an arbitrary complete plan: every coordinate must name the exact policy * which supplies its runtime factor. This prevents a custom policy from * advertising IdentityCoordinate (or another policy's method) while * silently installing a different diagonal at runtime. */ template concept RuntimePreparedNormalizationFor = NormalizationPrescription> && utils::blocks::block_form_is_valid_v> && CompilableNormalizationFor< std::remove_cvref_t, std::remove_cvref_t> && std::same_as< NormalizationPlanFor< std::remove_cvref_t, std::remove_cvref_t>, RuntimePreparedNormalizationPlanFor< std::remove_cvref_t, std::remove_cvref_t>>; namespace detail { template < typename Prescription, typename Form, typename PhysicalCore, typename SpecificationTypes, typename = void> struct StellarNormalizationRuntimeAudit : std::false_type { }; template < typename Prescription, typename Form, typename PhysicalCore, typename SpecificationTypes> struct StellarNormalizationRuntimeAudit< Prescription, Form, PhysicalCore, SpecificationTypes, std::void_t< std::enable_if_t::registered>, decltype(std::bool_constant( NormalizationCompilation< Prescription, Form>::template runtimeAvailableFor< PhysicalCore, SpecificationTypes>)>{})>> : std::bool_constant< (std::same_as || PhysicalRieszDiagonalPrescription || RuntimePreparedNormalizationFor) && static_cast(NormalizationCompilation< Prescription, Form>::template runtimeAvailableFor< PhysicalCore, SpecificationTypes>)> { }; } // namespace detail /* * Single detection-safe authority for pairing a compiled stellar form, * its selected physical core, and a runtime normalization prescription. * Each public NormalizationCompilation specialization declares this * compatibility alongside its plan. The identity policy needs only a * complete plan. Physical Riesz also requires every map consumed during * assembly and global-scalar runtime preparation for every generated * coordinate in the specification pack. */ template < typename Prescription, typename Form, typename PhysicalCore, typename SpecificationTypes> concept StellarNormalizationRuntimeAvailableFor = detail::StellarNormalizationRuntimeAudit< std::remove_cvref_t, std::remove_cvref_t, std::remove_cvref_t, std::remove_cvref_t>::value; } // namespace mean_field::normalization