2026-09-16 14:34:06 -04:00
# include <algorithm>
# include <chrono>
# include <cmath>
# include <filesystem>
# include <fstream>
# include <iomanip>
# include <iostream>
# include <limits>
# include <map>
# include <mfem.hpp>
# include <numbers>
# include <span>
2026-09-17 08:06:37 -04:00
# include <sstream>
2026-09-16 14:34:06 -04:00
# include <stdexcept>
# include <string>
# include <vector>
import mean_field ;
# include "plateau_enthalpy_step_diagnostics.hpp"
# include "plateau_physical_diagnostics.hpp"
# include "plateau_preconditioner_diagnostics.hpp"
2026-09-17 16:16:46 -04:00
# include "plateau_preconditioner_benchmark.hpp"
2026-09-17 17:05:44 -04:00
# include "plateau_newton_preconditioner.hpp"
2026-09-17 08:18:09 -04:00
# include "plateau_pressure_obstruction_diagnostics.hpp"
# include "plateau_shape_curvature_diagnostics.hpp"
2026-09-17 09:45:05 -04:00
# include "plateau_numerical_strategy_diagnostics.hpp"
2026-09-18 08:26:51 -04:00
# include "plateau_nonlinear_step_diagnostics.hpp"
2026-09-17 10:19:17 -04:00
# include "plateau_gravity_application_diagnostics.hpp"
2026-09-17 09:45:05 -04:00
# include "plateau_positivity_strategy_diagnostics.hpp"
2026-09-17 10:51:07 -04:00
# include "plateau_polynomial_certificate.hpp"
2026-09-16 14:34:06 -04:00
using namespace mean_field ;
namespace {
using Clock = std : : chrono : : steady_clock ;
struct Options {
double n = 1. , angularMomentum = 0. , atol = 0. , rtol = 1e-8 , linearTolerance = .03 ;
2026-09-16 14:42:41 -04:00
int maxNewton = 12 , maxLinear = 200 , restart = 40 , probeRestart = 40 ;
2026-09-16 14:34:06 -04:00
bool probes = false , noCentral = false ;
2026-09-17 10:51:07 -04:00
bool strategyProbe = false , positivityProbe = false , positivityCertificate = false ;
2026-09-18 08:26:51 -04:00
bool preconditionerProbe = false , nonlinearStepProbe = false ;
2026-09-17 16:16:46 -04:00
std : : string preconditionerVariant = " independent " ;
2026-09-17 16:23:13 -04:00
int schurCorrectionSweeps = 1 ;
double schurRelaxation = 0.5 ;
2026-09-17 09:45:05 -04:00
std : : string strategy = " baseline " ;
double gravityTolerance = 1e-10 ;
int gravityIterations = 300 , gravityRestart = 80 ;
2026-09-17 08:35:49 -04:00
bool pressureProbe = false , shapeCurvature = false , surfaceTrace = false ;
2026-09-17 08:06:37 -04:00
std : : vector < int > captureIterations ;
2026-09-16 14:34:06 -04:00
std : : string mesh = " sandbox.smesh " ;
2026-09-17 08:18:09 -04:00
std : : filesystem : : path output , probeState , probeDirection , replayCandidate ;
2026-09-16 14:34:06 -04:00
} ;
Options Parse ( int argc , char * * argv ) {
Options o ;
for ( int i = 1 ; i < argc ; + + i ) {
std : : string a = argv [ i ] ;
auto v = [ & ] ( ) {
if ( + + i > = argc )
throw std : : invalid_argument ( " Missing value " ) ;
return std : : string ( argv [ i ] ) ;
} ;
if ( a = = " --index " )
o . n = std : : stod ( v ( ) ) ;
else if ( a = = " --angular-momentum " )
o . angularMomentum = std : : stod ( v ( ) ) ;
else if ( a = = " --output " )
o . output = v ( ) ;
else if ( a = = " --mesh " )
o . mesh = v ( ) ;
else if ( a = = " --max-newton " )
o . maxNewton = std : : stoi ( v ( ) ) ;
else if ( a = = " --max-linear " )
o . maxLinear = std : : stoi ( v ( ) ) ;
else if ( a = = " --restart " )
o . restart = std : : stoi ( v ( ) ) ;
2026-09-16 14:42:41 -04:00
else if ( a = = " --probe-restart " )
o . probeRestart = std : : stoi ( v ( ) ) ;
2026-09-16 14:34:06 -04:00
else if ( a = = " --linear-tol " )
o . linearTolerance = std : : stod ( v ( ) ) ;
else if ( a = = " --atol " )
o . atol = std : : stod ( v ( ) ) ;
else if ( a = = " --rtol " )
o . rtol = std : : stod ( v ( ) ) ;
else if ( a = = " --probe-state " )
o . probeState = v ( ) ;
2026-09-17 08:18:09 -04:00
else if ( a = = " --probe-direction " )
o . probeDirection = v ( ) ;
else if ( a = = " --replay-candidate " )
o . replayCandidate = v ( ) ;
2026-09-17 09:45:05 -04:00
else if ( a = = " --strategy " )
o . strategy = v ( ) ;
else if ( a = = " --gravity-tol " )
o . gravityTolerance = std : : stod ( v ( ) ) ;
else if ( a = = " --gravity-iterations " )
o . gravityIterations = std : : stoi ( v ( ) ) ;
else if ( a = = " --gravity-restart " )
o . gravityRestart = std : : stoi ( v ( ) ) ;
else if ( a = = " --strategy-probe " )
o . strategyProbe = true ;
else if ( a = = " --positivity-probe " )
o . positivityProbe = true ;
2026-09-17 10:51:07 -04:00
else if ( a = = " --positivity-certificate " )
o . positivityCertificate = true ;
2026-09-17 16:16:46 -04:00
else if ( a = = " --preconditioner-probe " )
o . preconditionerProbe = true ;
2026-09-18 08:26:51 -04:00
else if ( a = = " --nonlinear-step-probe " )
o . nonlinearStepProbe = true ;
2026-09-17 16:16:46 -04:00
else if ( a = = " --preconditioner-variant " )
o . preconditionerVariant = v ( ) ;
2026-09-17 16:23:13 -04:00
else if ( a = = " --schur-sweeps " )
o . schurCorrectionSweeps = std : : stoi ( v ( ) ) ;
else if ( a = = " --schur-relaxation " )
o . schurRelaxation = std : : stod ( v ( ) ) ;
2026-09-17 08:18:09 -04:00
else if ( a = = " --pressure-probe " )
o . pressureProbe = true ;
else if ( a = = " --shape-curvature " )
o . shapeCurvature = true ;
2026-09-17 08:35:49 -04:00
else if ( a = = " --surface-trace " )
o . surfaceTrace = true ;
2026-09-16 14:34:06 -04:00
else if ( a = = " --probes " )
o . probes = true ;
else if ( a = = " --no-central " )
o . noCentral = true ;
2026-09-17 08:06:37 -04:00
else if ( a = = " --capture-iterations " ) {
std : : istringstream entries ( v ( ) ) ;
std : : string entry ;
while ( std : : getline ( entries , entry , ' , ' ) ) {
size_t used = 0 ;
const int iteration = std : : stoi ( entry , & used ) ;
if ( used ! = entry . size ( ) | | iteration < 0 )
throw std : : invalid_argument ( " Invalid capture iteration " ) ;
o . captureIterations . push_back ( iteration ) ;
}
if ( o . captureIterations . empty ( ) )
throw std : : invalid_argument ( " Empty capture iteration list " ) ;
}
2026-09-16 14:34:06 -04:00
else
throw std : : invalid_argument ( " Unknown option: " + a ) ;
}
if ( o . output . empty ( ) | | o . n < 1 | | o . n > = 5 | | o . maxNewton < 0 | | o . maxLinear < 1 | | o . restart < 1 )
throw std : : invalid_argument ( " Invalid options " ) ;
2026-09-16 14:42:41 -04:00
if ( o . probeRestart ! = 40 & & o . probeRestart ! = 160 )
throw std : : invalid_argument ( " --probe-restart must be 40 or 160 " ) ;
2026-09-17 09:45:05 -04:00
if ( ( o . pressureProbe | | o . shapeCurvature | | o . surfaceTrace | | o . strategyProbe | | o . positivityProbe ) & & ( o . probeState . empty ( ) | | o . probeDirection . empty ( ) ) )
2026-09-17 08:18:09 -04:00
throw std : : invalid_argument ( " Focused probes require --probe-state and --probe-direction " ) ;
2026-09-17 10:51:07 -04:00
if ( o . positivityCertificate & & o . probeState . empty ( ) )
throw std : : invalid_argument ( " --positivity-certificate requires --probe-state " ) ;
if ( o . positivityCertificate & & ( o . probes | | o . pressureProbe | | o . shapeCurvature | | o . surfaceTrace | |
2026-09-18 08:26:51 -04:00
o . strategyProbe | | o . positivityProbe | | o . preconditionerProbe | | o . nonlinearStepProbe | | ! o . probeDirection . empty ( ) | | ! o . replayCandidate . empty ( ) ) )
2026-09-17 10:51:07 -04:00
throw std : : invalid_argument ( " --positivity-certificate is a certificate-only mode; omit other probe options " ) ;
2026-09-17 16:16:46 -04:00
if ( o . preconditionerProbe ) {
experiment : : plateau_diagnostics : : ParsePreconditionerBenchmarkVariant ( o . preconditionerVariant ) ;
if ( o . maxNewton ! = 0 | | o . probes | | o . pressureProbe | | o . shapeCurvature | | o . surfaceTrace | |
2026-09-18 08:26:51 -04:00
o . strategyProbe | | o . positivityProbe | | o . positivityCertificate | | o . nonlinearStepProbe | | ! o . probeDirection . empty ( ) | |
2026-09-17 16:16:46 -04:00
! o . replayCandidate . empty ( ) | | ! o . captureIterations . empty ( ) | | o . strategy ! = " baseline " )
throw std : : invalid_argument ( " --preconditioner-probe requires --max-newton 0 and no nonlinear/probe strategies " ) ;
if ( ! std : : isfinite ( o . linearTolerance ) | | o . linearTolerance < = 0.0 )
throw std : : invalid_argument ( " A preconditioner benchmark requires a finite positive --linear-tol " ) ;
}
2026-09-18 08:26:51 -04:00
if ( o . nonlinearStepProbe ) {
if ( o . maxNewton ! = 0 | | o . probes | | o . pressureProbe | | o . shapeCurvature | | o . surfaceTrace | |
o . strategyProbe | | o . positivityProbe | | o . positivityCertificate | | o . preconditionerProbe | |
! o . probeDirection . empty ( ) | | ! o . replayCandidate . empty ( ) | | ! o . captureIterations . empty ( ) | |
o . strategy ! = " baseline " )
throw std : : invalid_argument ( " --nonlinear-step-probe requires --max-newton 0 and no other probes or strategy; it compares raw and curvature paths itself " ) ;
if ( ! std : : isfinite ( o . linearTolerance ) | | o . linearTolerance < = 0.0 )
throw std : : invalid_argument ( " A nonlinear-step probe requires a finite positive --linear-tol " ) ;
}
2026-09-17 17:05:44 -04:00
if ( ! o . preconditionerProbe ) {
if ( o . preconditionerVariant ! = " independent " & & o . preconditionerVariant ! = " gravity-schur " & &
2026-09-17 17:36:48 -04:00
o . preconditionerVariant ! = " stellar-ldu " & & o . preconditionerVariant ! = " stellar-ldu-gravity-schur " )
throw std : : invalid_argument ( " Newton preconditioner variants are independent, gravity-schur, stellar-ldu, or stellar-ldu-gravity-schur " ) ;
2026-09-17 17:05:44 -04:00
if ( o . preconditionerVariant ! = " independent " ) {
if ( o . probes | | o . pressureProbe | | o . shapeCurvature | | o . surfaceTrace | | o . strategyProbe | |
2026-09-18 08:26:51 -04:00
o . positivityProbe | | o . positivityCertificate | | ( ! o . probeState . empty ( ) & & ! o . nonlinearStepProbe ) | | ! o . probeDirection . empty ( ) | |
2026-09-17 17:05:44 -04:00
! o . replayCandidate . empty ( ) )
throw std : : invalid_argument ( " A custom Newton preconditioner cannot be combined with frozen or certificate probes " ) ;
if ( o . strategy ! = " baseline " & & o . strategy ! = " positivity " & & o . strategy ! = " curvature " & &
o . strategy ! = " positivity-curvature " )
throw std : : invalid_argument ( " Custom Newton preconditioners support baseline, positivity, curvature, or positivity-curvature " ) ;
if ( ! std : : isfinite ( o . linearTolerance ) | | o . linearTolerance < = 0.0 | | o . schurCorrectionSweeps < 0 | |
! std : : isfinite ( o . schurRelaxation ) | | o . schurRelaxation < = 0.0 )
throw std : : invalid_argument ( " Invalid custom Newton preconditioner controls " ) ;
}
}
2026-09-17 08:18:09 -04:00
if ( ( ! o . probeDirection . empty ( ) | | ! o . replayCandidate . empty ( ) ) & & o . probeState . empty ( ) )
throw std : : invalid_argument ( " A supplied direction or candidate requires --probe-state " ) ;
2026-09-17 09:45:05 -04:00
if ( o . strategy ! = " baseline " & & o . strategy ! = " positivity " & & o . strategy ! = " curvature " & &
o . strategy ! = " reduced " & & o . strategy ! = " positivity-curvature " & & o . strategy ! = " positivity-reduced " )
throw std : : invalid_argument ( " Unknown numerical strategy " ) ;
if ( ! std : : isfinite ( o . gravityTolerance ) | | o . gravityTolerance < = 0.0 | | o . gravityIterations < 1 | | o . gravityRestart < 1 )
throw std : : invalid_argument ( " Invalid gravity solve controls " ) ;
2026-09-16 14:34:06 -04:00
if ( std : : filesystem : : exists ( o . output ) )
throw std : : runtime_error ( " Output directory already exists " ) ;
std : : filesystem : : create_directories ( o . output ) ;
return o ;
}
std : : ofstream File ( const std : : filesystem : : path & p ) {
std : : ofstream s ( p ) ;
s . exceptions ( std : : ios : : failbit | std : : ios : : badbit ) ;
s < < std : : setprecision ( 17 ) ;
return s ;
}
double Norm ( std : : span < const mfem : : real_t > v , int offset , int size ) {
long double sum = 0 ;
for ( int i = offset ; i < offset + size ; + + i )
sum + = ( long double ) v [ i ] * v [ i ] ;
return std : : sqrt ( sum ) ;
}
double Norm ( const mfem : : Vector & v , int offset , int size ) {
return Norm ( std : : span < const mfem : : real_t > { v . GetData ( ) , ( size_t ) v . Size ( ) } , offset , size ) ;
}
struct Scales {
double K , rho , xi , mu ;
} ;
Scales Derive ( double n ) {
if ( n = = 1. )
return { 2. * utils : : G / std : : numbers : : pi , std : : numbers : : pi / 4. , std : : numbers : : pi , std : : numbers : : pi } ;
auto le = seed : : integrateLaneEmden ( n , 2000. , 1e-3 ) ;
if ( ! le . firstZeroCoordinate )
throw std : : runtime_error ( " No Lane Emden root " ) ;
const double xi = * le . firstZeroCoordinate , mu = - xi * xi * le . thetaDerivative ( le . thetaDerivative . Size ( ) - 1 ) ;
const double rho = xi * xi * xi / ( 4 * std : : numbers : : pi * mu ) ;
return { 4 * std : : numbers : : pi * utils : : G * std : : pow ( rho , 1. - 1. / n ) / ( ( n + 1 ) * xi * xi ) , rho , xi , mu } ;
}
void SaveVector ( const std : : filesystem : : path & p , const mfem : : Vector & v ) {
auto s = File ( p ) ;
s < < v . Size ( ) < < ' \n ' ;
v . Print ( s , 1 ) ;
}
2026-09-17 08:18:09 -04:00
mfem : : Vector LoadVector ( const std : : filesystem : : path & path , int expectedSize ) {
std : : ifstream input ( path ) ;
int count = - 1 ;
input > > count ;
if ( ! input | | count ! = expectedSize )
throw std : : runtime_error ( " Snapshot size mismatch: " + path . string ( ) ) ;
mfem : : Vector result ( count ) ;
for ( int i = 0 ; i < count ; + + i ) {
input > > result ( i ) ;
if ( ! input | | ! std : : isfinite ( result ( i ) ) )
throw std : : runtime_error ( " Invalid snapshot: " + path . string ( ) ) ;
}
std : : string extra ;
if ( input > > extra )
throw std : : runtime_error ( " Unexpected trailing snapshot data: " + path . string ( ) ) ;
return result ;
}
template < class State >
void FocusedProbes ( State & s , const fem : : FEM & f , const Options & o , const mfem : : Vector & physicalBase ) {
mfem : : Vector base ;
s . normalizedOperator - > NormalizeState ( physicalBase , base ) ;
auto restore = [ & ] ( ) {
s . trialNormalizedState = base ;
if ( ! s . PrepareTrial ( ) )
throw std : : runtime_error ( " Focused probe base is inadmissible " ) ;
} ;
restore ( ) ;
const mfem : : Vector residual ( s . trialNormalizedResidual ) ;
SaveVector ( o . output / " frozen_physical_base.txt " , physicalBase ) ;
SaveVector ( o . output / " frozen_normalized_base.txt " , base ) ;
SaveVector ( o . output / " frozen_normalized_residual.txt " , residual ) ;
auto layout = File ( o . output / " layout.csv " ) ;
layout < < " kind,block,offset,size,scale \n " ;
for ( const auto & b : s . Problem ( ) . GetManifest ( ) . residualBlocks ( ) )
layout < < " residual, " < < b . stableId < < ' , ' < < b . offset < < ' , ' < < b . size < < ' , ' < < b . scale < < ' \n ' ;
for ( const auto & b : s . Problem ( ) . GetManifest ( ) . valueBlocks ( ) )
layout < < " value, " < < b . stableId < < ' , ' < < b . offset < < ' , ' < < b . size < < ' , ' < < b . scale < < ' \n ' ;
auto checks = File ( o . output / " frozen_input_checks.csv " ) ;
checks < < " metric,value \n base_residual_norm, " < < residual . Norml2 ( ) < < ' \n ' ;
const auto capturedBase = o . probeState . parent_path ( ) / " normalized_base.txt " ;
if ( std : : filesystem : : is_regular_file ( capturedBase ) ) {
mfem : : Vector difference = LoadVector ( capturedBase , base . Size ( ) ) ;
difference - = base ;
checks < < " captured_normalized_base_difference, " < < difference . Norml2 ( ) < < ' \n ' ;
if ( difference . Norml2 ( ) > 64. * std : : numeric_limits < double > : : epsilon ( ) * std : : max ( 1. , base . Norml2 ( ) ) )
throw std : : runtime_error ( " Frozen normalization disagrees with the captured base " ) ;
}
const auto capturedResidual = o . probeState . parent_path ( ) / " normalized_residual.txt " ;
if ( std : : filesystem : : is_regular_file ( capturedResidual ) ) {
mfem : : Vector difference = LoadVector ( capturedResidual , residual . Size ( ) ) ;
difference - = residual ;
checks < < " captured_residual_difference, " < < difference . Norml2 ( ) < < ' \n ' ;
}
if ( ! o . replayCandidate . empty ( ) ) {
s . trialNormalizedState = LoadVector ( o . replayCandidate , base . Size ( ) ) ;
const auto prepared = s . PrepareTrial ( ) ;
auto replay = File ( o . output / " replayed_candidate.csv " ) ;
replay < < " admissible,reason,stage,eos_code,minimum_jacobian,residual_norm \n " ;
if ( prepared )
replay < < " 1,-1,-1,-1, " < < prepared - > minimumJacobianDeterminant < < ' , '
< < s . trialNormalizedResidual . Norml2 ( ) < < ' \n ' ;
else
replay < < " 0, " < < int ( prepared . error ( ) . reason ) < < ' , ' < < int ( prepared . error ( ) . stage ) < < ' , '
< < int ( prepared . error ( ) . thermodynamicErrorCode ) < < ' , '
< < prepared . error ( ) . minimumJacobianDeterminant < < " ,nan \n " ;
restore ( ) ;
}
if ( ! o . probeDirection . empty ( ) ) {
const auto direction = LoadVector ( o . probeDirection , base . Size ( ) ) ;
mfem : : Vector action ( base . Size ( ) ) ;
restore ( ) ;
s . normalizedOperator - > Mult ( direction , action ) ;
SaveVector ( o . output / " probe_normalized_correction.txt " , direction ) ;
checks < < " direction_norm, " < < direction . Norml2 ( ) < < " \n action_norm, " < < action . Norml2 ( ) < < ' \n ' ;
action + = residual ;
checks < < " linear_defect_norm, " < < action . Norml2 ( ) < < " \n linear_defect_relative, "
< < action . Norml2 ( ) / std : : max ( residual . Norml2 ( ) , 1e-300 ) < < ' \n ' ;
checks . flush ( ) ;
s . normalizedCorrection = direction ;
auto bounds = File ( o . output / " frozen_enthalpy_bound.csv " ) ;
bounds < < " metric,value \n " ;
for ( const auto & [ key , value ] : experiment : : plateau_diagnostics : : MeasureEnthalpyStep ( s , f , o . n , & physicalBase ) )
bounds < < key < < ' , ' < < value < < ' \n ' ;
bounds . flush ( ) ;
2026-09-17 10:19:17 -04:00
if ( o . strategyProbe ) {
s . physicalInverse - > Refresh ( ) ;
using Form = typename std : : remove_cvref_t < decltype ( s . Problem ( ) ) > : : FormType ;
solver : : PreparedGravitySubsystem gravity ( * s . normalizedOperator , * s . normalizedInverse ,
solver : : MakeGravityBlockPartition < Form > ( s . Problem ( ) . GetManifest ( ) . layout ( ) ) ,
s . Problem ( ) . GetCommunicator ( ) ) ;
plateau_diagnostics : : ProbeGravityApplications ( gravity , o . output ) ;
2026-09-17 09:45:05 -04:00
experiment : : plateau_diagnostics : : ProbeNumericalStrategies ( s , f , base , direction , o . output ) ;
2026-09-17 10:19:17 -04:00
}
2026-09-17 09:45:05 -04:00
if ( o . positivityProbe )
experiment : : plateau_diagnostics : : ProbePolynomialEnthalpy ( s , f , base , direction , o . output ) ;
2026-09-17 08:35:49 -04:00
if ( o . surfaceTrace )
experiment : : plateau_diagnostics : : ProbeSurfaceTrace ( s , f , o . n , base , direction , o . output ) ;
2026-09-17 08:18:09 -04:00
if ( o . pressureProbe )
experiment : : plateau_diagnostics : : ProbePressureObstruction ( s , f , o . n , base , direction , o . output ) ;
if ( o . shapeCurvature )
experiment : : plateau_diagnostics : : ProbeShapeCurvature ( s , f , o . n , base , direction , o . output ) ;
}
restore ( ) ;
mfem : : Vector difference ( s . trialNormalizedResidual ) ;
difference - = residual ;
checks < < " final_restoration_residual_difference, " < < difference . Norml2 ( ) < < ' \n ' ;
}
2026-09-16 14:34:06 -04:00
template < class State >
void FrozenProbes ( State & s , const fem : : FEM & f , const Options & o , const mfem : : Vector * physicalBase = nullptr ) {
auto rows = s . Problem ( ) . GetManifest ( ) . residualBlocks ( ) ;
mfem : : Vector frozenState ( s . acceptedNormalizedState ) ;
if ( physicalBase )
s . normalizedOperator - > NormalizeState ( * physicalBase , frozenState ) ;
auto restoreFrozen = [ & ] ( ) {
s . trialNormalizedState = frozenState ;
auto p = s . PrepareTrial ( ) ;
if ( ! p )
throw std : : runtime_error ( " Frozen diagnostic state is inadmissible " ) ;
} ;
restoreFrozen ( ) ;
s . physicalInverse - > Refresh ( ) ;
const mfem : : Vector base ( s . trialNormalizedResidual ) ;
if ( base . Norml2 ( ) = = 0. )
return ;
auto results = File ( o . output / " frozen_linear.csv " ) ;
results < < " restart,tolerance,iterations,converged,true_relative,seconds \n " ;
auto histories = File ( o . output / " frozen_history.csv " ) ;
histories < < " restart,tolerance,iteration,reported_norm,final \n " ;
auto blocks = File ( o . output / " frozen_blocks.csv " ) ;
blocks < < " restart,tolerance,block,F_norm,Jp_plus_F_norm,global_correction_norm \n " ;
2026-09-16 14:42:41 -04:00
mfem : : Vector selectedCorrection , baselineAction ( base . Size ( ) ) ;
// These are two cold direct MFEM solves, not the configured production
// backend or any recycled Krylov history.
2026-09-16 14:34:06 -04:00
for ( int restart : { 40 , 160 } ) {
mfem : : FGMRESSolver linear ( MPI_COMM_WORLD ) ;
solver : : ResidualHistoryMonitor monitor ;
linear . SetPreconditioner ( * s . normalizedInverse ) ;
linear . SetOperator ( * s . normalizedOperator ) ;
linear . SetKDim ( restart ) ;
linear . SetRelTol ( 0. ) ;
linear . SetAbsTol ( o . linearTolerance * base . Norml2 ( ) ) ;
linear . SetMaxIter ( o . maxLinear ) ;
linear . SetPrintLevel ( - 1 ) ;
linear . SetMonitor ( monitor ) ;
linear . iterative_mode = false ;
mfem : : Vector b ( base ) , p ( base . Size ( ) ) , action ( base . Size ( ) ) ;
b * = - 1. ;
p = 0. ;
const auto start = Clock : : now ( ) ;
linear . Mult ( b , p ) ;
double seconds = std : : chrono : : duration < double > ( Clock : : now ( ) - start ) . count ( ) ;
s . normalizedOperator - > Mult ( p , action ) ;
action + = base ;
const double rel = action . Norml2 ( ) / base . Norml2 ( ) ;
results < < restart < < ' , ' < < o . linearTolerance < < ' , ' < < linear . GetNumIterations ( ) < < ' , '
< < linear . GetConverged ( ) < < ' , ' < < rel < < ' , ' < < seconds < < ' \n ' ;
results . flush ( ) ;
for ( const auto & h : monitor . GetHistory ( ) )
histories < < restart < < ' , ' < < o . linearTolerance < < ' , ' < < h . iteration < < ' , ' < < h . reportedNorm < < ' , '
< < h . final < < ' \n ' ;
histories . flush ( ) ;
for ( const auto & r : rows )
blocks < < restart < < ' , ' < < o . linearTolerance < < ' , ' < < r . stableId < < ' , ' < < Norm ( base , r . offset , r . size )
< < ' , ' < < Norm ( action , r . offset , r . size ) < < ' , ' < < p . Norml2 ( ) < < ' \n ' ;
blocks . flush ( ) ;
std : : cout < < " Frozen restart= " < < restart < < " true= " < < rel < < " seconds= " < < seconds < < std : : endl ;
2026-09-16 14:42:41 -04:00
if ( restart = = o . probeRestart )
selectedCorrection = p ;
2026-09-16 14:34:06 -04:00
}
2026-09-16 14:42:41 -04:00
std : : cout < < " Frozen probe direction restart= " < < o . probeRestart < < std : : endl ;
s . normalizedCorrection = selectedCorrection ;
s . normalizedOperator - > Mult ( selectedCorrection , baselineAction ) ;
2026-09-16 14:34:06 -04:00
auto positivity = File ( o . output / " enthalpy_step_bound.csv " ) ;
positivity < < " metric,value \n " ;
for ( const auto & [ key , value ] : experiment : : plateau_diagnostics : : MeasureEnthalpyStep ( s , f , o . n , physicalBase ) )
positivity < < key < < ' , ' < < value < < ' \n ' ;
positivity . flush ( ) ;
2026-09-16 14:42:41 -04:00
SaveVector ( o . output / " probe_normalized_correction.txt " , selectedCorrection ) ;
2026-09-16 14:34:06 -04:00
auto fd = File ( o . output / " finite_differences.csv " ) ;
fd < < " epsilon,admissible,block,absolute_error,relative_to_Jp,action_norm \n " ;
const auto safe = s . EstimateLargestSafeStepSize ( 1. , .9 ) ;
if ( ! std : : isfinite ( safe . stepSize ) | | safe . stepSize < = 0. )
return ;
auto trials = File ( o . output / " frozen_trials.csv " ) ;
trials < < " alpha,admissible,rejection_reason,F_norm,merit_ratio \n " ;
// Diagnostic trials are never committed. Each samples the same frozen Newton direction.
for ( double a : { 1. , .5 , .1 , .01 , .001 , .0001 , .00001 } ) {
const double epsilon = std : : min ( 1. , safe . stepSize ) * a ;
s . trialNormalizedState = frozenState ;
2026-09-16 14:42:41 -04:00
s . trialNormalizedState . Add ( epsilon , selectedCorrection ) ;
2026-09-16 14:34:06 -04:00
const auto prep = s . PrepareTrial ( ) ;
if ( ! prep ) {
trials < < epsilon < < " ,0, " < < int ( prep . error ( ) . reason ) < < " ,nan,nan \n " ;
fd < < epsilon < < " ,0,all,nan,nan,nan \n " ;
} else {
trials < < epsilon < < " ,1,-1, " < < s . trialNormalizedResidual . Norml2 ( ) < < ' , '
< < std : : pow ( s . trialNormalizedResidual . Norml2 ( ) / base . Norml2 ( ) , 2 ) < < ' \n ' ;
mfem : : Vector error ( s . trialNormalizedResidual ) ;
error - = base ;
error / = epsilon ;
error - = baselineAction ;
for ( const auto & r : rows ) {
double e = Norm ( error , r . offset , r . size ) , j = Norm ( baselineAction , r . offset , r . size ) ;
fd < < epsilon < < " ,1, " < < r . stableId < < ' , ' < < e < < ' , ' < < e / std : : max ( j , 1e-300 ) < < ' , ' < < j < < ' \n ' ;
}
}
trials . flush ( ) ;
fd . flush ( ) ;
restoreFrozen ( ) ;
}
}
2026-09-17 17:05:44 -04:00
template < bool FullDefaultStack , class Context >
int RunContext ( Context & context , const Options & o , const Scales & scales ) {
2026-09-16 14:34:06 -04:00
std : : vector < operators : : RootBlockDescriptor > rows , values ;
2026-09-17 10:09:22 -04:00
mfem : : Vector residualFactors , stateFactors ;
2026-09-16 14:34:06 -04:00
std : : vector < int > centralRows ;
int centralOffset = - 1 ;
2026-09-17 17:36:48 -04:00
const bool contextUsesGravitySchur = ! o . preconditionerProbe & &
( o . preconditionerVariant = = " gravity-schur " | | o . preconditionerVariant = = " stellar-ldu-gravity-schur " ) ;
2026-09-16 14:34:06 -04:00
auto metadata = File ( o . output / " metadata.txt " ) ;
metadata < < " n= " < < o . n < < " \n K= " < < scales . K < < " \n rho_c= " < < scales . rho < < " \n J= " < < o . angularMomentum
< < " \n M=1 \n R_reference=1 \n xi1= " < < scales . xi < < " \n mu1= " < < scales . mu < < " \n central_phase= " < < ! o . noCentral
2026-09-16 14:42:41 -04:00
< < " \n restart= " < < o . restart < < " \n probe_restart= " < < o . probeRestart
2026-09-17 16:16:46 -04:00
< < " \n frozen_probe_backend= " < < ( o . preconditionerProbe ? " direct_mfem_fgmres_preconditioner_benchmark " :
2026-09-18 08:26:51 -04:00
o . nonlinearStepProbe ? " prepared_fgmres_cold_with_independent_true_gate " :
2026-09-17 16:16:46 -04:00
o . positivityCertificate ? " polynomial_certificate_no_linear_solve " :
2026-09-17 10:51:07 -04:00
o . probeDirection . empty ( ) ? " direct_mfem_fgmres_cold " : " supplied_direction_no_linear_solve " )
2026-09-16 14:42:41 -04:00
< < " \n linear_tol= " < < o . linearTolerance < < " \n max_linear= " < < o . maxLinear
2026-09-16 14:34:06 -04:00
< < " \n max_newton= " < < o . maxNewton < < " \n atol= " < < o . atol < < " \n rtol= " < < o . rtol
2026-09-17 09:45:05 -04:00
< < " \n strategy= " < < o . strategy < < " \n gravity_tolerance= " < < o . gravityTolerance
< < " \n gravity_iterations= " < < o . gravityIterations < < " \n gravity_restart= " < < o . gravityRestart
2026-09-17 16:16:46 -04:00
< < " \n preconditioner_probe= " < < o . preconditionerProbe
2026-09-18 08:26:51 -04:00
< < " \n nonlinear_step_probe= " < < o . nonlinearStepProbe
2026-09-17 16:16:46 -04:00
< < " \n preconditioner_variant= " < < o . preconditionerVariant
2026-09-17 17:05:44 -04:00
< < " \n context_preconditioner_variant= " < < ( o . preconditionerProbe ? " independent " : o . preconditionerVariant )
< < " \n context_gravity_schur_sweeps= "
2026-09-17 17:36:48 -04:00
< < ( contextUsesGravitySchur ? o . schurCorrectionSweeps : 0 )
< < " \n context_gravity_schur_relaxation= " < < ( contextUsesGravitySchur ? o . schurRelaxation : 0.0 )
2026-09-17 16:23:13 -04:00
< < " \n schur_correction_sweeps= " < < o . schurCorrectionSweeps < < " \n schur_relaxation= " < < o . schurRelaxation
2026-09-16 14:34:06 -04:00
< < " \n compiler= " < < __VERSION__ < < " \n compiled= " < < __DATE__ < < ' ' < < __TIME__ < < ' \n ' ;
metadata . flush ( ) ;
2026-09-18 08:26:51 -04:00
if ( o . nonlinearStepProbe ) {
metadata < < " nonlinear_step_input= " < < ( o . probeState . empty ( ) ? " lane_emden_seed " : " physical_snapshot " )
< < " \n nonlinear_step_paths=raw,curvature \n nonlinear_step_positivity_repair=0 \n " ;
if ( ! o . probeState . empty ( ) ) metadata < < " probe_state= " < < o . probeState < < ' \n ' ;
metadata . flush ( ) ;
solver : : detail : : StellarEquilibriumContextDiagnostics : : WithState ( context , [ & ] ( auto & s , const fem : : FEM & ) {
mfem : : Vector base ( s . acceptedNormalizedState ) ;
if ( ! o . probeState . empty ( ) ) {
const auto snapshot = LoadVector ( o . probeState , s . AcceptedPhysicalState ( ) . Size ( ) ) ;
s . normalizedOperator - > NormalizeState ( snapshot , base ) ;
}
experiment : : plateau_diagnostics : : ProbeNonlinearStep ( s , base , o . output , {
. linear = { . relativeTolerance = o . linearTolerance , . absoluteTolerance = 0. ,
. maximumIterations = o . maxLinear } ,
. restartLength = o . restart ,
. gravity = { . control = { . relativeTolerance = o . gravityTolerance , . absoluteTolerance = 1e-14 ,
. maximumIterations = o . gravityIterations } ,
. backend = { . restartLength = o . gravityRestart , . printLevel = - 1 } } } ) ;
} ) ;
return 0 ;
}
2026-09-17 17:05:44 -04:00
// Custom Newton recipes do not instantiate the nine-variant frozen suite.
if constexpr ( FullDefaultStack ) if ( o . preconditionerProbe ) {
2026-09-17 16:16:46 -04:00
metadata < < " benchmark_input= " < < ( o . probeState . empty ( ) ? " lane_emden_seed " : " physical_snapshot " ) < < ' \n ' ;
if ( ! o . probeState . empty ( ) ) metadata < < " probe_state= " < < o . probeState < < ' \n ' ;
solver : : detail : : StellarEquilibriumContextDiagnostics : : WithState ( context , [ & ] ( auto & s , const fem : : FEM & ) {
if ( o . probeState . empty ( ) ) {
s . trialNormalizedState = s . acceptedNormalizedState ;
} else {
const auto snapshot = LoadVector ( o . probeState , s . AcceptedPhysicalState ( ) . Size ( ) ) ;
s . normalizedOperator - > NormalizeState ( snapshot , s . trialNormalizedState ) ;
}
const auto preparationStart = Clock : : now ( ) ;
if ( ! s . PrepareTrial ( ) )
throw std : : runtime_error ( " Preconditioner benchmark state is inadmissible to the physical operator " ) ;
metadata < < " benchmark_frozen_preparation_seconds= "
< < std : : chrono : : duration < double > ( Clock : : now ( ) - preparationStart ) . count ( ) < < ' \n ' ;
metadata . flush ( ) ;
experiment : : plateau_diagnostics : : BenchmarkPreconditioners ( s , o . output ,
{ . variant = o . preconditionerVariant , . restart = o . restart ,
2026-09-17 16:23:13 -04:00
. maximumIterations = o . maxLinear , . relativeTolerance = o . linearTolerance ,
. schurCorrectionSweeps = o . schurCorrectionSweeps , . schurRelaxation = o . schurRelaxation } ) ;
2026-09-17 16:16:46 -04:00
} ) ;
// A missed linear gate is a measured outcome, not failure to execute
// the experiment. The CSV retains both recurrence and true gate results.
return 0 ;
}
2026-09-17 17:05:44 -04:00
if constexpr ( FullDefaultStack ) if ( ! o . probeState . empty ( ) ) {
2026-09-16 14:34:06 -04:00
if ( o . maxNewton ! = 0 )
throw std : : invalid_argument ( " --probe-state requires --max-newton 0 " ) ;
metadata < < " probe_state= " < < o . probeState < < ' \n ' ;
2026-09-17 08:18:09 -04:00
if ( ! o . probeDirection . empty ( ) )
metadata < < " probe_direction= " < < o . probeDirection < < ' \n ' ;
if ( ! o . replayCandidate . empty ( ) )
metadata < < " replay_candidate= " < < o . replayCandidate < < ' \n ' ;
2026-09-17 08:35:49 -04:00
metadata < < " pressure_probe= " < < o . pressureProbe < < " \n shape_curvature= " < < o . shapeCurvature
2026-09-17 10:51:07 -04:00
< < " \n surface_trace= " < < o . surfaceTrace < < " \n positivity_certificate= " < < o . positivityCertificate < < ' \n ' ;
2026-09-16 14:34:06 -04:00
metadata . flush ( ) ;
solver : : detail : : StellarEquilibriumContextDiagnostics : : WithState ( context , [ & ] ( auto & s , const fem : : FEM & f ) {
2026-09-17 08:18:09 -04:00
const auto snapshot = LoadVector ( o . probeState , s . AcceptedPhysicalState ( ) . Size ( ) ) ;
2026-09-17 10:51:07 -04:00
if ( o . positivityCertificate ) {
// A legacy endpoint may already violate admissibility. Its
// polynomial must be inspectable without preparing that state.
mfem : : Vector normalizedSnapshot ;
s . normalizedOperator - > NormalizeState ( snapshot , normalizedSnapshot ) ;
experiment : : plateau_diagnostics : : CertifyPolynomialEnthalpy ( s , normalizedSnapshot , o . output ) ;
return ;
}
2026-09-16 14:34:06 -04:00
s . normalizedOperator - > NormalizeState ( snapshot , s . trialNormalizedState ) ;
auto prep = s . PrepareTrial ( ) ;
if ( ! prep )
throw std : : runtime_error ( " Snapshot is inadmissible " ) ;
std : : cout < < " Snapshot F= " < < s . trialNormalizedResidual . Norml2 ( ) < < std : : endl ;
2026-09-17 08:18:09 -04:00
if ( ! o . probeDirection . empty ( ) | | ! o . replayCandidate . empty ( ) ) {
FocusedProbes ( s , f , o , snapshot ) ;
return ;
}
2026-09-16 14:34:06 -04:00
experiment : : plateau_diagnostics : : InspectPreconditioner ( s , o . output ) ;
FrozenProbes ( s , f , o , & snapshot ) ;
} ) ;
return 0 ;
}
auto physicalMetrics = [ & ] ( auto & s , const fem : : FEM & f , const std : : string & label ) {
std : : cout < < label < < " physical diagnostics " < < std : : endl ;
experiment : : polytrope_validation : : PhysicalState physical ( s , f ) ;
auto out = File ( o . output / ( label + " _physical.csv " ) ) ;
out < < " quadrature,metric,value \n " ;
for ( int q : { 14 , 18 } ) {
auto metrics = experiment : : plateau_diagnostics : : MeasurePhysical ( physical , o . n , scales . K , q ) ;
metrics [ " normalized_bordered_residual " ] = physical . normalizedBorderedResidualNorm ;
metrics [ " normalized_unbordered_residual " ] = physical . normalizedUnborderedResidualNorm ;
metrics [ " central_border " ] = physical . centralBorder ;
metrics [ " normalized_border_action " ] = physical . normalizedCentralBorderActionNorm ;
metrics [ " minimum_sampled_jacobian " ] = s . acceptedMinimumJacobianDeterminant ;
metrics [ " mass_absolute_error " ] = std : : abs ( metrics [ " mass " ] - 1. ) ;
metrics [ " angular_momentum_absolute_error " ] = std : : abs ( metrics [ " angular_momentum_z " ] - o . angularMomentum ) ;
std : : cout < < " q= " < < q < < " virial_h= " < < metrics [ " enthalpy_virial_error " ] < < " M= " < < metrics [ " mass " ]
< < std : : endl ;
for ( auto & [ k , v ] : metrics )
out < < q < < ' , ' < < k < < ' , ' < < v < < ' \n ' ;
out . flush ( ) ;
}
} ;
solver : : detail : : StellarEquilibriumContextDiagnostics : : WithState ( context , [ & ] ( auto & s , const fem : : FEM & f ) {
auto r = s . Problem ( ) . GetManifest ( ) . residualBlocks ( ) , v = s . Problem ( ) . GetManifest ( ) . valueBlocks ( ) ;
rows . assign ( r . begin ( ) , r . end ( ) ) ;
values . assign ( v . begin ( ) , v . end ( ) ) ;
residualFactors = s . normalizedOperator - > GetNormalization ( ) . ResidualFactors ( ) ;
2026-09-17 10:09:22 -04:00
stateFactors = s . normalizedOperator - > GetNormalization ( ) . StateFactors ( ) ;
2026-09-16 14:34:06 -04:00
for ( const auto & b : values )
if ( b . stableId = = " fixed_central_density.border " )
centralOffset = b . offset ;
experiment : : polytrope_validation : : PhysicalState physical ( s , f ) ;
centralRows = physical . centralHydrostaticRows ;
auto layout = File ( o . output / " layout.csv " ) ;
layout < < " kind,block,offset,size,scale \n " ;
for ( auto & b : rows )
layout < < " residual, " < < b . stableId < < ' , ' < < b . offset < < ' , ' < < b . size < < ' , ' < < b . scale < < ' \n ' ;
for ( auto & b : values )
layout < < " value, " < < b . stableId < < ' , ' < < b . offset < < ' , ' < < b . size < < ' , ' < < b . scale < < ' \n ' ;
physicalMetrics ( s , f , " initial " ) ;
} ) ;
auto history = File ( o . output / " newton.csv " ) ;
history < < " iteration,accepted,F,alpha,trials,linear_status,linear_iterations,linear_true,seconds,linear_seconds, "
2026-09-16 14:42:41 -04:00
" prepare_seconds,refresh_seconds,geometry_limited,geometry_safe_step,central_border,unbordered_F, "
" linear_initial_residual,linear_rhs_norm,linear_initial_over_rhs \n " ;
2026-09-16 14:34:06 -04:00
auto blocklog = File ( o . output / " blocks.csv " ) ;
blocklog < < " iteration,stage,block,normalized_norm,physical_norm,max_abs_normalized \n " ;
auto triallog = File ( o . output / " trials.csv " ) ;
triallog < < " iteration,trial,alpha,disposition,source,F,min_jacobian \n " ;
2026-09-17 10:09:22 -04:00
auto stateConsistency = File ( o . output / " trial_state_consistency.csv " ) ;
stateConsistency < < " iteration,trial,normalized_physical_roundtrip_error,relative_error \n " ;
2026-09-17 08:06:37 -04:00
mfem : : Vector captureBase ;
auto captureDirectory = [ & ] ( int iteration ) {
return o . output / ( " capture_ " + std : : to_string ( iteration ) ) ;
} ;
auto shouldCapture = [ & ] ( int iteration ) {
return std : : find ( o . captureIterations . begin ( ) , o . captureIterations . end ( ) , iteration ) ! = o . captureIterations . end ( ) ;
} ;
auto copySpan = [ ] ( std : : span < const mfem : : real_t > values ) {
mfem : : Vector result ( static_cast < int > ( values . size ( ) ) ) ;
for ( int i = 0 ; i < result . Size ( ) ; + + i )
result ( i ) = values [ i ] ;
return result ;
} ;
2026-09-16 14:34:06 -04:00
auto record = [ & ] ( int it , const char * stage , std : : span < const mfem : : real_t > residual ) {
for ( const auto & b : rows ) {
long double ps = 0 ;
double mx = 0 ;
for ( int i = b . offset ; i < b . offset + b . size ; + + i ) {
const double p = residual [ i ] / residualFactors ( i ) ;
ps + = ( long double ) p * p ;
mx = std : : max ( mx , std : : abs ( residual [ i ] ) ) ;
}
blocklog < < it < < ' , ' < < stage < < ' , ' < < b . stableId < < ' , ' < < Norm ( residual , b . offset , b . size ) < < ' , '
< < std : : sqrt ( ps ) < < ' , ' < < mx < < ' \n ' ;
}
blocklog . flush ( ) ;
} ;
auto observer = solver : : nonlinear : : makeObserver (
[ & ] ( const solver : : nonlinear : : BeforeIteration & e ) {
record ( e . iteration , " before " , e . normalizedResidual ) ;
2026-09-17 08:06:37 -04:00
if ( shouldCapture ( e . iteration ) ) {
const auto directory = captureDirectory ( e . iteration ) ;
std : : filesystem : : create_directory ( directory ) ;
captureBase = copySpan ( e . normalizedState ) ;
SaveVector ( directory / " normalized_base.txt " , captureBase ) ;
SaveVector ( directory / " physical_base.txt " , copySpan ( e . physicalState ) ) ;
SaveVector ( directory / " normalized_residual.txt " , copySpan ( e . normalizedResidual ) ) ;
}
2026-09-16 14:34:06 -04:00
std : : cout < < " Newton " < < e . iteration < < " F= " < < e . residualNorm < < std : : endl ;
} ,
[ & ] ( const solver : : nonlinear : : AfterLineSearchTrial & e ) {
2026-09-17 10:09:22 -04:00
if ( e . candidatePhysicalState . size ( ) ! = e . candidateNormalizedState . size ( ) | |
e . candidateNormalizedState . size ( ) ! = static_cast < size_t > ( stateFactors . Size ( ) ) )
throw std : : runtime_error ( " Trial observer exposed incompatible state representations " ) ;
long double stateError = 0.0 L , stateNorm = 0.0 L ;
for ( int i = 0 ; i < stateFactors . Size ( ) ; + + i ) {
const long double difference = e . candidatePhysicalState [ i ] * stateFactors ( i ) - e . candidateNormalizedState [ i ] ;
stateError + = difference * difference ;
stateNorm + = static_cast < long double > ( e . candidateNormalizedState [ i ] ) * e . candidateNormalizedState [ i ] ;
}
const double relativeStateError = std : : sqrt ( stateError ) / std : : max ( 1.0 L , std : : sqrt ( stateNorm ) ) ;
stateConsistency < < e . iteration < < ' , ' < < e . trial < < ' , ' < < std : : sqrt ( stateError ) < < ' , ' < < relativeStateError < < ' \n ' ;
stateConsistency . flush ( ) ;
if ( std : : isfinite ( relativeStateError ) & & relativeStateError > 64.0 * std : : numeric_limits < double > : : epsilon ( ) )
throw std : : runtime_error ( " Trial observer physical state disagrees with its normalized state " ) ;
2026-09-16 14:34:06 -04:00
triallog < < e . iteration < < ' , ' < < e . trial < < ' , ' < < e . stepLength < < ' , ' < < int ( e . disposition ) < < ' , '
< < e . rejectionSource < < ' , ' < < ( e . metric ? e . metric - > residualNorm : NAN ) < < ' , '
< < e . minimumJacobianDeterminant . value_or ( NAN ) < < ' \n ' ;
triallog . flush ( ) ;
2026-09-17 08:06:37 -04:00
if ( e . trial = = 0 & & shouldCapture ( e . iteration ) ) {
if ( ! ( e . stepLength > 0. ) | | ! std : : isfinite ( e . stepLength ) | |
e . candidateNormalizedState . size ( ) ! = static_cast < size_t > ( captureBase . Size ( ) ) )
throw std : : runtime_error ( " Invalid candidate for diagnostic capture " ) ;
const auto directory = captureDirectory ( e . iteration ) ;
const auto candidate = copySpan ( e . candidateNormalizedState ) ;
SaveVector ( directory / " normalized_candidate.txt " , candidate ) ;
SaveVector ( directory / " physical_candidate.txt " , copySpan ( e . candidatePhysicalState ) ) ;
mfem : : Vector direction ( candidate ) , reconstructed ( captureBase ) ;
direction - = captureBase ;
direction / = e . stepLength ;
reconstructed . Add ( e . stepLength , direction ) ;
reconstructed - = candidate ;
2026-09-17 10:09:22 -04:00
SaveVector ( directory / " normalized_effective_trial_direction.txt " , direction ) ;
SaveVector ( directory / " normalized_direction.txt " , copySpan ( e . normalizedDirection ) ) ;
2026-09-17 08:06:37 -04:00
auto info = File ( directory / " capture.txt " ) ;
info < < " iteration= " < < e . iteration < < " \n first_trial_alpha= " < < e . stepLength
< < " \n disposition= " < < int ( e . disposition ) < < " \n source= " < < e . rejectionSource
< < " \n base_norm= " < < captureBase . Norml2 ( ) < < " \n direction_norm= " < < direction . Norml2 ( )
< < " \n candidate_reconstruction_error= " < < reconstructed . Norml2 ( )
< < " \n direction_roundoff_scale_estimate= "
< < 8. * std : : numeric_limits < double > : : epsilon ( ) *
( captureBase . Norml2 ( ) + candidate . Norml2 ( ) ) / e . stepLength
2026-09-17 10:09:22 -04:00
< < " \n direction_source=exact_observer_tangent \n "
< < " effective_trial_direction_source=candidate_difference_divided_by_alpha \n "
< < " roundoff_scale_applies_to_effective_trial_direction_only=1 \n "
2026-09-17 08:06:37 -04:00
< < " roundoff_scale_is_estimate_not_certificate=1 \n " ;
}
2026-09-16 14:34:06 -04:00
} ,
[ & ] ( const solver : : nonlinear : : AfterIteration & e ) {
record ( e . iteration , " after " , e . normalizedResidual ) ;
double border = centralOffset < 0 ? 0 : e . physicalState [ centralOffset ] ;
mfem : : Vector unbordered ( ( int ) e . normalizedResidual . size ( ) ) ;
for ( int i = 0 ; i < unbordered . Size ( ) ; + + i )
unbordered ( i ) = e . normalizedResidual [ i ] ;
for ( int i : centralRows )
unbordered ( i ) - = residualFactors ( i ) * border ;
2026-09-16 14:42:41 -04:00
const double linearInitial = e . linearSolve ? e . linearSolve - > initialResidualNorm : NAN ;
const double linearRhs = e . linearSolve ? e . linearSolve - > rightHandSideNorm : NAN ;
double linearInitialRelative = NAN ;
if ( linearRhs > 0. )
linearInitialRelative = linearInitial / linearRhs ;
else if ( linearRhs = = 0. & & std : : isfinite ( linearInitial ) )
linearInitialRelative = linearInitial = = 0. ? 0. : std : : numeric_limits < double > : : infinity ( ) ;
2026-09-16 14:34:06 -04:00
history < < e . iteration < < ' , ' < < e . stepAccepted < < ' , ' < < e . residualNorm < < ' , ' < < e . acceptedStepLength
< < ' , ' < < e . lineSearchTrials < < ' , ' < < ( e . linearSolve ? int ( e . linearSolve - > status ) : - 1 ) < < ' , '
< < ( e . linearSolve ? e . linearSolve - > iterations : 0 ) < < ' , '
< < ( e . linearSolve ? e . linearSolve - > relativeTrueResidualNorm : NAN ) < < ' , ' < < e . iterationSeconds
< < ' , ' < < ( e . linearSolve ? e . linearSolve - > solveSeconds : 0 ) < < ' , ' < < e . trialPreparationSeconds
< < ' , ' < < e . preconditionerRefreshSeconds < < ' , '
< < ( e . geometryPreflight ? e . geometryPreflight - > limitedByGeometry : false ) < < ' , '
< < ( e . geometryPreflight ? e . geometryPreflight - > stepSize : NAN ) < < ' , ' < < border < < ' , '
2026-09-16 14:42:41 -04:00
< < unbordered . Norml2 ( ) < < ' , ' < < linearInitial < < ' , ' < < linearRhs < < ' , '
< < linearInitialRelative < < ' \n ' ;
2026-09-16 14:34:06 -04:00
history . flush ( ) ;
std : : cout < < " alpha= " < < e . acceptedStepLength < < " F= " < < e . residualNorm
< < " linear= " < < ( e . linearSolve ? e . linearSolve - > relativeTrueResidualNorm : NAN )
< < " lambda= " < < border < < " unbordered= " < < unbordered . Norml2 ( )
< < " seconds= " < < e . iterationSeconds < < std : : endl ;
} ) ;
bool converged = false ;
if ( o . maxNewton > 0 ) {
2026-09-17 09:45:05 -04:00
namespace nl = solver : : nonlinear ;
const nl : : NewtonOptions options {
. relativeTolerance = o . rtol , . absoluteTolerance = o . atol , . maximumIterations = o . maxNewton ,
. linearSolve = { . relativeTolerance = o . linearTolerance , . absoluteTolerance = 0. , . maximumIterations = o . maxLinear } } ;
const nl : : GravitySolveOptions gravity {
. control = { . relativeTolerance = o . gravityTolerance , . absoluteTolerance = 1e-14 ,
. maximumIterations = o . gravityIterations } ,
2026-09-17 10:09:22 -04:00
. backend = { . restartLength = o . gravityRestart , . printLevel = - 1 } } ;
2026-09-17 09:45:05 -04:00
auto run = [ & ] ( auto numerics ) {
auto solve = solver : : make ( context , nl : : Newton ( options , nl : : NormalizedResidualMetric { } , numerics ) , observer ) ;
2026-09-16 14:34:06 -04:00
return solve . evaluate ( ) ;
2026-09-17 09:45:05 -04:00
} ;
auto report = [ & ] {
if ( o . strategy = = " positivity " ) return run ( nl : : NewtonNumerics { nl : : PolynomialEnthalpy { } , nl : : MonolithicGravity { } } ) ;
if ( o . strategy = = " curvature " ) return run ( nl : : NewtonNumerics { nl : : SampledEnthalpy { } , nl : : GravityCurvatureCorrection { gravity } } ) ;
2026-09-17 17:05:44 -04:00
if constexpr ( FullDefaultStack )
if ( o . strategy = = " reduced " ) return run ( nl : : NewtonNumerics { nl : : SampledEnthalpy { } , nl : : EliminatedGravity { gravity } } ) ;
2026-09-17 09:45:05 -04:00
if ( o . strategy = = " positivity-curvature " ) return run ( nl : : NewtonNumerics { nl : : PolynomialEnthalpy { } , nl : : GravityCurvatureCorrection { gravity } } ) ;
2026-09-17 17:05:44 -04:00
if constexpr ( FullDefaultStack )
if ( o . strategy = = " positivity-reduced " ) return run ( nl : : NewtonNumerics { nl : : PolynomialEnthalpy { } , nl : : EliminatedGravity { gravity } } ) ;
2026-09-17 09:45:05 -04:00
return run ( nl : : NewtonNumerics { } ) ;
2026-09-16 14:34:06 -04:00
} ( ) ;
2026-09-17 09:45:05 -04:00
const auto & strategyDiagnostics = report . diagnostics ( ) . numericalStrategies ;
auto strategyMetrics = File ( o . output / " numerical_strategies.csv " ) ;
strategyMetrics < < " metric,value \n " ;
const auto metric = [ & ] ( const char * name , auto value ) { strategyMetrics < < name < < ' , ' < < value < < ' \n ' ; } ;
metric ( " polynomial_checks " , strategyDiagnostics . polynomialChecks ) ;
metric ( " direction_repairs " , strategyDiagnostics . directionRepairs ) ;
metric ( " last_repair_fraction " , strategyDiagnostics . lastRepairFraction ) ;
metric ( " last_merit_directional_derivative " , strategyDiagnostics . lastMeritDirectionalDerivative ) ;
metric ( " gravity_solves " , strategyDiagnostics . gravitySolves ) ;
metric ( " gravity_iterations " , strategyDiagnostics . gravityIterations ) ;
metric ( " gravity_failures " , strategyDiagnostics . gravityFailures ) ;
2026-09-17 10:09:22 -04:00
metric ( " gravity_prepared_failures " , strategyDiagnostics . gravityPreparedFailures ) ;
2026-09-17 10:22:25 -04:00
metric ( " compact_gravity_operator " , strategyDiagnostics . compactGravityOperator ) ;
metric ( " compact_gravity_inverse " , strategyDiagnostics . compactGravityInverse ) ;
2026-09-17 10:09:22 -04:00
metric ( " linear_report_space " , static_cast < int > ( strategyDiagnostics . lastLinearReportSpace ) ) ;
2026-09-17 09:45:05 -04:00
metric ( " gravity_solve_seconds " , strategyDiagnostics . gravitySolveSeconds ) ;
metric ( " last_gravity_true_residual " , strategyDiagnostics . lastGravityTrueResidual ) ;
metric ( " last_gravity_correction_norm " , strategyDiagnostics . lastGravityCorrectionNorm ) ;
metric ( " last_gravity_prepared_defect " , strategyDiagnostics . lastGravityPreparedDefect ) ;
2026-09-17 10:22:25 -04:00
metric ( " last_gravity_affine_disagreement " , strategyDiagnostics . lastGravityAffineDisagreement ) ;
metric ( " last_gravity_arithmetic_allowance " , strategyDiagnostics . lastGravityArithmeticAllowance ) ;
metric ( " last_gravity_prepared_target_threshold " , strategyDiagnostics . lastGravityPreparedTargetThreshold ) ;
2026-09-17 09:45:05 -04:00
metric ( " initial_gravity_residual " , strategyDiagnostics . initialGravityResidual ) ;
metric ( " completed_initial_gravity_residual " , strategyDiagnostics . completedInitialGravityResidual ) ;
metric ( " last_lifted_relative_true_residual " , strategyDiagnostics . lastLiftedRelativeTrueResidual ) ;
2026-09-16 14:34:06 -04:00
converged = report . converged ( ) ;
metadata < < " converged= " < < converged < < " \n accepted_steps= " < < report . completedNonlinearIterations ( ) < < ' \n ' ;
if ( ! converged )
metadata < < " failure= " < < report . failure ( ) . message < < ' \n ' ;
metadata . flush ( ) ;
}
solver : : detail : : StellarEquilibriumContextDiagnostics : : WithState ( context , [ & ] ( auto & s , const fem : : FEM & f ) {
SaveVector ( o . output / " accepted_state.txt " , s . AcceptedPhysicalState ( ) ) ;
physicalMetrics ( s , f , " final " ) ;
const auto & schur = s . physicalInverse - > GetGroupedPreconditioner ( ) . GetFactorization ( ) . GetSchurComplement ( ) ;
auto sh = File ( o . output / " border_schur.csv " ) ;
for ( int i = 0 ; i < schur . Height ( ) ; + + i ) {
for ( int j = 0 ; j < schur . Width ( ) ; + + j )
sh < < ( j ? " , " : " " ) < < schur ( i , j ) ;
sh < < ' \n ' ;
}
2026-09-17 17:05:44 -04:00
if constexpr ( FullDefaultStack ) if ( o . probes ) {
2026-09-16 14:34:06 -04:00
experiment : : plateau_diagnostics : : InspectPreconditioner ( s , o . output ) ;
FrozenProbes ( s , f , o ) ;
}
} ) ;
return converged ? 0 : 3 ;
}
template < class Model > int Setup ( Model model , fem : : FEM f , const Options & o , const Scales & scales ) {
auto d = equilibrium : : makeStellarDiscretization (
std : : move ( f ) , normalization : : PhysicalRieszDiagonal { dimensions : : LengthValue { 1. } , utils : : G } ) ;
2026-09-17 17:05:44 -04:00
const auto withRecipe = [ & ] ( auto recipe ) {
using Recipe = decltype ( recipe ) ;
using Problem = equilibrium : : StellarEquilibriumProblem < Model , decltype ( d ) > ;
static_assert ( preconditioning : : StellarPreconditionerRuntimeAvailableFor < Recipe , Problem > ) ;
using Inverse = preconditioning : : PreparedStellarInverseType < Recipe , Problem > ;
static_assert ( solver : : LinearPreconditionerApplicationContractAvailable < Inverse > ) ;
static_assert ( solver : : linearPreconditionerApplicationContract < Inverse > = =
preconditioning : : ApplicationContract : : stationary_linear ) ;
auto context = solver : : makeContext ( std : : move ( model ) , std : : move ( d ) , std : : move ( recipe ) ,
solver : : linear : : FGMRES ( { . restartLength = o . restart , . printLevel = - 1 } ) ,
seed : : LaneEmden ( { . centralDensity = dimensions : : DensityValue { scales . rho } } ) ) ;
constexpr bool fullDefaultStack = std : : same_as < Recipe , preconditioning : : DefaultStellarPreconditioner > ;
return RunContext < fullDefaultStack > ( context , o , scales ) ;
} ;
if ( o . preconditionerProbe | | o . preconditionerVariant = = " independent " )
return withRecipe ( preconditioning : : makePreconditioner ( ) ) ;
if ( o . preconditionerVariant = = " gravity-schur " )
return withRecipe ( experiment : : plateau_diagnostics : : GravitySchurNewtonPreconditioner {
{ . correctionSweeps = o . schurCorrectionSweeps , . relaxation = o . schurRelaxation } } ) ;
if ( o . preconditionerVariant = = " stellar-ldu " )
return withRecipe ( experiment : : plateau_diagnostics : : StellarLDUNewtonPreconditioner {
{ . correctionSweeps = 0 , . relaxation = 1.0 } } ) ;
2026-09-17 17:36:48 -04:00
if ( o . preconditionerVariant = = " stellar-ldu-gravity-schur " )
return withRecipe ( experiment : : plateau_diagnostics : : StellarLDUGravitySchurNewtonPreconditioner {
{ . correctionSweeps = o . schurCorrectionSweeps , . relaxation = o . schurRelaxation } } ) ;
2026-09-17 17:05:44 -04:00
throw std : : logic_error ( " An unsupported Newton preconditioner bypassed option validation. " ) ;
2026-09-16 14:34:06 -04:00
}
int Run ( const Options & o ) {
const auto scales = Derive ( o . n ) ;
std : : cout < < " n= " < < o . n < < " K= " < < scales . K < < " rho_c= " < < scales . rho < < " J= " < < o . angularMomentum < < std : : endl ;
utils : : Args args ;
args . mesh_file = o . mesh ;
args . p . rtol = 1e-12 ;
args . p . atol = 1e-12 ;
auto f = fem : : setup_fem ( o . mesh , args , 0 ) ;
if ( ! f . okay ( ) )
throw std : : runtime_error ( " FEM setup failed " ) ;
auto eos = eos : : Polytrope ( { . n = o . n , . K = scales . K } ) ;
auto surf = surface : : Isobaric ( { . Psurf = dimensions : : PressureValue { 0. } } ) ;
auto mass = integral : : FixedTotalMass ( { . Mtotal = dimensions : : MassValue { 1. } } ) ;
auto angular = integral : : FixedAngularMomentum (
{ . Jtotal = dimensions : : AngularMomentumValue { o . angularMomentum } , . axis = { 0. , 0. , 1. } , . center = { 0. , 0. , 0. } } ) ;
if ( o . noCentral )
return Setup ( model : : StellarModel ( eos , surf , mass , angular ) , std : : move ( f ) , o , scales ) ;
return Setup ( model : : StellarModel ( eos , surf , mass , angular ,
constraint : : FixedCentralDensity ( { . RhoC = dimensions : : DensityValue { scales . rho } } ) ) ,
std : : move ( f ) , o , scales ) ;
}
} // namespace
int main ( int argc , char * * argv ) {
mfem : : Mpi : : Init ( argc , argv ) ;
int result = 0 ;
try {
int count ;
MPI_Comm_size ( MPI_COMM_WORLD , & count ) ;
if ( count ! = 1 )
throw std : : runtime_error ( " Single rank only " ) ;
mfem : : Device device ( " cpu " ) ;
std : : cout < < std : : setprecision ( 12 ) ;
result = Run ( Parse ( argc , argv ) ) ;
} catch ( const std : : exception & e ) {
std : : cerr < < " Diagnostic failure: " < < e . what ( ) < < std : : endl ;
result = 2 ;
}
mfem : : Mpi : : Finalize ( ) ;
return result ;
}