diff --git a/experiments/POLYNOMIAL_ENTHALPY_ADMISSIBILITY_2026-09-17.md b/experiments/POLYNOMIAL_ENTHALPY_ADMISSIBILITY_2026-09-17.md index 8bb5762..6ffff18 100644 --- a/experiments/POLYNOMIAL_ENTHALPY_ADMISSIBILITY_2026-09-17.md +++ b/experiments/POLYNOMIAL_ENTHALPY_ADMISSIBILITY_2026-09-17.md @@ -21,3 +21,5 @@ Tests cover nodal-positive/interior-negative cubics, a positive polynomial with A standalone extraction of the scalar/tensor certification engine was compiled with the system C++20 compiler and exercised before module integration. It certified a strictly positive quadratic with negative coarse coefficients and a three-dimensional zero-face bubble; it proved a negative pocket of depth 1e-10; it returned unresolved for an exact tangential zero. Production module compilation and MPI execution remain part of the integration campaign. Arithmetic requirements are enforced at compilation: `__FAST_MATH__` and a positive `__FINITE_MATH_ONLY__` are rejected, and static assertions require IEC 559 binary64 with subnormal support. Runtime floating-point modes must retain gradual underflow; the application must not enable flush-to-zero after construction. The existing Release build was inspected: `-O3 -g -DNDEBUG -std=c++23 -flto=thin -arch arm64`; no unsafe floating-point flags were present in the generated build commands. + +The stellar strategy explicitly supports only state-independent pressure-to-specific-enthalpy surface relations. That dependency is checked from the compiled relation type. Its constructor collectively rejects nonzero prescribed surface pressure with an unsupported-prescription error before checking seed coefficients, and verifies that the EOS's enthalpy reference satisfies h(P=0)=0. This prevents a valid positive-pressure model from being mislabeled an invalid numerical base. The focused regression validates zero pressure and collective rejection when only one MPI rank supplies positive pressure. diff --git a/libmeanfield/interface/solver/stellar_enthalpy_strategy.cppm b/libmeanfield/interface/solver/stellar_enthalpy_strategy.cppm index 034fc5d..7e5e857 100644 --- a/libmeanfield/interface/solver/stellar_enthalpy_strategy.cppm +++ b/libmeanfield/interface/solver/stellar_enthalpy_strategy.cppm @@ -18,6 +18,44 @@ export import :solver.enthalpy_admissibility; export import :normalization.stellar_equilibrium; export import :utils.domain; +export namespace mean_field::solver::detail { + template + concept PolynomialEnthalpySurfaceConstraint = requires { + typename SurfaceConstraint::CarrierQuantity; + typename SurfaceConstraint::Relation; + requires std::same_as; + requires std::same_as; + }; + + // The polynomial engine fixes homogeneous trace rows. Check the physical + // prescription before inspecting coefficients, so an unsupported surface + // condition is never reported as an invalid numerical starting state. + template + void RequirePolynomialEnthalpySurface(const SurfaceConstraint &surface, + const EquationOfState &equationOfState, MPI_Comm communicator) { + int localUnsupported = surface.targetPressure().value() == 0.0 ? 0 : 1; + int globalUnsupported = 0; + if (MPI_Allreduce(&localUnsupported,&globalUnsupported,1,MPI_INT,MPI_MAX,communicator) != MPI_SUCCESS) + throw std::runtime_error("Could not validate the polynomial enthalpy surface prescription."); + if (globalUnsupported) + throw std::invalid_argument("PolynomialEnthalpy supports only zero prescribed surface pressure (Psurf=0)."); + bool homogeneous = false; + try { + const double trace = eos::evaluate( + equationOfState,surface.targetPressure()).value(); + homogeneous = std::isfinite(trace) && trace == 0.0; + } catch (...) { + // A rank-local EOS exception must not strand other ranks in the + // collective compatibility check that follows. + } + localUnsupported = homogeneous ? 0 : 1; + if (MPI_Allreduce(&localUnsupported,&globalUnsupported,1,MPI_INT,MPI_MAX,communicator) != MPI_SUCCESS) + throw std::runtime_error("Could not validate the zero-pressure enthalpy reference."); + if (globalUnsupported) + throw std::invalid_argument("PolynomialEnthalpy requires the EOS to prescribe h(Psurf=0)=0."); + } +} + export namespace mean_field::solver { /* * The solver owns this workspace only when a positivity policy is selected. @@ -28,6 +66,8 @@ export namespace mean_field::solver { class PreparedStellarEnthalpyStrategy final { using Problem = std::remove_cvref_t().Problem())>; using Form = typename Problem::FormType; + static_assert(detail::PolynomialEnthalpySurfaceConstraint, + "PolynomialEnthalpy requires a state-independent specific-enthalpy pressure surface relation."); using Schema = utils::domain::CoreEnvelopeVacuumDomainSchema; using Domain = field::FieldDomainT; static constexpr int enthalpyIndex = utils::blocks::type_index_v< @@ -39,6 +79,8 @@ export namespace mean_field::solver { : m_state(&state), m_offset(state.Problem().GetManifest().layout().offset(enthalpyBlock)), m_size(state.Problem().GetManifest().layout().size(enthalpyBlock)) { + detail::RequirePolynomialEnthalpySurface(state.Problem().GetCompiledSurfaceConstraint(), + state.Problem().GetStellarModel().equationOfState(),state.Problem().GetCommunicator()); const auto &fem = equilibrium::detail::StellarEquilibriumProblemFactory::FiniteElementModel(state.Problem()); if (!fem.mesh || !fem.enthalpyFes) throw std::invalid_argument("Stellar enthalpy certification requires a complete enthalpy FE space."); diff --git a/tests/solver/enthalpy_admissibility.cpp b/tests/solver/enthalpy_admissibility.cpp index 98a430e..9a5672f 100644 --- a/tests/solver/enthalpy_admissibility.cpp +++ b/tests/solver/enthalpy_admissibility.cpp @@ -4,10 +4,12 @@ #include #include #include +#include #include #include #include +#include #include #include @@ -199,3 +201,18 @@ TEST_CASE("Feasible direction repair enforces exact homogeneous trace and reject CHECK(prepared.MinimumRepairFraction(invalidTrace,anchor,direction).status == EnthalpyDirectionRepairStatus::invalid_base); } + +TEST_CASE("Polynomial enthalpy validates the physical surface prescription before inspecting a state", + "[solver][enthalpy_admissibility][unit][parallel]") { + using namespace mean_field; + const eos::Polytrope equationOfState({.n=1.0,.K=1.0}); + const auto zero = surface::compilePressureSurfaceConstraint( + surface::Isobaric({.Psurf=dimensions::PressureValue{0.0}}),equationOfState); + STATIC_CHECK(solver::detail::PolynomialEnthalpySurfaceConstraint>); + REQUIRE_NOTHROW(solver::detail::RequirePolynomialEnthalpySurface(zero,equationOfState,MPI_COMM_WORLD)); + int rank = 0; MPI_Comm_rank(MPI_COMM_WORLD,&rank); + const auto positive = surface::compilePressureSurfaceConstraint( + surface::Isobaric({.Psurf=dimensions::PressureValue{rank == 0 ? 0.01 : 0.0}}),equationOfState); + CHECK_THROWS_WITH(solver::detail::RequirePolynomialEnthalpySurface(positive,equationOfState,MPI_COMM_WORLD), + "PolynomialEnthalpy supports only zero prescribed surface pressure (Psurf=0)."); +}