Add checked harmonic snapshot continuation and matched linear trial diagnostics
This commit is contained in:
@@ -36,6 +36,8 @@ import mean_field;
|
||||
#include "negative_density_diagnostics.hpp"
|
||||
#include "negative_density_regions.hpp"
|
||||
#include "density_order_projection.hpp"
|
||||
#include "continuation_snapshot.hpp"
|
||||
#include "harmonic_linear_trials.hpp"
|
||||
|
||||
using namespace mean_field;
|
||||
namespace {
|
||||
@@ -70,7 +72,8 @@ struct Options {
|
||||
std::vector<int> captureIterations;
|
||||
std::string mesh = "sandbox.smesh";
|
||||
std::filesystem::path output, probeState, probeDirection, replayCandidate;
|
||||
std::filesystem::path densitySnapshot;
|
||||
std::filesystem::path densitySnapshot, restartSnapshot;
|
||||
bool continuation = false, allowUnconvergedRestart = false, benchmarkTrials = false;
|
||||
};
|
||||
Options Parse(int argc, char **argv) {
|
||||
Options o;
|
||||
@@ -143,6 +146,14 @@ Options Parse(int argc, char **argv) {
|
||||
}
|
||||
else if (a == "--density-order-final")
|
||||
o.densityOrderFinal = true;
|
||||
else if (a == "--restart-snapshot")
|
||||
o.restartSnapshot = v();
|
||||
else if (a == "--continuation")
|
||||
o.continuation = true;
|
||||
else if (a == "--allow-unconverged-restart")
|
||||
o.allowUnconvergedRestart = true;
|
||||
else if (a == "--benchmark-trials")
|
||||
o.benchmarkTrials = true;
|
||||
else if (a == "--density-snapshot")
|
||||
o.densitySnapshot = v();
|
||||
else if (a == "--surface-prescription")
|
||||
@@ -213,6 +224,13 @@ Options Parse(int argc, char **argv) {
|
||||
}
|
||||
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");
|
||||
if ((o.continuation || o.allowUnconvergedRestart) && o.restartSnapshot.empty())
|
||||
throw std::invalid_argument("Continuation/restart permission requires --restart-snapshot");
|
||||
if (!o.restartSnapshot.empty() && (o.densityDiagnostic || !o.probeState.empty() ||
|
||||
!o.probeDirection.empty() || !o.replayCandidate.empty() || o.surfacePrescription != "harmonics"))
|
||||
throw std::invalid_argument("Snapshot restart requires native harmonic coordinates and no legacy replay");
|
||||
if (o.benchmarkTrials && !o.preconditionerProbe)
|
||||
throw std::invalid_argument("--benchmark-trials requires --preconditioner-probe");
|
||||
if (o.surfacePrescription != "nodal" && o.surfacePrescription != "harmonics")
|
||||
throw std::invalid_argument("Surface prescriptions are nodal or harmonics");
|
||||
if (!o.densitySnapshot.empty() && !o.densityDiagnostic)
|
||||
@@ -234,10 +252,10 @@ Options Parse(int argc, char **argv) {
|
||||
// size match is insufficient to establish modal snapshot provenance.
|
||||
if (o.reducedSurfaceProbe || o.reducedSurfaceAlgebraCheck || o.weakShapeProbe || o.weakShapeFinal ||
|
||||
o.probes || o.pressureProbe || o.shapeCurvature || o.surfaceTrace || o.strategyProbe ||
|
||||
o.positivityProbe || o.positivityCertificate || o.preconditionerProbe || o.nonlinearStepProbe ||
|
||||
o.positivityProbe || o.positivityCertificate ||
|
||||
o.coupledTrialProbe || o.dampedCoupledProbe || !o.probeState.empty() || !o.probeDirection.empty() ||
|
||||
!o.replayCandidate.empty() || o.strategy != "baseline")
|
||||
throw std::invalid_argument("Native harmonic diagnostics currently support live baseline Newton trajectories only");
|
||||
!o.replayCandidate.empty() || (o.strategy != "baseline" && o.strategy != "curvature"))
|
||||
throw std::invalid_argument("Native harmonic diagnostics exclude legacy nodal probes and unsupported strategies");
|
||||
} else if (o.surfaceMaximumOrder != -1) {
|
||||
throw std::invalid_argument("--surface-maximum-order requires --surface-prescription harmonics");
|
||||
}
|
||||
@@ -656,6 +674,36 @@ int RunContext(Context &context, const Options &o, const Scales &scales) {
|
||||
mfem::Vector residualFactors, stateFactors;
|
||||
std::vector<int> centralRows;
|
||||
int centralOffset = -1;
|
||||
if (!o.restartSnapshot.empty()) {
|
||||
mfem::Vector restartPhysical;
|
||||
solver::detail::StellarEquilibriumContextDiagnostics::WithState(context, [&](auto &s, const fem::FEM &) {
|
||||
const auto directory = std::filesystem::is_directory(o.restartSnapshot) ? o.restartSnapshot :
|
||||
(std::filesystem::exists(o.restartSnapshot.parent_path() / "metadata.txt") ?
|
||||
o.restartSnapshot.parent_path() : o.restartSnapshot.parent_path().parent_path());
|
||||
const auto saved = experiment::continuation::ParseSnapshotMetadata(directory);
|
||||
const auto number = [&](const char *key, double expected) {
|
||||
const double actual = std::stod(saved.at(key));
|
||||
if (!std::isfinite(actual) || std::abs(actual - expected) >
|
||||
1e-13 * std::max(1., std::abs(expected)))
|
||||
throw std::invalid_argument(std::string("Restart metadata mismatch: ") + key);
|
||||
};
|
||||
if (saved.at("surface_prescription") != o.surfacePrescription)
|
||||
throw std::invalid_argument("Restart surface coordinate prescription differs");
|
||||
number("surface_degree", o.surfaceDegree); number("surface_maximum_order", o.surfaceMaximumOrder);
|
||||
number("density_order", field::Density::scalarOrder); number("enthalpy_order", field::Enthalpy::scalarOrder);
|
||||
number("gravity_potential_order", field::Gravity::potentialOrder);
|
||||
number("gravity_flux_order", field::Gravity::fluxOrder);
|
||||
number("displacement_order", field::Displacement::Vector::familyOrder);
|
||||
number("M", 1.); number("R_reference", 1.);
|
||||
if (!o.continuation) {
|
||||
number("n", o.n); number("K", scales.K); number("J", o.angularMomentum);
|
||||
number("central_phase", !o.noCentral);
|
||||
}
|
||||
restartPhysical = experiment::continuation::LoadMappedSnapshot(s.Problem(),
|
||||
o.restartSnapshot, o.continuation, o.allowUnconvergedRestart);
|
||||
});
|
||||
solver::detail::StellarEquilibriumContextDiagnostics::InitializePhysicalState(context, restartPhysical);
|
||||
}
|
||||
const bool contextUsesGravitySchur = !o.preconditionerProbe &&
|
||||
(o.preconditionerVariant == "gravity-schur" || o.preconditionerVariant == "stellar-ldu-gravity-schur");
|
||||
auto metadata = File(o.output / "metadata.txt");
|
||||
@@ -697,6 +745,9 @@ int RunContext(Context &context, const Options &o, const Scales &scales) {
|
||||
<< "\ncontext_gravity_schur_relaxation=" << (contextUsesGravitySchur ? o.schurRelaxation : 0.0)
|
||||
<< "\nschur_correction_sweeps=" << o.schurCorrectionSweeps << "\nschur_relaxation=" << o.schurRelaxation
|
||||
<< "\ncompiler=" << __VERSION__ << "\ncompiled=" << __DATE__ << ' ' << __TIME__ << '\n';
|
||||
metadata << "restart_snapshot=" << o.restartSnapshot.string() << "\ncontinuation=" << o.continuation
|
||||
<< "\nallow_unconverged_restart=" << o.allowUnconvergedRestart
|
||||
<< "\nbenchmark_trials=" << o.benchmarkTrials << '\n';
|
||||
metadata << "density_order=" << field::Density::scalarOrder
|
||||
<< "\nenthalpy_order=" << field::Enthalpy::scalarOrder
|
||||
<< "\ngravity_potential_order=" << field::Gravity::potentialOrder
|
||||
@@ -880,9 +931,10 @@ int RunContext(Context &context, const Options &o, const Scales &scales) {
|
||||
}
|
||||
// Custom Newton recipes do not instantiate the nine-variant frozen suite.
|
||||
if constexpr (FullDefaultStack) if (o.preconditionerProbe) {
|
||||
metadata << "benchmark_input=" << (o.probeState.empty() ? "lane_emden_seed" : "physical_snapshot") << '\n';
|
||||
metadata << "benchmark_input=" << (!o.restartSnapshot.empty() ? "checked_restart_snapshot" :
|
||||
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 &) {
|
||||
solver::detail::StellarEquilibriumContextDiagnostics::WithState(context, [&](auto &s, const fem::FEM &f) {
|
||||
if (o.probeState.empty()) {
|
||||
s.trialNormalizedState = s.acceptedNormalizedState;
|
||||
} else {
|
||||
@@ -899,6 +951,8 @@ int RunContext(Context &context, const Options &o, const Scales &scales) {
|
||||
{.variant = o.preconditionerVariant, .restart = o.restart,
|
||||
.maximumIterations = o.maxLinear, .relativeTolerance = o.linearTolerance,
|
||||
.schurCorrectionSweeps = o.schurCorrectionSweeps, .schurRelaxation = o.schurRelaxation});
|
||||
if (o.benchmarkTrials)
|
||||
experiment::plateau_diagnostics::RecordHarmonicLinearTrials(s, f, o.output, o.n);
|
||||
});
|
||||
// A missed linear gate is a measured outcome, not failure to execute
|
||||
// the experiment. The CSV retains both recurrence and true gate results.
|
||||
|
||||
Reference in New Issue
Block a user