module; #include #include #include #include #include #include #include export module mean_field:seed.stellar_equilibrium_projection; export import :operators.stellar_equilibrium_problem; export import :physics.gravity; export import :seed.lane_emden; export namespace mean_field::seed { struct StellarEquilibriumProjectionOptions final { physics::GravitySolveOptions gravity{}; double surfaceRadiusRelativeTolerance{5.0e-4}; }; template struct ProjectedEquilibriumState final { using ModelType = std::remove_cvref_t; mfem::Vector values; }; /* * The public radial-projection extension boundary deliberately speaks in * physical state names. A specification author supplies one small rule * and opts in with * * using RadialProjection = seed::projection::Use; * * There is no registry ordinal and no model-combination specialization. */ struct RadialProjectionScales final { dimensions::MassValue targetMass; dimensions::LengthValue stellarRadius; double bernoulliConstant; double sphericalMomentOfInertia; const mfem::Array *surfaceCarrierRows; }; struct RadialProjectionState final { mfem::Vector density; mfem::Vector surfaceShape; mfem::Vector gravityGradient; mfem::Vector gravityPotential; mfem::Vector specificEnthalpy; }; namespace projection { template struct Use final { using PhysicsType = Physics; }; /* An explicit opt-in for a specification that leaves a radial seed unchanged. */ struct NoStateChange { static constexpr bool registered = true; static constexpr bool providesRadialMass = false; template static constexpr bool supports = true; template < typename Specification, typename Model> static void validate( const Specification &, const Model &, const RadialProfile &, const StellarEquilibriumProjectionOptions & ) noexcept { } template < typename Specification, typename Model> static void initialize( const Specification &, const Model &, const RadialProjectionScales &, RadialProjectionState &, mfem::Vector ) noexcept { } }; } // namespace projection namespace detail { struct ProjectedRadialFields final { mfem::Vector density; mfem::Vector gravityGradient; mfem::Vector gravityPotential; mfem::Vector specificEnthalpy; double bernoulliConstant; double sphericalMomentOfInertia; }; [[nodiscard]] ProjectedRadialFields projectRadialFields( fem::FEM &finiteElementModel, const RadialProfile &profile, dimensions::MassValue targetMass, const StellarEquilibriumProjectionOptions &options ); inline void assignProjectedBlock( mfem::Vector destination, const mfem::Vector &source, const char *name ) { if (destination.Size() != source.Size()) { throw std::invalid_argument(name); } destination = source; } struct UnavailableRadialProjectionPhysics final { static constexpr bool registered = false; static constexpr bool providesRadialMass = false; template static constexpr bool supports = false; }; struct FixedTotalMassRadialProjectionPhysics final { static constexpr bool registered = true; static constexpr bool providesRadialMass = true; template static constexpr bool supports = true; [[nodiscard]] static dimensions::MassValue targetMass(const models::FixedTotalMass &specification) { return specification.targetMass(); } template static void validate( const models::FixedTotalMass &, const Model &, const RadialProfile &, const StellarEquilibriumProjectionOptions & ) noexcept { } template static void initialize( const models::FixedTotalMass &, const Model &, const RadialProjectionScales &scales, RadialProjectionState &, mfem::Vector coordinate ) { if (coordinate.Size() != 1) { throw std::invalid_argument( "A fixed-total-mass radial projection requires exactly one generated multiplier." ); } coordinate(0) = scales.bernoulliConstant; } }; struct IsobaricRadialProjectionPhysics final { static constexpr bool registered = true; static constexpr bool providesRadialMass = false; template static constexpr bool supports = requires(const Model &model, const surface::Isobaric &condition) { { eos::evaluate( model.equationOfState(), condition.targetPressure() ) } -> std::same_as; }; template static void validate( const surface::Isobaric &condition, const Model &, const RadialProfile &, const StellarEquilibriumProjectionOptions & ) { if (condition.targetPressure().value() != 0.0) { throw std::invalid_argument("A Lane-Emden radial seed requires a zero-pressure isobaric surface."); } } template static void initialize( const surface::Isobaric &condition, const Model &model, const RadialProjectionScales &scales, RadialProjectionState &state, mfem::Vector coordinate ) { if (coordinate.Size() != 0) { throw std::logic_error("An isobaric surface must not generate a radial-seed coordinate."); } if (scales.surfaceCarrierRows == nullptr) { throw std::logic_error("An isobaric radial projection requires compiled surface-carrier rows."); } const dimensions::SpecificEnthalpyValue requiredSurfaceEnthalpy = eos::evaluate( model.equationOfState(), condition.targetPressure() ); for (const int surfaceRow : *scales.surfaceCarrierRows) { state.specificEnthalpy(surfaceRow) = requiredSurfaceEnthalpy.value(); } } }; struct FixedCentralDensityRadialProjectionPhysics final { static constexpr bool registered = true; static constexpr bool providesRadialMass = false; template static constexpr bool supports = true; template static void validate( const models::FixedCentralDensity &, const Model &, const RadialProfile &, const StellarEquilibriumProjectionOptions & ) noexcept { } template static void initialize( const models::FixedCentralDensity &, const Model &, const RadialProjectionScales &, RadialProjectionState &, mfem::Vector coordinate ) { if (coordinate.Size() != 1) { throw std::invalid_argument( "A fixed-central-density radial projection requires exactly one generated phase coordinate." ); } coordinate(0) = 0.0; } }; struct FixedAngularMomentumRadialProjectionPhysics final { static constexpr bool registered = true; static constexpr bool providesRadialMass = false; template static constexpr bool supports = true; template static void validate( const models::FixedAngularMomentum &, const Model &, const RadialProfile &, const StellarEquilibriumProjectionOptions & ) noexcept { } template static void initialize( const models::FixedAngularMomentum &constraint, const Model &, const RadialProjectionScales &scales, RadialProjectionState &, mfem::Vector coordinate ) { if (coordinate.Size() != 1) { throw std::logic_error( "A fixed-angular-momentum radial projection requires one angular-velocity coordinate." ); } double centerSquared = 0.0; double centerAlongAxis = 0.0; for (std::size_t component = 0; component < constraint.center().size(); ++component) { centerSquared += constraint.center()[component] * constraint.center()[component]; centerAlongAxis += constraint.center()[component] * constraint.axis()[component]; } const double parallelAxisCorrection = scales.targetMass.value() * (centerSquared - centerAlongAxis * centerAlongAxis); const double momentOfInertia = scales.sphericalMomentOfInertia + parallelAxisCorrection; if (!std::isfinite(momentOfInertia) || momentOfInertia <= 0.0) { throw std::runtime_error("The radial seed has no finite, positive axial moment of inertia."); } coordinate(0) = constraint.targetAngularMomentum().value() / momentOfInertia; } }; template struct BuiltinRadialProjectionPhysics { using Type = UnavailableRadialProjectionPhysics; }; template <> struct BuiltinRadialProjectionPhysics { using Type = projection::NoStateChange; }; template <> struct BuiltinRadialProjectionPhysics { using Type = IsobaricRadialProjectionPhysics; }; template <> struct BuiltinRadialProjectionPhysics { using Type = FixedTotalMassRadialProjectionPhysics; }; template <> struct BuiltinRadialProjectionPhysics { using Type = FixedCentralDensityRadialProjectionPhysics; }; template <> struct BuiltinRadialProjectionPhysics { using Type = FixedAngularMomentumRadialProjectionPhysics; }; template struct UnwrapRadialProjectionPhysics { using Type = UnavailableRadialProjectionPhysics; static constexpr bool valid = false; }; template struct UnwrapRadialProjectionPhysics> { using Type = Physics; static constexpr bool valid = true; }; template struct SelectRadialProjectionPhysics { using Type = typename BuiltinRadialProjectionPhysics::Type; }; template struct SelectRadialProjectionPhysics> { private: using Wrapped = UnwrapRadialProjectionPhysics; public: using Type = std::conditional_t; }; template [[nodiscard]] consteval bool radialProjectionPhysicsRegistered() { if constexpr (requires { { Physics::registered } -> std::convertible_to; }) { return static_cast(Physics::registered); } else { return false; } } template [[nodiscard]] consteval bool radialProjectionPhysicsProvidesMass() { if constexpr (requires { { Physics::providesRadialMass } -> std::convertible_to; }) { return static_cast(Physics::providesRadialMass); } else { return false; } } template < typename Specification, typename Model> [[nodiscard]] consteval bool radialProjectionPhysicsIsComplete() { using Physics = typename SelectRadialProjectionPhysics::Type; if constexpr (!radialProjectionPhysicsRegistered()) { return false; } else if constexpr (!requires { { Physics::template supports } -> std::convertible_to; }) { return false; } else if constexpr (!static_cast(Physics::template supports)) { return false; } else if constexpr (!requires( const Specification &specification, const Model &model, const RadialProfile &profile, const StellarEquilibriumProjectionOptions &options, const RadialProjectionScales &scales, RadialProjectionState &state, mfem::Vector coordinate ) { Physics::validate(specification, model, profile, options); Physics::initialize(specification, model, scales, state, coordinate); }) { return false; } else if constexpr (radialProjectionPhysicsProvidesMass()) { return requires(const Specification &specification) { { Physics::targetMass(specification) } -> std::same_as; }; } else { return true; } } template struct RadialProjectionCoordinateTerm; template struct RadialProjectionCoordinateTerm final { using value = utils::blocks::generated_value_block>; }; template struct RadialProjectionCoordinateTerm final { using value = utils::blocks::generated_value_block>; }; template struct RadialProjectionCoordinateTerm final { using value = utils::blocks::generated_value_block>; }; template struct CompileRadialProjection; template struct CompileRadialProjection> { using ModelType = std::remove_cvref_t; static constexpr std::size_t radialMassProviderCount = (std::size_t{0} + ... + (radialProjectionPhysicsProvidesMass::Type>() ? std::size_t{1} : std::size_t{0})); static constexpr bool complete = radialMassProviderCount == 1 && (radialProjectionPhysicsIsComplete() && ...); [[nodiscard]] static dimensions::MassValue targetMass(const ModelType &model) requires complete { dimensions::MassValue result{0.0}; ( [&] { using Physics = typename SelectRadialProjectionPhysics::Type; if constexpr (radialProjectionPhysicsProvidesMass()) { result = Physics::targetMass(model.template specification()); } }(), ...); return result; } static void validate( const ModelType &model, const RadialProfile &profile, const StellarEquilibriumProjectionOptions &options ) requires complete { ( [&] { using Physics = typename SelectRadialProjectionPhysics::Type; Physics::validate(model.template specification(), model, profile, options); }(), ...); } template static void initialize( const ModelType &model, const RadialProjectionScales &scales, RadialProjectionState &state, const StateView &stateView ) requires complete { ( [&] { using Contribution = models::SpecificationContribution; using Physics = typename SelectRadialProjectionPhysics::Type; if constexpr (Contribution::generatedValueArity == 0) { Physics::initialize( model.template specification(), model, scales, state, mfem::Vector{} ); } else { static_assert( Contribution::generatedValueArity == 1, "Radial projection currently requires each specification contribution to generate at " "most one scalar coordinate." ); using Term = RadialProjectionCoordinateTerm; Physics::initialize( model.template specification(), model, scales, state, stateView.block(Term{}) ); } }(), ...); } }; template > struct RadialProjectionCompilationAudit { static constexpr bool complete = false; }; template struct RadialProjectionCompilationAudit : CompileRadialProjection { }; } // namespace detail template inline constexpr bool radialProjectionIsCompilable = detail::RadialProjectionCompilationAudit>::complete; template concept RadialProfileProjectableModel = model::StellarModelType && radialProjectionIsCompilable>; template < equilibrium::StellarEquilibriumModel Model, equilibrium::StellarDiscretizationType Discretization> requires RadialProfileProjectableModel [[nodiscard]] ProjectedEquilibriumState projectRadialProfile( equilibrium::StellarEquilibriumProblem< Model, Discretization> &problem, const RadialProfile &profile, const StellarEquilibriumProjectionOptions &options = {} ) { using Projection = detail::CompileRadialProjection< std::remove_cvref_t, typename std::remove_cvref_t::SpecificationTypes>; const auto &stellarModel = problem.GetStellarModel(); Projection::validate(stellarModel, profile, options); const dimensions::MassValue targetMass = Projection::targetMass(stellarModel); const detail::ProjectedRadialFields fields = detail::projectRadialFields( equilibrium::detail::StellarEquilibriumProblemFactory::MutableFiniteElementModelForProjection(problem), profile, targetMass, options ); mfem::Vector values(problem.StateSize()); values = 0.0; const auto stateView = problem.GetManifest().stateView(values); detail::assignProjectedBlock( stateView.block(utils::blocks::density_field.mass_term), fields.density, "The projected density does not match the compiled equilibrium-state block." ); stateView.block(utils::blocks::surface_deformation_field.parameters_term) = 0.0; detail::assignProjectedBlock( stateView.block(utils::blocks::gravity_field.gradient_term), fields.gravityGradient, "The projected gravity gradient does not match the compiled equilibrium-state block." ); detail::assignProjectedBlock( stateView.block(utils::blocks::gravity_field.poisson_term), fields.gravityPotential, "The projected gravity potential does not match the compiled equilibrium-state block." ); detail::assignProjectedBlock( stateView.block(utils::blocks::enthalpy_field.specific_term), fields.specificEnthalpy, "The projected specific enthalpy does not match the compiled equilibrium-state block." ); /* * Each specification now initializes only its inferred contribution. * In particular, the surface rule imposes the exact carrier trace and * generated constraints obtain their coordinate by type, not by a * hard-coded whole-model layout. */ RadialProjectionState projectedState{ .density = stateView.block(utils::blocks::density_field.mass_term), .surfaceShape = stateView.block(utils::blocks::surface_deformation_field.parameters_term), .gravityGradient = stateView.block(utils::blocks::gravity_field.gradient_term), .gravityPotential = stateView.block(utils::blocks::gravity_field.poisson_term), .specificEnthalpy = stateView.block(utils::blocks::enthalpy_field.specific_term) }; const RadialProjectionScales scales{ .targetMass = targetMass, .stellarRadius = profile.stellarRadius, .bernoulliConstant = fields.bernoulliConstant, .sphericalMomentOfInertia = fields.sphericalMomentOfInertia, .surfaceCarrierRows = &problem.GetPressureSurfaceRows().reduced_dofs() }; Projection::initialize(stellarModel, scales, projectedState, stateView); return {.values = std::move(values)}; } template < equilibrium::StellarEquilibriumModel Model, equilibrium::StellarDiscretizationType Discretization, typename Strategy> requires RadialSeedStrategyFor< Strategy, typename equilibrium::StellarEquilibriumProblem< Model, Discretization>::ModelType> && RadialProfileProjectableModel [[nodiscard]] ProjectedEquilibriumState makeProjectedEquilibriumState( equilibrium::StellarEquilibriumProblem< Model, Discretization> &problem, const Strategy &strategy, const StellarEquilibriumProjectionOptions &options = {} ) { return projectRadialProfile(problem, generateRadialProfile(problem.GetStellarModel(), strategy), options); } } // namespace mean_field::seed