Snapshot optimized solver and audit diagnostics before numerical experiments

This commit is contained in:
2026-09-16 14:34:06 -04:00
parent 75cc638739
commit 80cc3d83d7
64 changed files with 11969 additions and 267 deletions

View File

@@ -1,5 +1,8 @@
module;
#include "interior_displacement_contractions.hpp"
#include <algorithm>
#include <array>
#include <cmath>
#include <cstdint>
@@ -406,13 +409,42 @@ namespace mean_field::operators {
PreparedPressureForceOperator::TryPrepare(
const context::pressure_force::PressureForceStateView &state,
const context::pressure_force::PressureForceDependencies &dependencies
) {
return TryPrepareImpl(state, dependencies, true);
}
PreparedPressureForceReport PreparedPressureForceOperator::PrepareReference(
const context::pressure_force::PressureForceStateView &state,
const context::pressure_force::PressureForceDependencies &dependencies
) {
auto result = TryPrepareImpl(state, dependencies, false, false);
if (!result.has_value()) throw_rejection(result.error());
return std::move(result).value();
}
PreparedPressureForceReport PreparedPressureForceOperator::PrepareCachedReference(
const context::pressure_force::PressureForceStateView &state,
const context::pressure_force::PressureForceDependencies &dependencies
) {
auto result = TryPrepareImpl(state, dependencies, true, false);
if (!result.has_value()) throw_rejection(result.error());
return std::move(result).value();
}
std::expected<PreparedPressureForceReport, PressureForcePreparationRejection>
PreparedPressureForceOperator::TryPrepareImpl(
const context::pressure_force::PressureForceStateView &state,
const context::pressure_force::PressureForceDependencies &dependencies,
const bool useFastPreparation, const bool useTensorPreparation
) {
const bool wasPrepared = m_isPrepared;
const bool implementationChanged = m_preparedWithFastMaterialAssembly != useFastPreparation ||
m_preparedWithTensorPreparation != useTensorPreparation;
PreparedPressureForceReport report;
report.contextReport = m_context.Prepare(state, dependencies);
if (!report.contextReport.DidAnyWork() && wasPrepared) {
if (!report.contextReport.DidAnyWork() && wasPrepared && !implementationChanged) {
return report;
}
@@ -433,16 +465,16 @@ namespace mean_field::operators {
PrepareStaticPlan();
}
if (report.contextReport.preparedGeometryState || !wasPrepared) {
const auto globalGeometryFailure = synchronize_rejection(PrepareGeometry(), m_fem.enthalpyFes->GetComm());
if (report.contextReport.preparedGeometryState || !wasPrepared || implementationChanged) {
const auto globalGeometryFailure = synchronize_rejection(PrepareGeometry(useTensorPreparation), m_fem.enthalpyFes->GetComm());
if (globalGeometryFailure.has_value()) {
return std::unexpected(*globalGeometryFailure);
}
}
std::optional<PressureForcePreparationRejection> localMaterialFailure;
if (report.contextReport.preparedMaterialState || !wasPrepared) {
localMaterialFailure = PrepareMaterialState();
if (report.contextReport.preparedMaterialState || !wasPrepared || implementationChanged) {
localMaterialFailure = PrepareMaterialState(useFastPreparation, useTensorPreparation);
}
const auto globalMaterialFailure = synchronize_rejection(localMaterialFailure, m_fem.enthalpyFes->GetComm());
@@ -450,7 +482,7 @@ namespace mean_field::operators {
return std::unexpected(*globalMaterialFailure);
}
if (report.contextReport.preparedMaterialState || !wasPrepared) {
if (report.contextReport.preparedMaterialState || !wasPrepared || implementationChanged) {
FinalizeDisplacementJacobianPreparation();
const auto globalAssemblyFailure =
@@ -482,6 +514,8 @@ namespace mean_field::operators {
"supported displacement size."
);
m_preparedWithTensorPreparation = useTensorPreparation;
m_preparedWithFastMaterialAssembly = useFastPreparation;
m_isPrepared = true;
return report;
@@ -564,7 +598,7 @@ namespace mean_field::operators {
}
}
std::optional<PressureForcePreparationRejection> PreparedPressureForceOperator::PrepareGeometry() {
std::optional<PressureForcePreparationRejection> PreparedPressureForceOperator::PrepareGeometry(const bool useTensorPreparation) {
mfem::Vector displacementLocal;
true_to_local(*m_fem.displacementFes, m_baseDisplacementTrue, displacementLocal);
@@ -574,6 +608,8 @@ namespace mean_field::operators {
mfem::Vector elementDisplacement;
mfem::Vector elementCompactification;
mfem::Vector sampleValue(3), tensorWorkspace;
mfem::DenseMatrix sampleGradient(3), tensorSamples;
for (ElementPAData &data : m_elements) {
MFEM_VERIFY(data.integrationRule != nullptr, "Prepared pressure-force geometry has no integration rule.");
@@ -626,13 +662,24 @@ namespace mean_field::operators {
data.physicalTestGradients.resize(quadraturePointCount);
const bool tensor = useTensorPreparation && data.displacementReferenceTable &&
data.displacementReferenceTable->HasTensorEvaluation3D();
if (tensor) data.displacementReferenceTable->EvaluateTensor3D(
data.baseDisplacementData->GetDofMatrix(), tensorSamples, tensorWorkspace);
for (int quadraturePoint = 0; quadraturePoint < quadraturePointCount; ++quadraturePoint) {
const mfem::IntegrationPoint &integrationPoint = data.integrationRule->IntPoint(quadraturePoint);
transformation->SetIntPoint(&integrationPoint);
if (tensor) for (int c = 0; c < 3; ++c) {
sampleValue(c) = tensorSamples(quadraturePoint, 4*c);
for (int a = 0; a < 3; ++a)
sampleGradient(c,a) = tensorSamples(quadraturePoint, 4*c+a+1);
}
const mapping::DomainMapper::ReferenceDisplacementSample sample{sampleValue, sampleGradient};
const mapping::MappingStatus mappingStatus = m_domainMapper.EvaluateVolume(
mappingData, *transformation, integrationPoint, workspace, mappingContext
mappingData, *transformation, integrationPoint, workspace, mappingContext, tensor ? &sample : nullptr
);
if (mappingStatus != mapping::MappingStatus::valid) {
@@ -675,7 +722,9 @@ namespace mean_field::operators {
return std::nullopt;
}
std::optional<PressureForcePreparationRejection> PreparedPressureForceOperator::PrepareMaterialState() {
std::optional<PressureForcePreparationRejection> PreparedPressureForceOperator::PrepareMaterialState(
const bool useFastPreparation, const bool useTensorPreparation
) {
mfem::Vector enthalpyLocal;
true_to_local(*m_fem.enthalpyFes, m_baseEnthalpyTrue, enthalpyLocal);
@@ -687,6 +736,8 @@ namespace mean_field::operators {
const mfem::Ordering::Type displacementOrdering = m_fem.displacementFes->GetOrdering();
std::optional<PressureForcePreparationRejection> materialFailure;
mfem::DenseMatrix weightedGradients;
mfem::DenseMatrix tensorFactors, tensorInverse;
for (ElementPAData &data : m_elements) {
enthalpyLocal.GetSubVector(data.enthalpyDofs, elementEnthalpy);
@@ -717,7 +768,9 @@ namespace mean_field::operators {
quadratureEnthalpy.SetSize(quadraturePointCount);
enthalpyBasis.Mult(elementEnthalpy, quadratureEnthalpy);
if (useTensorPreparation && data.enthalpyReferenceTable->HasTensorEvaluation3D())
data.enthalpyReferenceTable->EvaluateValuesTensor3D(elementEnthalpy, quadratureEnthalpy, m_tensorWorkspace);
else enthalpyBasis.Mult(elementEnthalpy, quadratureEnthalpy);
data.pressure.SetSize(quadraturePointCount);
@@ -796,14 +849,72 @@ namespace mean_field::operators {
data.elementResidual(vectorDof) -= residualContribution;
for (int enthalpyDof = 0; enthalpyDof < enthalpyDofCount; ++enthalpyDof) {
const double jacobianContribution =
pressureDerivative * weightedTestGradient * enthalpyBasis(quadraturePoint, enthalpyDof);
if (!std::isfinite(jacobianContribution)) {
materialFailure = non_finite_rejection();
continue;
if (!useFastPreparation) {
for (int enthalpyDof = 0; enthalpyDof < enthalpyDofCount; ++enthalpyDof) {
const double jacobianContribution = pressureDerivative * weightedTestGradient *
enthalpyBasis(quadraturePoint, enthalpyDof);
if (!std::isfinite(jacobianContribution)) {
materialFailure = non_finite_rejection();
continue;
}
data.enthalpyJacobian(vectorDof, enthalpyDof) -= jacobianContribution;
}
}
}
}
}
if (useFastPreparation && useTensorPreparation &&
data.displacementReferenceTable->HasTensorEvaluation3D() && !materialFailure.has_value()) {
// Build each enthalpy column by a tensor gradient transpose.
// These are the same -w P'(h) grad(test) B_h entries.
tensorFactors.SetSize(quadraturePointCount, 9);
m_tensorDuals.SetSize(quadraturePointCount, 9);
for (int q = 0; q < quadraturePointCount; ++q) {
data.baseMappingContexts.LoadInverseJacobian(q, tensorInverse);
const double w = -data.pressureDerivative(q)*data.quadratureWeights(q);
for (int c = 0; c < 3; ++c) for (int a = 0; a < 3; ++a)
tensorFactors(q, 3*c+a) = w*tensorInverse(a,c);
}
for (int col = 0; col < enthalpyDofCount; ++col) {
for (int a = 0; a < 9; ++a) for (int q = 0; q < quadraturePointCount; ++q)
m_tensorDuals(q,a) = tensorFactors(q,a)*enthalpyBasis(q,col);
data.displacementReferenceTable->ApplyGradientTransposeTensor3D(
m_tensorDuals, m_tensorElement, m_tensorWorkspace);
for (int c = 0; c < 3; ++c) for (int i = 0; i < scalarDisplacementDofCount; ++i)
data.enthalpyJacobian(i+c*scalarDisplacementDofCount,col) = m_tensorElement(i,c);
}
} else if (useFastPreparation && !materialFailure.has_value()) {
// J_h = -sum_q w_q P'(h_q) grad(test)_q B_h(q).
// Pack once, then accumulate small contiguous output tiles.
// The old loop is retained above as the independent reference.
weightedGradients.SetSize(displacementDofCount, quadraturePointCount);
for (int q = 0; q < quadraturePointCount; ++q) {
for (int c = 0; c < dimension; ++c) {
for (int i = 0; i < scalarDisplacementDofCount; ++i) {
const int row = vector_dof_index(
displacementOrdering, i, c, scalarDisplacementDofCount, dimension);
weightedGradients(row, q) = -data.pressureDerivative(q) *
(data.quadratureWeights(q) * data.physicalTestGradients[q](i, c));
}
}
}
const double *g = weightedGradients.Data();
const double *b = enthalpyBasis.Data();
double *j = data.enthalpyJacobian.Data();
constexpr int rowBlock = 32, columnBlock = 8;
for (int colBegin = 0; colBegin < enthalpyDofCount; colBegin += columnBlock) {
const int colEnd = std::min(colBegin + columnBlock, enthalpyDofCount);
for (int rowBegin = 0; rowBegin < displacementDofCount; rowBegin += rowBlock) {
const int rowEnd = std::min(rowBegin + rowBlock, displacementDofCount);
for (int q = 0; q < quadraturePointCount; ++q) {
const double *gradient = g + q * displacementDofCount;
for (int col = colBegin; col < colEnd; ++col) {
const double basis = b[q + col * quadraturePointCount];
double *column = j + col * displacementDofCount;
for (int row = rowBegin; row < rowEnd; ++row)
column[row] += gradient[row] * basis;
}
data.enthalpyJacobian(vectorDof, enthalpyDof) -= jacobianContribution;
}
}
}
@@ -938,8 +1049,32 @@ namespace mean_field::operators {
}
void PreparedPressureForceOperator::ApplyDisplacementJacobianAction(
const mfem::Vector &displacementVariation,
mfem::Vector &action
const mfem::Vector &displacementVariation, mfem::Vector &action
) const {
ApplyDisplacementJacobianActionImpl(displacementVariation, action, true);
}
void PreparedPressureForceOperator::ApplyDisplacementJacobianActionReference(
const mfem::Vector &displacementVariation, mfem::Vector &action
) const {
ApplyDisplacementJacobianActionImpl(displacementVariation, action, false, false);
}
void PreparedPressureForceOperator::ApplyDisplacementJacobianActionCachedReference(
const mfem::Vector &displacementVariation, mfem::Vector &action
) const {
ApplyDisplacementJacobianActionImpl(displacementVariation, action, true, false);
}
int PreparedPressureForceOperator::GetTensorElementCount() const noexcept {
int count = 0;
for (const auto &data : m_elements)
count += data.displacementReferenceTable->HasTensorEvaluation3D();
return count;
}
void PreparedPressureForceOperator::ApplyDisplacementJacobianActionImpl(
const mfem::Vector &displacementVariation, mfem::Vector &action, const bool useFastPath, const bool useTensorPath
) const {
VerifyPrepared();
@@ -1017,7 +1152,39 @@ namespace mean_field::operators {
matrixTemporary.SetSize(dimension, dimension);
physicalTestGradientVariation.SetSize(scalarDisplacementDofCount, dimension);
for (int quadraturePoint = 0; quadraturePoint < quadraturePointCount; ++quadraturePoint) {
const bool tensor = useTensorPath && data.displacementReferenceTable->HasTensorEvaluation3D();
if (tensor) {
data.displacementReferenceTable->EvaluateTensor3D(directionDofs, m_tensorSamples, m_tensorWorkspace);
m_tensorDuals.SetSize(quadraturePointCount, 9);
for (int q = 0; q < quadraturePointCount; ++q) {
data.baseMappingContexts.LoadInverseJacobian(q, inverseElementJacobian);
double gradient[3][3]{};
for (int c = 0; c < 3; ++c) for (int j = 0; j < 3; ++j)
for (int a = 0; a < 3; ++a)
gradient[c][j] += m_tensorSamples(q, 4*c+a+1)*inverseElementJacobian(a,j);
const double trace = gradient[0][0]+gradient[1][1]+gradient[2][2];
const double weight = data.pressure(q)*data.quadratureWeights(q);
// d(-P grad(test) dx): grad_ref(test) J^-1 (du/dx - tr(du/dx) I).
for (int c = 0; c < 3; ++c) for (int a = 0; a < 3; ++a) {
double value = 0.0;
for (int j = 0; j < 3; ++j)
value += inverseElementJacobian(a,j)*(gradient[j][c]-(j==c ? trace : 0.0));
m_tensorDuals(q, 3*c+a) = weight*value;
}
}
data.displacementReferenceTable->ApplyGradientTransposeTensor3D(
m_tensorDuals, m_tensorElement, m_tensorWorkspace);
for (int c = 0; c < 3; ++c) for (int i = 0; i < scalarDisplacementDofCount; ++i)
elementAction(i+c*scalarDisplacementDofCount) = m_tensorElement(i,c);
}
for (int quadraturePoint = 0; !tensor && quadraturePoint < quadraturePointCount; ++quadraturePoint) {
if (useFastPath && dimension == 3) {
pressure_displacement_action_3d(
directionDofs, data.physicalTestGradients[quadraturePoint],
data.pressure(quadraturePoint) * data.quadratureWeights(quadraturePoint), elementAction);
continue;
}
const mfem::DenseMatrix &referenceTestGradient =
data.displacementReferenceTable->GetGradients(quadraturePoint);
@@ -1076,7 +1243,7 @@ namespace mean_field::operators {
m_displacementMap.gather(m_fullDisplacementAction, action);
++m_displacementJacobianStatistics.applications;
if (useFastPath) ++m_displacementJacobianStatistics.applications;
}
void PreparedPressureForceOperator::ApplyCompleteJacobianAction(