Apply canonical gravity blocks without full root work
This commit is contained in:
27
experiments/GRAVITY_COMPACT_APPLICATIONS_2026-09-17.md
Normal file
27
experiments/GRAVITY_COMPACT_APPLICATIONS_2026-09-17.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Compact gravity applications
|
||||
|
||||
Branch: `research/gravity-compact-application-20260917`.
|
||||
|
||||
This change removes work from applications inside a gravity-only Krylov solve. It does not change equations, coordinates, tolerances, gravity completion, line search, or the verified true-defect gate. Full root preparation and inverse refresh remain mandatory.
|
||||
|
||||
## Selection and coordinates
|
||||
|
||||
`PreparedGravitySubsystem` accepts a final compile-time application policy. Its default, `AutomaticGravityApplications`, uses a compact action only when a registered capability is present and the supplied local partition exactly equals the typed canonical partition. A collective minimum selects the same path on every rank. `RootRestrictedGravityApplications` keeps the original full-root injection/application/restriction as a test reference and fallback. Existing construction and user-facing stellar specifications are unchanged.
|
||||
|
||||
The built-in operator capability requires the known physical stellar core and checks the compiled specification contributions: none may modify gravity rows. It reuses `GravityFieldOperator::ApplyGravityUnknowns`, the existing affine action on gradient and potential at fixed material state and geometry. This applies `[M B^T; B 0]`, including existing reduced/full boundary maps, without invoking zero density or displacement variations. Its compact packing is always gradient followed by potential; the typed root layout chooses the row and value locations independently.
|
||||
|
||||
Normalization is unchanged: the action is `D_r A_G D_x^-1`, and the inverse action is `D_x P_G D_r^-1`. Factors are gathered from the frozen normalization on refresh, after the new root preparation is ready. Root generations and child inverse currentness invalidate stale applications. No pointers into normalization buffers survive a refresh.
|
||||
|
||||
The direct inverse capability recognizes the standard normalized physical preconditioner and independent material/gravity factorization with the exact gravity block identities. It invokes the already-prepared physical gravity block. Unknown custom inverse recipes and other structure factorizations retain the full-root restriction. The capability's application contract is included in the wrapper's compile-time contract; a potentially flexible branch cannot be advertised as stationary. Selecting a compact inverse is a preconditioning choice, not an assertion that it is the exact gravity inverse. Full-root restriction equivalence must be measured for actual fixtures; the same exact gravity true-defect gate is always used.
|
||||
|
||||
`GravityCompactOperatorAccess` and `GravityCompactInverseAccess` are explicit extension contracts. Custom code opts in by specializing them; similarly named getters alone never enable a shortcut. A registered operator promises the exact gravity principal block, canonical partition, and full diagonal factors. A registered inverse promises a current compact physical action with a declared application contract. These are lower-level numerical extension points, not required model inputs.
|
||||
|
||||
## Verification
|
||||
|
||||
`tests/solver/gravity_compact_applications.cpp` compares compact and explicit root-reference actions for nontrivial directions, nonuniform row/state factors, and both standard and optional-central-border typed forms. It checks factor refresh, stale root and child inverse rejection, unknown inverse fallback, and collective fallback when only rank zero supplies a noncanonical map. Existing generic tests retain empty-local-rank and true-defect failure coverage.
|
||||
|
||||
`plateau_gravity_application_diagnostics.hpp` supplies `plateau_diagnostics::ProbeGravityApplications(subsystem, outputDirectory)`. Call after preparing the full normalized operator, refreshing its inverse, and preparing/refreshing the subsystem. It writes `gravity_applications.csv` with three deterministic nontrivial global directions, selected paths, relative/absolute compact-versus-full errors, and separate application times. It neither prepares a candidate nor changes accepted state. The reference policy also allows direct solve comparisons outside the probe.
|
||||
|
||||
Recommended bounded real checks: run the helper once on the frozen n=1.5 fixture and once on a fixture with the central-density border; repeat the smaller viable fixture on two ranks. Require operator relative error near floating-point roundoff, then inspect inverse equivalence and timings separately. Run both compact and reference inner solves at the same true-defect tolerance before interpreting speed or iteration changes. The frozen strategy probe's existing inner report already carries operator/inverse application times; expose those alongside `UsesCompactOperator()` and `UsesCompactPreconditioner()` when recording campaign results.
|
||||
|
||||
At the time of this implementation commit, the new C++ tests and real-FEM comparison helper await the root-coordinated build/run. No performance or stellar-physics acceptance is claimed from the algebraic tests. End-to-end models continue to require the existing strict convergence and virial checks.
|
||||
121
experiments/plateau_gravity_application_diagnostics.hpp
Normal file
121
experiments/plateau_gravity_application_diagnostics.hpp
Normal file
@@ -0,0 +1,121 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <mfem.hpp>
|
||||
#include <mpi.h>
|
||||
|
||||
// Include after importing mean_field, as with the other frozen-state probes.
|
||||
namespace plateau_diagnostics {
|
||||
struct GravityApplicationProbeOptions final {
|
||||
int directions{3};
|
||||
};
|
||||
|
||||
/* Exercise complete, nontrivial compact vectors: isolated basis entries
|
||||
* can miss permutation or normalization mistakes shared by large blocks.
|
||||
* The reference is deliberately the full normalized root application.
|
||||
* No preparation, accepted-state update, or convergence claim occurs here. */
|
||||
template <mean_field::solver::GravitySubsystemRuntime Subsystem>
|
||||
void ProbeGravityApplications(
|
||||
const Subsystem &subsystem,
|
||||
const std::filesystem::path &outputDirectory,
|
||||
const GravityApplicationProbeOptions options = {}
|
||||
) {
|
||||
using namespace mean_field::solver;
|
||||
const auto communicator = subsystem.GetCommunicator();
|
||||
int rank = 0;
|
||||
MPI_Comm_rank(communicator, &rank);
|
||||
const int localValid = subsystem.IsCurrent() && options.directions > 0;
|
||||
int allValid = 0;
|
||||
MPI_Allreduce(&localValid, &allValid, 1, MPI_INT, MPI_MIN, communicator);
|
||||
int minimumDirections = 0, maximumDirections = 0;
|
||||
MPI_Allreduce(&options.directions, &minimumDirections, 1, MPI_INT, MPI_MIN, communicator);
|
||||
MPI_Allreduce(&options.directions, &maximumDirections, 1, MPI_INT, MPI_MAX, communicator);
|
||||
if (!allValid || minimumDirections != maximumDirections) {
|
||||
throw std::invalid_argument(
|
||||
"Gravity application probes require a current subsystem and common positive direction count."
|
||||
);
|
||||
}
|
||||
PreparedGravitySubsystem reference(
|
||||
subsystem.GetFullOperator(), subsystem.GetFullPreconditioner(), subsystem.GetPartition(), communicator,
|
||||
linear::FGMRES{}, RootRestrictedGravityApplications{}
|
||||
);
|
||||
const int size = subsystem.GetPartition().GravityValueSize();
|
||||
const long long localSize = size;
|
||||
long long globalOffset = 0;
|
||||
MPI_Exscan(&localSize, &globalOffset, 1, MPI_LONG_LONG, MPI_SUM, communicator);
|
||||
if (rank == 0)
|
||||
globalOffset = 0;
|
||||
const auto norm = [communicator](const mfem::Vector &vector) {
|
||||
const double local = vector * vector;
|
||||
double global = 0.0;
|
||||
MPI_Allreduce(&local, &global, 1, MPI_DOUBLE, MPI_SUM, communicator);
|
||||
return std::sqrt(global);
|
||||
};
|
||||
const auto elapsed = [communicator](const double start) {
|
||||
const double local = MPI_Wtime() - start;
|
||||
double global = 0.0;
|
||||
MPI_Allreduce(&local, &global, 1, MPI_DOUBLE, MPI_MAX, communicator);
|
||||
return global;
|
||||
};
|
||||
std::ofstream output;
|
||||
if (rank == 0) {
|
||||
std::filesystem::create_directories(outputDirectory);
|
||||
output.open(outputDirectory / "gravity_applications.csv");
|
||||
output
|
||||
<< std::setprecision(17)
|
||||
<< "direction,compact_operator,compact_inverse,operator_norm,operator_error,operator_relative_error,"
|
||||
"compact_operator_seconds,root_operator_seconds,inverse_norm,inverse_error,inverse_relative_error,"
|
||||
"compact_inverse_seconds,root_inverse_seconds\n";
|
||||
}
|
||||
int outputReady = rank != 0 || output.good();
|
||||
MPI_Allreduce(MPI_IN_PLACE, &outputReady, 1, MPI_INT, MPI_MIN, communicator);
|
||||
if (!outputReady)
|
||||
throw std::runtime_error("Could not create gravity application diagnostics.");
|
||||
mfem::Vector direction(size), compact(size), restricted(size), difference(size);
|
||||
for (int trial = 0; trial < options.directions; ++trial) {
|
||||
for (int i = 0; i < size; ++i) {
|
||||
const double coordinate = static_cast<double>(globalOffset + i + 1);
|
||||
direction(i) =
|
||||
std::sin(0.37 * coordinate * (trial + 1)) + 0.4 * std::cos(0.11 * coordinate * (trial + 2));
|
||||
}
|
||||
double start = MPI_Wtime();
|
||||
subsystem.GetOperator().Mult(direction, compact);
|
||||
const double compactOperatorSeconds = elapsed(start);
|
||||
start = MPI_Wtime();
|
||||
reference.GetOperator().Mult(direction, restricted);
|
||||
const double rootOperatorSeconds = elapsed(start);
|
||||
difference = compact;
|
||||
difference -= restricted;
|
||||
const double operatorNorm = norm(restricted);
|
||||
const double operatorError = norm(difference);
|
||||
start = MPI_Wtime();
|
||||
subsystem.GetPreconditioner().Mult(direction, compact);
|
||||
const double compactInverseSeconds = elapsed(start);
|
||||
start = MPI_Wtime();
|
||||
reference.GetPreconditioner().Mult(direction, restricted);
|
||||
const double rootInverseSeconds = elapsed(start);
|
||||
difference = compact;
|
||||
difference -= restricted;
|
||||
const double inverseNorm = norm(restricted);
|
||||
const double inverseError = norm(difference);
|
||||
const auto relative = [](double error, double scale) {
|
||||
return scale == 0.0 ? (error == 0.0 ? 0.0 : std::numeric_limits<double>::infinity()) : error / scale;
|
||||
};
|
||||
if (rank == 0) {
|
||||
output << trial << ',' << subsystem.UsesCompactOperator() << ','
|
||||
<< subsystem.UsesCompactPreconditioner() << ',' << operatorNorm << ',' << operatorError << ','
|
||||
<< relative(operatorError, operatorNorm) << ',' << compactOperatorSeconds << ','
|
||||
<< rootOperatorSeconds << ',' << inverseNorm << ',' << inverseError << ','
|
||||
<< relative(inverseError, inverseNorm) << ',' << compactInverseSeconds << ','
|
||||
<< rootInverseSeconds << '\n';
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace plateau_diagnostics
|
||||
Reference in New Issue
Block a user