feat(solver/seed): Initial guess system

the seed system has two jobs, get the lane-emden solution for a reasonable n and project that onto the mesh. Note that the seed system does not solve lane-emden, that is in utils/laneemden (so that it can be reused by other systems). Currently SERiF only solves polytrope so the lane-emden initial guess is generally very good; however, as we move to non polytropic EOS we will still use a projected lane-emden as our initial guess for the starting model
This commit is contained in:
2026-11-09 13:35:24 -05:00
parent 9f4207bc6a
commit ac2cccda29
5 changed files with 260 additions and 0 deletions

View File

@@ -0,0 +1,50 @@
#pragma once
#include "serif/solver/seed/polytrope.hpp"
namespace serif::solver::seed {
/**
* @brief An explicitly stretched initial guess, distinct from an equilibrium Lane–Emden scaling.
* @note The source EOS, density and enthalpy stay unchanged at corresponding fractional radii.
* Gravity follows the stretched density; this generally breaks hydrostatic balance, intentionally.
*/
struct FittedRadialSeed final {
RadialSeed source;
dimensions::LengthValue radius;
dimensions::MassValue mass;
double moment_of_inertia{};
double angular_velocity{};
};
/** @brief Evaluate the original equilibrium profile without changing its physical scaling. */
[[nodiscard]] inline auto evaluate_radial_seed(const RadialSeed& seed, dimensions::LengthValue radius) {
return utils::laneemden::evaluate_physical(seed.solution, seed.scales, radius);
}
/**
* @brief Copy a radial seed onto another radius without changing its material thermodynamics.
* @note For stretch s, enclosed mass scales as s^3, potential as s^2, gravity as s and inertia as s^5.
* Omega becomes Omega_source/s^5, preserving the angular momentum inferred from the source guess.
* The original PhysicalScales remain intact; no equilibrium or target-constraint satisfaction is implied.
*/
[[nodiscard]] std::expected<FittedRadialSeed, std::string> fit_radial_seed(const RadialSeed& seed, dimensions::LengthValue radius);
/** @brief Evaluate the fitted density and its consistent spherical gravity, retaining the source enthalpy. */
[[nodiscard]] std::expected<utils::laneemden::PhysicalSample, std::string> evaluate_radial_seed(
const FittedRadialSeed& seed, dimensions::LengthValue radius);
namespace detail {
/** @brief Inspect the unchanged physical source of a strict seed. */
[[nodiscard]] inline const RadialSeed& radial_source(const RadialSeed& seed) { return seed; }
/** @brief Inspect the unchanged physical source of an explicitly fitted guess. */
[[nodiscard]] inline const RadialSeed& radial_source(const FittedRadialSeed& seed) { return seed.source; }
/** @brief Use the equilibrium radius for ordinary projection. */
[[nodiscard]] inline dimensions::LengthValue radial_radius(const RadialSeed& seed) { return seed.scales.radius; }
/** @brief Use the explicit destination radius for fitted projection. */
[[nodiscard]] inline dimensions::LengthValue radial_radius(const FittedRadialSeed& seed) { return seed.radius; }
/** @brief Retain the original spherical mass for ordinary projection. */
[[nodiscard]] inline dimensions::MassValue radial_mass(const RadialSeed& seed) { return seed.scales.mass; }
/** @brief Use the mass of the stretched density for fitted gravity projection. */
[[nodiscard]] inline dimensions::MassValue radial_mass(const FittedRadialSeed& seed) { return seed.mass; }
}
}

View File

@@ -0,0 +1,54 @@
#pragma once
#include <array>
#include <concepts>
#include <expected>
#include <optional>
#include <string>
#include "serif/eos/models/polytropic.hpp"
#include "serif/model/stellar_model.hpp"
#include "serif/model/specifications/fixed_total_mass.hpp"
#include "serif/model/specifications/fixed_angular_momentum.hpp"
#include "serif/utils/laneemden/laneemden.hpp"
namespace serif::solver::seed {
/** @brief Numerical choices for an EOS-consistent spherical starting guess, separate from solver normalization. */
struct PolytropeOptions final {
dimensions::QuantityValue<dimensions::GravitationalConstant> gravitational_constant{1.0};
std::optional<dimensions::DensityValue> central_density;
utils::laneemden::IntegrationOptions integration;
};
/** @brief Own a spherical physical profile and its first rotation estimate without changing the stellar model. */
struct RadialSeed final {
utils::laneemden::Solution solution;
utils::laneemden::PhysicalScales scales;
double moment_of_inertia{};
double angular_velocity{};
std::array<double, 3> center{};
};
/**
* @brief Fix the EOS K and requested mass, solving for central density when n differs from three.
* @note At n=3 mass does not determine central density: supply that homology scale explicitly.
* An explicit density specifies the initial guess only; its mass need not satisfy the final constraint.
*/
[[nodiscard]] std::expected<RadialSeed, std::string> make_polytropic_seed(
const eos::models::Polytrope& equation_of_state, dimensions::MassValue mass, PolytropeOptions options = {});
/** @brief Read the authoritative EOS, mass and optional rotation prescription from a model. */
template <model::StellarModelType Model>
requires std::same_as<typename Model::EquationOfState, eos::models::Polytrope> && Model::template contains_specification<model::FixedTotalMass>
[[nodiscard]] std::expected<RadialSeed, std::string> make_polytropic_seed(const Model& model, PolytropeOptions options = {}) {
auto seed = make_polytropic_seed(model.equation_of_state(), model.template specification<model::FixedTotalMass>().target(), options);
if (seed) {
if constexpr (Model::template contains_specification<model::FixedAngularMomentum>) {
const auto& rotation = model.template specification<model::FixedAngularMomentum>();
seed->angular_velocity = rotation.target().value() / seed->moment_of_inertia;
seed->center = rotation.center();
}
}
return seed;
}
}

View File

@@ -0,0 +1,9 @@
#pragma once
#include "serif/backend/mfem/solver/seed/state.hpp"
#include "serif/backend/mfem/solver/seed/fitted.hpp"
namespace serif::solver::seed {
using backend::mfem::make_polytropic_state;
using backend::mfem::fit_radial_seed;
}