perf(allocations): reduced overall allocations by 95%, increaseed jacobian applicatin by 2x
This commit uses global pre allocated work space to dramatically reduce memory usage and allocation time
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
module;
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
@@ -8,6 +9,7 @@ module;
|
||||
#include <cstdint>
|
||||
#include <exception>
|
||||
#include <expected>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
@@ -17,6 +19,7 @@ module;
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <mfem.hpp>
|
||||
#include <mpi.h>
|
||||
@@ -37,6 +40,11 @@ export namespace mean_field::solver {
|
||||
template <typename Context, typename NewtonConfiguration, typename Observer> class StellarEquilibriumSolver;
|
||||
} // namespace mean_field::solver
|
||||
|
||||
export namespace mean_field::solver::detail {
|
||||
// Internal, synchronous experiment access; not a stable solver API.
|
||||
struct StellarEquilibriumContextDiagnostics;
|
||||
}
|
||||
|
||||
namespace mean_field::solver::detail {
|
||||
template <typename Model, typename Discretization>
|
||||
using StellarContextProblem =
|
||||
@@ -256,6 +264,7 @@ export namespace mean_field::solver {
|
||||
private:
|
||||
template <typename, typename, typename> friend class StellarEquilibriumSolver;
|
||||
friend struct detail::StellarEquilibriumContextAssembly;
|
||||
friend struct detail::StellarEquilibriumContextDiagnostics;
|
||||
|
||||
using NormalizedOperatorType = detail::StellarContextNormalizedOperator<ProblemType>;
|
||||
using PhysicalInverseType = detail::StellarContextPhysicalInverse<PreconditionerPrescriptionType, ProblemType>;
|
||||
@@ -323,11 +332,16 @@ export namespace mean_field::solver {
|
||||
trialNormalizedResidual(RequireProblem(storage).EquationSize()),
|
||||
linearRightHandSide(RequireProblem(storage).EquationSize()),
|
||||
normalizedCorrection(RequireProblem(storage).StateSize()),
|
||||
physicalCorrection(RequireProblem(storage).StateSize()),
|
||||
volumeDisplacementDirection(
|
||||
RequireProblem(storage).GetPhysicalOperator().GetDomainDeformation().volumeDisplacementSize()
|
||||
),
|
||||
candidatePhysicalState(RequireProblem(storage).StateSize()),
|
||||
normalizedOperator(std::make_unique<NormalizedOperatorType>(RequireProblem(storage))) {
|
||||
ValidateInitialState();
|
||||
InitializeWorkspaces();
|
||||
PrepareInitialOperator();
|
||||
InitializeGeometryPreflightRules();
|
||||
|
||||
physicalInverse = std::unique_ptr<PhysicalInverseType>{new PhysicalInverseType(
|
||||
PreparePhysicalInverse(std::move(preconditionerPrescription), RequireProblem(storage))
|
||||
@@ -375,6 +389,9 @@ export namespace mean_field::solver {
|
||||
trialNormalizedResidual.Size() == problem->EquationSize() &&
|
||||
linearRightHandSide.Size() == problem->EquationSize() &&
|
||||
normalizedCorrection.Size() == problem->StateSize() &&
|
||||
physicalCorrection.Size() == problem->StateSize() &&
|
||||
volumeDisplacementDirection.Size() ==
|
||||
problem->GetPhysicalOperator().GetDomainDeformation().volumeDisplacementSize() &&
|
||||
candidatePhysicalState.Size() == problem->StateSize() &&
|
||||
storage->physicalState->Size() == problem->StateSize();
|
||||
}
|
||||
@@ -464,6 +481,26 @@ export namespace mean_field::solver {
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] deformation::LargestSafeNewtonStepSizeEstimate EstimateLargestSafeStepSize(
|
||||
const double maximumStepSize,
|
||||
const double fractionToBoundarySafety
|
||||
) {
|
||||
normalizedOperator->DenormalizeState(normalizedCorrection, physicalCorrection);
|
||||
const auto &physicalOperator = Problem().GetPhysicalOperator();
|
||||
Problem().BuildVolumeDisplacementDirection(physicalCorrection, volumeDisplacementDirection);
|
||||
|
||||
const fem::FEM &finiteElements =
|
||||
equilibrium::detail::StellarEquilibriumProblemFactory::FiniteElementModel(Problem());
|
||||
return deformation::estimate_largest_safe_newton_step_size(
|
||||
Problem().GetDiscretization().domainMapper(), *finiteElements.displacementFes,
|
||||
*finiteElements.compactificationCoordinate, physicalOperator.GetGeneratedVolumeDisplacement(),
|
||||
volumeDisplacementDirection, geometryPreflightRules,
|
||||
{.maximumStepSize = maximumStepSize,
|
||||
.determinantFloor = 0.0,
|
||||
.fractionToBoundarySafety = fractionToBoundarySafety}
|
||||
);
|
||||
}
|
||||
|
||||
std::shared_ptr<Storage> storage;
|
||||
DependencyLedger dependencyLedger;
|
||||
mfem::Vector acceptedNormalizedState;
|
||||
@@ -472,7 +509,10 @@ export namespace mean_field::solver {
|
||||
mfem::Vector trialNormalizedResidual;
|
||||
mfem::Vector linearRightHandSide;
|
||||
mfem::Vector normalizedCorrection;
|
||||
mfem::Vector physicalCorrection;
|
||||
mfem::Vector volumeDisplacementDirection;
|
||||
mfem::Vector candidatePhysicalState;
|
||||
std::vector<deformation::NewtonStepGeometryRule> geometryPreflightRules;
|
||||
double acceptedMinimumJacobianDeterminant{std::numeric_limits<double>::quiet_NaN()};
|
||||
std::unique_ptr<NormalizedOperatorType> normalizedOperator;
|
||||
std::unique_ptr<PhysicalInverseType> physicalInverse;
|
||||
@@ -565,12 +605,14 @@ export namespace mean_field::solver {
|
||||
|
||||
void InitializeWorkspaces() {
|
||||
normalizedOperator->NormalizeState(AcceptedPhysicalState(), acceptedNormalizedState);
|
||||
trialNormalizedState = acceptedNormalizedState;
|
||||
acceptedNormalizedResidual = 0.0;
|
||||
trialNormalizedResidual = 0.0;
|
||||
linearRightHandSide = 0.0;
|
||||
normalizedCorrection = 0.0;
|
||||
candidatePhysicalState = AcceptedPhysicalState();
|
||||
trialNormalizedState = acceptedNormalizedState;
|
||||
acceptedNormalizedResidual = 0.0;
|
||||
trialNormalizedResidual = 0.0;
|
||||
linearRightHandSide = 0.0;
|
||||
normalizedCorrection = 0.0;
|
||||
physicalCorrection = 0.0;
|
||||
volumeDisplacementDirection = 0.0;
|
||||
candidatePhysicalState = AcceptedPhysicalState();
|
||||
}
|
||||
|
||||
void PrepareInitialOperator() {
|
||||
@@ -582,6 +624,97 @@ export namespace mean_field::solver {
|
||||
trialNormalizedResidual = acceptedNormalizedResidual;
|
||||
}
|
||||
|
||||
void AppendGeometryPreflightRule(
|
||||
const int element,
|
||||
const mfem::IntegrationRule &integrationRule
|
||||
) {
|
||||
geometryPreflightRules.push_back({.element = element, .integrationRule = &integrationRule});
|
||||
}
|
||||
|
||||
void InitializeGeometryPreflightRules() {
|
||||
const ProblemType &problem = Problem();
|
||||
const fem::FEM &finiteElements =
|
||||
equilibrium::detail::StellarEquilibriumProblemFactory::FiniteElementModel(problem);
|
||||
if (finiteElements.mesh == nullptr || finiteElements.displacementFes == nullptr ||
|
||||
finiteElements.compactificationCoordinate == nullptr) {
|
||||
throw std::logic_error(
|
||||
"The Newton geometry preflight requires complete displacement geometry data."
|
||||
);
|
||||
}
|
||||
if (!problem.GetPhysicalOperator().GetDomainDeformation().descriptor().linearOnReferenceGeometry) {
|
||||
throw std::invalid_argument(
|
||||
"The Newton geometry preflight requires a domain deformation that is linear on the "
|
||||
"reference geometry."
|
||||
);
|
||||
}
|
||||
|
||||
geometryPreflightRules.clear();
|
||||
geometryPreflightRules.reserve(
|
||||
static_cast<std::size_t>(finiteElements.mesh->GetNE()) * static_cast<std::size_t>(10)
|
||||
);
|
||||
|
||||
const int dimension = problem.GetDiscretization().domainMapper().GetDimension();
|
||||
for (int element = 0; element < finiteElements.mesh->GetNE(); ++element) {
|
||||
const mfem::FiniteElement *finiteElement = finiteElements.displacementFes->GetFE(element);
|
||||
mfem::ElementTransformation *transformation =
|
||||
finiteElements.mesh->GetElementTransformation(element);
|
||||
if (finiteElement == nullptr || transformation == nullptr) {
|
||||
throw std::logic_error(
|
||||
"The Newton geometry preflight encountered incomplete element geometry data."
|
||||
);
|
||||
}
|
||||
const int geometryInspectionOrder =
|
||||
std::max(finiteElement->GetOrder() + 2, 2 * dimension * finiteElement->GetOrder());
|
||||
AppendGeometryPreflightRule(
|
||||
element, mfem::IntRules.Get(transformation->GetGeometryType(), geometryInspectionOrder)
|
||||
);
|
||||
}
|
||||
|
||||
const auto appendPreparedRules = [this](const auto &preparedOperator) {
|
||||
preparedOperator.VisitMappedGeometryRules(
|
||||
[this](const int element, const mfem::IntegrationRule &integrationRule) {
|
||||
AppendGeometryPreflightRule(element, integrationRule);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const auto &physicalOperator = problem.GetPhysicalOperator();
|
||||
const auto &gravityGeometry = physicalOperator.GetGravityContext().GetGeometryContext();
|
||||
appendPreparedRules(gravityGeometry.GetMassOperator());
|
||||
appendPreparedRules(gravityGeometry.GetSourceOperator());
|
||||
appendPreparedRules(physicalOperator.GetBarotropicClosureOperator());
|
||||
appendPreparedRules(physicalOperator.GetHydrostaticOperator());
|
||||
|
||||
const auto &displacementOperator = physicalOperator.GetDisplacementOperator();
|
||||
appendPreparedRules(displacementOperator.GetPressureOperator());
|
||||
appendPreparedRules(displacementOperator.GetGravityOperator());
|
||||
appendPreparedRules(displacementOperator.GetRotationalOperator());
|
||||
appendPreparedRules(physicalOperator.GetMassNormalizationOperator());
|
||||
|
||||
if constexpr (ProblemType::hasFixedAngularMomentum) {
|
||||
appendPreparedRules(problem.GetPreparedOperator().GetAngularMomentumConstraint());
|
||||
}
|
||||
|
||||
const auto ruleLess = [](const deformation::NewtonStepGeometryRule &left,
|
||||
const deformation::NewtonStepGeometryRule &right) {
|
||||
if (left.element != right.element) {
|
||||
return left.element < right.element;
|
||||
}
|
||||
return std::less<const mfem::IntegrationRule *>{}(left.integrationRule, right.integrationRule);
|
||||
};
|
||||
std::sort(geometryPreflightRules.begin(), geometryPreflightRules.end(), ruleLess);
|
||||
geometryPreflightRules.erase(
|
||||
std::unique(
|
||||
geometryPreflightRules.begin(), geometryPreflightRules.end(),
|
||||
[](const deformation::NewtonStepGeometryRule &left,
|
||||
const deformation::NewtonStepGeometryRule &right) {
|
||||
return left.element == right.element && left.integrationRule == right.integrationRule;
|
||||
}
|
||||
),
|
||||
geometryPreflightRules.end()
|
||||
);
|
||||
}
|
||||
|
||||
[[nodiscard]] auto PrepareOperator(const mfem::Vector &normalizedState) {
|
||||
if constexpr (ProblemType::generatedRotationProviderCount == 0) {
|
||||
return normalizedOperator->Prepare(
|
||||
@@ -695,6 +828,42 @@ export namespace mean_field::solver {
|
||||
concept StellarEquilibriumContextType = IsStellarEquilibriumContext<std::remove_cvref_t<Candidate>>::value;
|
||||
} // namespace mean_field::solver
|
||||
|
||||
export namespace mean_field::solver::detail {
|
||||
struct StellarEquilibriumContextDiagnostics final {
|
||||
// The callback must not retain references to runtime storage. It may
|
||||
// prepare trial states, but accepted vectors must remain unchanged.
|
||||
// Restore the production preparation and correction on every exit.
|
||||
template <typename Context, typename Callback>
|
||||
static void WithState(Context &context, Callback &&callback) {
|
||||
if (context.hasActiveSolver() || !context.isReady()) {
|
||||
throw std::logic_error("Diagnostics require a ready context with no active solver.");
|
||||
}
|
||||
auto &state = *context.m_state;
|
||||
mfem::Vector savedCorrection(state.normalizedCorrection);
|
||||
context.AcquireSolver();
|
||||
try {
|
||||
state.BeginEvaluation();
|
||||
std::invoke(std::forward<Callback>(callback), state,
|
||||
equilibrium::detail::StellarEquilibriumProblemFactory::FiniteElementModel(state.Problem()));
|
||||
state.RestoreAccepted();
|
||||
state.normalizedCorrection = savedCorrection;
|
||||
context.ReleaseSolver();
|
||||
} catch (...) {
|
||||
const auto original = std::current_exception();
|
||||
try {
|
||||
state.RestoreAccepted();
|
||||
state.normalizedCorrection = savedCorrection;
|
||||
} catch (...) {
|
||||
context.ReleaseSolver();
|
||||
throw;
|
||||
}
|
||||
context.ReleaseSolver();
|
||||
std::rethrow_exception(original);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
namespace mean_field::solver::detail {
|
||||
[[nodiscard]] inline physics::RigidRotation ZeroRigidRotation() {
|
||||
mfem::Vector angularVelocity(3);
|
||||
@@ -973,12 +1142,14 @@ export namespace mean_field::solver {
|
||||
|
||||
struct IterationTimings final {
|
||||
Clock::time_point start{};
|
||||
double geometryPreflightSeconds{0.0};
|
||||
double lineSearchSeconds{0.0};
|
||||
double trialPreparationSeconds{0.0};
|
||||
double metricEvaluationSeconds{0.0};
|
||||
double preconditionerRefreshSeconds{0.0};
|
||||
double rollbackSeconds{0.0};
|
||||
double observerSeconds{0.0};
|
||||
std::optional<deformation::LargestSafeNewtonStepSizeEstimate> geometryPreflight;
|
||||
};
|
||||
|
||||
public:
|
||||
@@ -1100,14 +1271,39 @@ export namespace mean_field::solver {
|
||||
}
|
||||
|
||||
const nonlinear::MetricEvaluation previousMetric = acceptedMetric;
|
||||
bool accepted = false;
|
||||
double acceptedStepLength = 0.0;
|
||||
int lineSearchTrials = 0;
|
||||
const Clock::time_point geometryPreflightStart = Clock::now();
|
||||
timings.geometryPreflight = state.EstimateLargestSafeStepSize(
|
||||
nextLineSearchStepLength, options.backtracking.fractionToBoundarySafety
|
||||
);
|
||||
timings.geometryPreflightSeconds =
|
||||
std::chrono::duration<double>(Clock::now() - geometryPreflightStart).count();
|
||||
diagnostics.totalGeometryPreflightSeconds += timings.geometryPreflightSeconds;
|
||||
diagnostics.lastGeometryPreflight = timings.geometryPreflight;
|
||||
if (timings.geometryPreflight->limitedByGeometry) {
|
||||
++diagnostics.geometryLimitedIterations;
|
||||
}
|
||||
|
||||
if (timings.geometryPreflight->stepSize < options.backtracking.minimumStepLength) {
|
||||
diagnostics.finalResidualNorm = acceptedMetric.residualNorm;
|
||||
NotifyAfter(
|
||||
iteration, nonlinear::IterationDisposition::globalization_failure, false, 0.0, 0,
|
||||
diagnostics.initialResidualNorm, previousMetric, acceptedMetric, linearReport, timings, state
|
||||
);
|
||||
return Failure(
|
||||
state, std::move(diagnostics), StellarEquilibriumFailureReason::globalization_failure,
|
||||
"The geometry preflight found no orientation-preserving Newton step at or above the "
|
||||
"configured minimum step length."
|
||||
);
|
||||
}
|
||||
|
||||
bool accepted = false;
|
||||
double acceptedStepLength = 0.0;
|
||||
int lineSearchTrials = 0;
|
||||
nonlinear::MetricEvaluation trialMetric{};
|
||||
StellarEquilibriumFailureReason rejectionReason =
|
||||
StellarEquilibriumFailureReason::globalization_failure;
|
||||
std::string rejectionMessage = "The backtracking line search found no acceptable Newton step.";
|
||||
double stepLength = nextLineSearchStepLength;
|
||||
double stepLength = timings.geometryPreflight->stepSize;
|
||||
|
||||
const Clock::time_point lineSearchStart = Clock::now();
|
||||
try {
|
||||
@@ -1553,11 +1749,13 @@ export namespace mean_field::solver {
|
||||
.relativeResidualNorm = RelativeResidual(metric.residualNorm, initialResidualNorm),
|
||||
.merit = metric.merit,
|
||||
.iterationSeconds = DurationExcludingObserver(timings.start, timings.observerSeconds),
|
||||
.geometryPreflightSeconds = timings.geometryPreflightSeconds,
|
||||
.lineSearchSeconds = timings.lineSearchSeconds,
|
||||
.trialPreparationSeconds = timings.trialPreparationSeconds,
|
||||
.metricEvaluationSeconds = timings.metricEvaluationSeconds,
|
||||
.preconditionerRefreshSeconds = timings.preconditionerRefreshSeconds,
|
||||
.rollbackSeconds = timings.rollbackSeconds,
|
||||
.geometryPreflight = timings.geometryPreflight,
|
||||
.linearSolve = linearReport,
|
||||
.communicator = state.Problem().GetCommunicator(),
|
||||
.physicalState = detail::ReadOnlySpan(state.AcceptedPhysicalState()),
|
||||
|
||||
Reference in New Issue
Block a user