This commit uses global pre allocated work space to dramatically reduce memory usage and allocation time
252 lines
12 KiB
C++
252 lines
12 KiB
C++
#include <chrono>
|
|
#include <exception>
|
|
#include <iostream>
|
|
#include <numbers>
|
|
#include <stdexcept>
|
|
#include <utility>
|
|
|
|
#include <mfem.hpp>
|
|
|
|
import mean_field;
|
|
|
|
using namespace mean_field;
|
|
|
|
namespace {
|
|
[[nodiscard]] const char *trialDispositionName(
|
|
const solver::nonlinear::LineSearchTrialDisposition disposition
|
|
) noexcept {
|
|
using Disposition = solver::nonlinear::LineSearchTrialDisposition;
|
|
switch (disposition) {
|
|
case Disposition::accepted:
|
|
return "accepted";
|
|
case Disposition::inadmissible_state:
|
|
return "inadmissible state";
|
|
case Disposition::non_finite_state:
|
|
return "non-finite state";
|
|
case Disposition::non_finite_residual:
|
|
return "non-finite residual";
|
|
case Disposition::insufficient_decrease:
|
|
default:
|
|
return "insufficient decrease";
|
|
}
|
|
}
|
|
|
|
[[nodiscard]] const char *linearStatusName(const solver::LinearSolveStatus status) noexcept {
|
|
switch (status) {
|
|
case solver::LinearSolveStatus::converged:
|
|
return "converged";
|
|
case solver::LinearSolveStatus::maximum_iterations:
|
|
return "maximum iterations";
|
|
case solver::LinearSolveStatus::breakdown:
|
|
return "breakdown";
|
|
case solver::LinearSolveStatus::non_finite:
|
|
return "non-finite";
|
|
case solver::LinearSolveStatus::backend_failure:
|
|
default:
|
|
return "backend failure";
|
|
}
|
|
}
|
|
} // namespace
|
|
|
|
int main(
|
|
int argc,
|
|
char **argv
|
|
) {
|
|
mfem::Mpi::Init(argc, argv);
|
|
int exitCode = 0;
|
|
|
|
try {
|
|
mfem::Device device("cpu");
|
|
int rank = 0;
|
|
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
|
|
|
|
utils::Args arguments;
|
|
arguments.mesh_file = "sandbox.smesh";
|
|
arguments.p.rtol = 1.0e-12;
|
|
arguments.p.atol = 1.0e-12;
|
|
const auto discretizationStart = std::chrono::steady_clock::now();
|
|
auto finiteElements = fem::setup_fem(arguments.mesh_file, arguments, 0);
|
|
if (!finiteElements.okay()) {
|
|
throw std::runtime_error("The sandbox could not construct its finite-element discretization.");
|
|
}
|
|
if (rank == 0) {
|
|
std::cout << "Finite-element setup: "
|
|
<< std::chrono::duration<double>(std::chrono::steady_clock::now() - discretizationStart).count()
|
|
<< " s\n";
|
|
}
|
|
|
|
constexpr double radius = utils::RADIUS;
|
|
constexpr double mass = utils::MASS;
|
|
constexpr double angularMomentum = 0.1;
|
|
constexpr double gravitationalConstant = utils::G;
|
|
const double polytropicConstant =
|
|
2.0 * gravitationalConstant * radius * radius / std::numbers::pi_v<double>;
|
|
std::println("K = {}", polytropicConstant);
|
|
const double centralDensity =
|
|
std::numbers::pi_v<double> * mass / (4.0 * radius * radius * radius);
|
|
|
|
auto stellarModel = model::StellarModel(
|
|
eos::Polytrope({.n = 1.0, .K = polytropicConstant}),
|
|
surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}),
|
|
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{mass}}),
|
|
integral::FixedAngularMomentum({
|
|
.Jtotal = dimensions::AngularMomentumValue{angularMomentum},
|
|
.axis = {0.0, 0.0, 1.0},
|
|
.center = {0.0, 0.0, 0.0}
|
|
}),
|
|
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{centralDensity}})
|
|
);
|
|
|
|
auto discretization = equilibrium::makeStellarDiscretization(
|
|
std::move(finiteElements),
|
|
normalization::PhysicalRieszDiagonal{
|
|
dimensions::LengthValue{radius},
|
|
gravitationalConstant
|
|
}
|
|
);
|
|
|
|
auto preconditioner = preconditioning::makePreconditioner();
|
|
auto linearSolver = solver::linear::FGMRES({.restartLength = 40, .printLevel = 1});
|
|
const auto contextStart = std::chrono::steady_clock::now();
|
|
auto context = solver::makeContext(
|
|
std::move(stellarModel), std::move(discretization), std::move(preconditioner), std::move(linearSolver)
|
|
);
|
|
if (rank == 0) {
|
|
std::cout << "Solver context setup: "
|
|
<< std::chrono::duration<double>(std::chrono::steady_clock::now() - contextStart).count()
|
|
<< " s\n";
|
|
}
|
|
|
|
auto observer = solver::nonlinear::makeObserver(
|
|
[](const solver::nonlinear::BeforeIteration &event) {
|
|
int rank = 0;
|
|
MPI_Comm_rank(event.communicator, &rank);
|
|
if (rank == 0) {
|
|
std::cout << "Newton " << event.iteration << ": |F| = " << event.residualNorm << '\n';
|
|
}
|
|
},
|
|
[](const solver::nonlinear::AfterLineSearchTrial &event) {
|
|
int rank = 0;
|
|
MPI_Comm_rank(event.communicator, &rank);
|
|
if (rank != 0) {
|
|
return;
|
|
}
|
|
|
|
std::cout << " trial " << event.trial + 1 << ": step = " << event.stepLength
|
|
<< ", outcome = " << trialDispositionName(event.disposition);
|
|
if (!event.rejectionSource.empty()) {
|
|
std::cout << ", source = " << event.rejectionSource;
|
|
}
|
|
if (event.metric.has_value()) {
|
|
std::cout << ", |F| = " << event.metric->residualNorm;
|
|
}
|
|
if (event.minimumJacobianDeterminant.has_value()) {
|
|
std::cout << ", min(det J_map) = " << *event.minimumJacobianDeterminant;
|
|
}
|
|
std::cout << ", prepare = " << event.preparationSeconds << " s"
|
|
<< ", metric = " << event.metricSeconds << " s\n";
|
|
},
|
|
[](const solver::nonlinear::AfterIteration &event) {
|
|
int rank = 0;
|
|
MPI_Comm_rank(event.communicator, &rank);
|
|
if (rank == 0) {
|
|
if (event.geometryPreflight.has_value()) {
|
|
const auto &geometry = *event.geometryPreflight;
|
|
std::cout << " geometry preflight = " << event.geometryPreflightSeconds
|
|
<< " s, samples = " << geometry.sampledQuadraturePointCount;
|
|
if (geometry.limitedByGeometry) {
|
|
std::cout << ", safe step = " << geometry.stepSize
|
|
<< ", boundary = " << geometry.boundaryStepSize
|
|
<< ", limiting rank = " << geometry.limitingRank
|
|
<< ", element = " << geometry.limitingElement;
|
|
}
|
|
std::cout << '\n';
|
|
}
|
|
std::cout << " step = " << event.acceptedStepLength
|
|
<< ", trials = " << event.lineSearchTrials
|
|
<< ", |F| = " << event.residualNorm
|
|
<< ", iteration = " << event.iterationSeconds << " s"
|
|
<< ", line search = " << event.lineSearchSeconds << " s"
|
|
<< ", trial preparation = " << event.trialPreparationSeconds << " s"
|
|
<< ", accepted refresh = " << event.preconditionerRefreshSeconds << " s\n";
|
|
if (event.rollbackSeconds > 0.0) {
|
|
std::cout << " rollback = " << event.rollbackSeconds << " s\n";
|
|
}
|
|
if (event.linearSolve.has_value()) {
|
|
const auto &linear = *event.linearSolve;
|
|
std::cout << " FGMRES: " << linearStatusName(linear.status)
|
|
<< ", iterations = " << linear.iterations
|
|
<< ", restarts = " << linear.restarts
|
|
<< ", initial/|b| = "
|
|
<< (linear.rightHandSideNorm > 0.0
|
|
? linear.initialResidualNorm / linear.rightHandSideNorm
|
|
: 0.0)
|
|
<< ", true/|b| = " << linear.relativeTrueResidualNorm
|
|
<< ", J calls = " << linear.operatorApplications
|
|
<< ", P^-1 calls = " << linear.inversePreconditionerApplications
|
|
<< ", solve = " << linear.solveSeconds << " s"
|
|
<< ", J time = " << linear.operatorSeconds << " s"
|
|
<< ", P^-1 time = " << linear.inversePreconditionerSeconds << " s\n";
|
|
}
|
|
}
|
|
}
|
|
);
|
|
|
|
auto nonlinearSolver = solver::nonlinear::Newton(solver::nonlinear::NewtonOptions{
|
|
.relativeTolerance = 1.0e-8,
|
|
.absoluteTolerance = 0.0,
|
|
.maximumIterations = 30,
|
|
.linearSolve = {
|
|
.relativeTolerance = 3.0e-2,
|
|
.absoluteTolerance = 0.0,
|
|
.maximumIterations = 200
|
|
},
|
|
.backtracking = {}
|
|
});
|
|
|
|
auto equilibriumSolver = solver::make(context, nonlinearSolver, observer);
|
|
const auto evaluateStart = std::chrono::steady_clock::now();
|
|
auto report = equilibriumSolver.evaluate();
|
|
if (rank == 0) {
|
|
std::cout << "Evaluation: "
|
|
<< std::chrono::duration<double>(std::chrono::steady_clock::now() - evaluateStart).count()
|
|
<< " s\n";
|
|
const auto &diagnostics = report.diagnostics();
|
|
std::cout << "Totals: linear = " << diagnostics.totalLinearSolveSeconds
|
|
<< " s, geometry preflight = " << diagnostics.totalGeometryPreflightSeconds
|
|
<< " s, line search = " << diagnostics.totalLineSearchSeconds
|
|
<< " s, trial preparation = " << diagnostics.totalTrialPreparationSeconds
|
|
<< " s, accepted refresh = " << diagnostics.totalPreconditionerRefreshSeconds
|
|
<< " s, rollback = " << diagnostics.totalRollbackSeconds
|
|
<< " s, geometry-limited iterations = " << diagnostics.geometryLimitedIterations
|
|
<< ", inadmissible trials = " << diagnostics.inadmissibleLineSearchTrials
|
|
<< ", non-finite trials = " << diagnostics.nonFiniteLineSearchTrials
|
|
<< ", insufficient-decrease trials = " << diagnostics.insufficientDecreaseTrials << '\n';
|
|
}
|
|
|
|
if (report.converged()) {
|
|
auto structureView = report.structureView();
|
|
std::cout << "Converged with " << structureView.state().size() << " state values.\n";
|
|
|
|
// These are the eventual persistence APIs. Both deliberately
|
|
// throw "not implemented" until the checkpoint schema is chosen:
|
|
// auto structure = structureView.capture();
|
|
// equilibrium::serialize(structureView, "structure.checkpoint");
|
|
} else {
|
|
auto checkpointView = report.lastAcceptedCheckpointView();
|
|
std::cerr << "Solve stopped after " << report.completedNonlinearIterations()
|
|
<< " accepted steps: " << report.failure().message << '\n'
|
|
<< "The last checkpoint has " << checkpointView.state().size() << " state values.\n";
|
|
// auto checkpoint = checkpointView.capture();
|
|
// equilibrium::serialize(checkpointView, "failed-step.checkpoint");
|
|
exitCode = 1;
|
|
}
|
|
} catch (const std::exception &error) {
|
|
std::cerr << "sandbox failure: " << error.what() << '\n';
|
|
exitCode = 2;
|
|
}
|
|
|
|
mfem::Mpi::Finalize();
|
|
return exitCode;
|
|
}
|