Reject unsupported pressure traces before polynomial state certification

This commit is contained in:
2026-09-17 10:15:57 -04:00
parent 6e812aeca2
commit 071417f0c1
3 changed files with 61 additions and 0 deletions

View File

@@ -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.

View File

@@ -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 <class SurfaceConstraint>
concept PolynomialEnthalpySurfaceConstraint = requires {
typename SurfaceConstraint::CarrierQuantity;
typename SurfaceConstraint::Relation;
requires std::same_as<typename SurfaceConstraint::CarrierQuantity,dimensions::quantity::SpecificEnthalpy>;
requires std::same_as<typename SurfaceConstraint::Relation,eos::SpecificEnthalpyFromPressure>;
};
// 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 <PolynomialEnthalpySurfaceConstraint SurfaceConstraint, class EquationOfState>
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<dimensions::quantity::SpecificEnthalpy>(
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<decltype(std::declval<State &>().Problem())>;
using Form = typename Problem::FormType;
static_assert(detail::PolynomialEnthalpySurfaceConstraint<typename Problem::CompiledSurfaceConstraintType>,
"PolynomialEnthalpy requires a state-independent specific-enthalpy pressure surface relation.");
using Schema = utils::domain::CoreEnvelopeVacuumDomainSchema;
using Domain = field::FieldDomainT<field::Enthalpy>;
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.");

View File

@@ -4,10 +4,12 @@
#include <limits>
#include <numeric>
#include <span>
#include <type_traits>
#include <vector>
#include <catch2/catch_approx.hpp>
#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers.hpp>
#include <mfem.hpp>
#include <mpi.h>
@@ -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::BarotropicSurfaceFormulation>(
surface::Isobaric({.Psurf=dimensions::PressureValue{0.0}}),equationOfState);
STATIC_CHECK(solver::detail::PolynomialEnthalpySurfaceConstraint<std::remove_cvref_t<decltype(zero)>>);
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::BarotropicSurfaceFormulation>(
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).");
}