Files
MeanField/libmeanfield/impl/operators/prepared_pressure_force.cpp
Emily Boudreaux 0f3ca8050b feat(field-support): added field support system, mid migration
currently the barotope and the pressure force operator are migrated to the new support system
2026-08-23 10:13:53 -04:00

1137 lines
43 KiB
C++

module;
#include <array>
#include <cmath>
#include <cstdint>
#include <limits>
#include <utility>
#include <mfem.hpp>
module mean_field;
import :operators.prepared_pressure_force;
import :field.registry;
import :utils.blocks;
import :utils.domain;
namespace {
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
using PressureDomain = mean_field::field::FieldDomainT<mean_field::field::Enthalpy>;
void verify_required_spaces(const mean_field::fem::FEM &f) {
MFEM_VERIFY(f.mesh != nullptr, "PreparedPressureForceOperator requires a mesh.");
MFEM_VERIFY(
f.enthalpyFes != nullptr, "PreparedPressureForceOperator requires the enthalpy "
"finite-element space."
);
MFEM_VERIFY(
f.displacementFes != nullptr, "PreparedPressureForceOperator requires the displacement "
"finite-element space."
);
MFEM_VERIFY(
f.compactificationFes != nullptr, "PreparedPressureForceOperator requires the compactification "
"finite-element space."
);
MFEM_VERIFY(
f.compactificationCoordinate != nullptr, "PreparedPressureForceOperator requires the compactification "
"coordinate."
);
MFEM_VERIFY(
f.quadratureFactory != nullptr, "PreparedPressureForceOperator requires the quadrature-rule "
"factory."
);
}
[[nodiscard]]
bool element_is_in_pressure_support(const int attribute) {
return DomainSchema::template attribute_belongs_to<PressureDomain>(attribute);
}
void true_to_local(
const mfem::ParFiniteElementSpace &finiteElementSpace,
const mfem::Vector &trueVector,
mfem::Vector &localVector
) {
MFEM_VERIFY(
trueVector.Size() == finiteElementSpace.GetTrueVSize(),
"Prepared pressure-force true vector has the wrong size."
);
localVector.SetSize(finiteElementSpace.GetVSize());
const mfem::Operator *prolongation = finiteElementSpace.GetProlongationMatrix();
if (prolongation != nullptr) {
prolongation->Mult(trueVector, localVector);
} else {
localVector = trueVector;
}
}
void local_to_true(
const mfem::ParFiniteElementSpace &finiteElementSpace,
const mfem::Vector &localVector,
mfem::Vector &trueVector
) {
MFEM_VERIFY(
localVector.Size() == finiteElementSpace.GetVSize(),
"Prepared pressure-force local vector has the wrong size."
);
trueVector.SetSize(finiteElementSpace.GetTrueVSize());
trueVector = 0.0;
const mfem::Operator *prolongation = finiteElementSpace.GetProlongationMatrix();
if (prolongation != nullptr) {
prolongation->MultTranspose(localVector, trueVector);
} else {
trueVector = localVector;
}
}
[[nodiscard]]
int vector_dof_index(
const mfem::Ordering::Type ordering,
const int scalarDof,
const int component,
const int scalarDofCount,
const int dimension
) {
if (ordering == mfem::Ordering::byNODES) {
return scalarDof + component * scalarDofCount;
}
if (ordering == mfem::Ordering::byVDIM) {
return scalarDof * dimension + component;
}
MFEM_ABORT(
"The prepared pressure-force displacement space uses an "
"unsupported ordering."
);
return -1;
}
[[nodiscard]]
int get_pressure_extra_order(const mean_field::eos::Polytrope &equationOfState) {
const double extraOrder =
equationOfState.polytropic_index() * static_cast<double>(mean_field::field::Enthalpy::Scalar::familyOrder);
MFEM_VERIFY(
std::isfinite(extraOrder) && extraOrder >= 0.0 &&
extraOrder <= static_cast<double>(std::numeric_limits<int>::max()),
"The prepared pressure-force EOS effective polynomial order "
"is invalid."
);
return static_cast<int>(std::ceil(extraOrder));
}
[[nodiscard]]
const mfem::IntegrationRule &get_pressure_force_rule(
const mean_field::fem::FEM &f,
const mean_field::eos::Polytrope &equationOfState,
const mfem::FiniteElement &enthalpyElement,
const mfem::FiniteElement &displacementElement,
const mfem::ElementTransformation &transformation
) {
using EnthalpyField = mean_field::field::Field<mean_field::field::Enthalpy>;
MFEM_VERIFY(
enthalpyElement.GetOrder() == mean_field::field::Enthalpy::Scalar::familyOrder,
"The prepared pressure-force enthalpy element does not "
"match the registered enthalpy field."
);
MFEM_VERIFY(
displacementElement.GetOrder() == mean_field::field::Displacement::Vector::familyOrder,
"The prepared pressure-force test element does not match "
"the registered displacement field."
);
/*
* Query.domain remains legacy quadrature metadata for now.
*
* Physical element selection is no longer based on utils::DOMAINS;
* it is performed from Enthalpy::Support + DomainSchema in
* PrepareStaticPlan().
*/
const mean_field::quadrature::Query query =
EnthalpyField::make_query<mean_field::field::Enthalpy::Form::PressureForce>(
mean_field::quadrature::QuadratureRole::discretization, transformation.OrderW(),
std::array<int, 1>{get_pressure_extra_order(equationOfState)}, mean_field::utils::DOMAINS::STELLAR,
mean_field::quadrature::MappingKind::general
);
const mean_field::quadrature::MfemRule rule = f.quadratureFactory->get(query, transformation.GetGeometryType());
MFEM_VERIFY(
rule.integration_rule != nullptr, "The quadrature policy did not return a prepared "
"pressure-force integration rule."
);
return *rule.integration_rule;
}
} // namespace
namespace mean_field::operators {
struct PreparedPressureForceOperator::ConstructionData final {
field::FieldDofMap enthalpyMap;
field::FieldDofMap displacementMap;
explicit ConstructionData(const fem::FEM &f)
: enthalpyMap(
field::make_field_dof_map<
field::Enthalpy,
DomainSchema>(*f.enthalpyFes)
),
displacementMap(
field::make_field_dof_map<
field::Displacement,
DomainSchema>(*f.displacementFes)
) {
}
};
PreparedPressureForceOperator::ConstructionData
PreparedPressureForceOperator::MakeConstructionData(const fem::FEM &f) {
verify_required_spaces(f);
return ConstructionData(f);
}
PreparedPressureForceOperator::PreparedPressureForceOperator(
const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper,
const eos::Polytrope &equationOfState
)
: PreparedPressureForceOperator(
f,
domainMapper,
equationOfState,
MakeConstructionData(f)
) {
}
PreparedPressureForceOperator::PreparedPressureForceOperator(
const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper,
const eos::Polytrope &equationOfState,
ConstructionData constructionData
)
: m_fem(f),
m_domainMapper(domainMapper),
m_equationOfState(equationOfState),
m_enthalpyMap(std::move(constructionData.enthalpyMap)),
m_displacementMap(std::move(constructionData.displacementMap)),
m_context(
f,
domainMapper,
m_enthalpyMap,
m_displacementMap
) {
MFEM_VERIFY(
m_domainMapper.GetDimension() == m_fem.mesh->Dimension(),
"The prepared pressure-force mapper dimension does not "
"match the mesh dimension."
);
MFEM_VERIFY(
m_fem.displacementFes->GetVDim() == m_fem.mesh->Dimension(),
"The prepared pressure-force displacement dimension does "
"not match the mesh dimension."
);
MFEM_VERIFY(
m_fem.displacementFes->GetOrdering() == mfem::Ordering::byNODES,
"PreparedPressureForceOperator requires the registered "
"byNODES displacement ordering."
);
MFEM_VERIFY(
m_enthalpyMap.full_size() == m_fem.enthalpyFes->GetTrueVSize(),
"The pressure-force enthalpy FieldDofMap does not match "
"the enthalpy finite-element space."
);
MFEM_VERIFY(
m_displacementMap.full_size() == m_fem.displacementFes->GetTrueVSize(),
"The pressure-force displacement FieldDofMap does not "
"match the displacement finite-element space."
);
m_baseEnthalpyTrue.SetSize(m_enthalpyMap.full_size());
m_baseDisplacementTrue.SetSize(m_displacementMap.full_size());
m_enthalpyVariationTrue.SetSize(m_enthalpyMap.full_size());
m_displacementVariationTrue.SetSize(m_displacementMap.full_size());
m_fullDisplacementAction.SetSize(m_displacementMap.full_size());
m_baseEnthalpyTrue = 0.0;
m_baseDisplacementTrue = 0.0;
m_enthalpyVariationTrue = 0.0;
m_displacementVariationTrue = 0.0;
m_fullDisplacementAction = 0.0;
}
PreparedPressureForceReport PreparedPressureForceOperator::Prepare(
const context::pressure_force::PressureForceStateView &state,
const context::pressure_force::PressureForceDependencies &dependencies
) {
PreparedPressureForceReport report;
report.contextReport = m_context.Prepare(state, dependencies);
if (!report.contextReport.DidAnyWork() && m_isPrepared) {
return report;
}
/*
* Canonical FieldDof -> MFEM expansion.
*
* Unsupported enthalpy true DOFs are set exactly to zero.
* Displacement currently has an identity map but is intentionally
* routed through the same abstraction.
*/
m_enthalpyMap.scatter(m_context.GetBaseEnthalpy(), m_baseEnthalpyTrue);
m_displacementMap.scatter(m_context.GetDisplacement(), m_baseDisplacementTrue);
m_isPrepared = false;
if (report.contextReport.preparedStaticDependencies) {
PrepareStaticPlan();
}
if (report.contextReport.preparedGeometryState) {
PrepareGeometry();
}
if (report.contextReport.preparedMaterialState) {
PrepareMaterialState();
FinalizeDisplacementJacobianPreparation();
AssembleCachedResidual();
++m_residualPreparationCount;
report.preparedEnthalpyJacobianData = true;
report.preparedDisplacementJacobianData = true;
report.preparedResidual = true;
}
MFEM_VERIFY(
!m_elements.empty(), "PreparedPressureForceOperator found no elements in the "
"pressure-force field support."
);
MFEM_VERIFY(
m_cachedResidual.Size() == m_displacementMap.reduced_size(),
"The prepared pressure-force residual has the wrong "
"supported displacement size."
);
m_isPrepared = true;
return report;
}
void PreparedPressureForceOperator::PrepareStaticPlan() {
m_elements.clear();
m_elements.reserve(m_fem.mesh->GetNE());
mfem::Vector enthalpyShape;
mfem::DenseMatrix displacementDShape;
for (int elementId = 0; elementId < m_fem.mesh->GetNE(); ++elementId) {
mfem::ElementTransformation *transformation = m_fem.mesh->GetElementTransformation(elementId);
MFEM_VERIFY(
transformation != nullptr, "Prepared pressure-force static planning received a null "
"element transformation."
);
if (!element_is_in_pressure_support(transformation->Attribute)) {
continue;
}
const mfem::FiniteElement &enthalpyElement = *m_fem.enthalpyFes->GetFE(elementId);
const mfem::FiniteElement &displacementElement = *m_fem.displacementFes->GetFE(elementId);
const mfem::FiniteElement &compactificationElement = *m_fem.compactificationFes->GetFE(elementId);
MFEM_VERIFY(
enthalpyElement.GetGeomType() == displacementElement.GetGeomType() &&
enthalpyElement.GetGeomType() == compactificationElement.GetGeomType() &&
enthalpyElement.GetGeomType() == transformation->GetGeometryType(),
"Prepared pressure-force element geometries do not agree."
);
m_elements.emplace_back();
ElementPAData &data = m_elements.back();
data.elementId = elementId;
data.enthalpyDofTransformation = m_fem.enthalpyFes->GetElementDofs(elementId, data.enthalpyDofs);
data.displacementDofTransformation =
m_fem.displacementFes->GetElementVDofs(elementId, data.displacementDofs);
data.compactificationDofTransformation =
m_fem.compactificationFes->GetElementDofs(elementId, data.compactificationDofs);
data.integrationRule = &get_pressure_force_rule(
m_fem, m_equationOfState, enthalpyElement, displacementElement, *transformation
);
const int dimension = m_fem.mesh->Dimension();
const int quadraturePointCount = data.integrationRule->GetNPoints();
const int enthalpyDofCount = enthalpyElement.GetDof();
const int scalarDisplacementDofCount = displacementElement.GetDof();
MFEM_VERIFY(quadraturePointCount > 0, "The prepared pressure-force integration rule is empty.");
MFEM_VERIFY(
data.enthalpyDofs.Size() == enthalpyDofCount, "The prepared pressure-force enthalpy element has an "
"unexpected DOF count."
);
MFEM_VERIFY(
data.displacementDofs.Size() == scalarDisplacementDofCount * dimension,
"The prepared pressure-force displacement element has "
"an unexpected vector DOF count."
);
data.enthalpyBasis.SetSize(quadraturePointCount, enthalpyDofCount);
data.referenceTestGradients.resize(quadraturePointCount);
data.physicalTestGradients.resize(quadraturePointCount);
enthalpyShape.SetSize(enthalpyDofCount);
displacementDShape.SetSize(scalarDisplacementDofCount, dimension);
for (int quadraturePoint = 0; quadraturePoint < quadraturePointCount; ++quadraturePoint) {
const mfem::IntegrationPoint &integrationPoint = data.integrationRule->IntPoint(quadraturePoint);
enthalpyElement.CalcShape(integrationPoint, enthalpyShape);
displacementElement.CalcDShape(integrationPoint, displacementDShape);
for (int enthalpyDof = 0; enthalpyDof < enthalpyDofCount; ++enthalpyDof) {
data.enthalpyBasis(quadraturePoint, enthalpyDof) = enthalpyShape(enthalpyDof);
}
data.referenceTestGradients[quadraturePoint] = displacementDShape;
}
}
}
void PreparedPressureForceOperator::PrepareGeometry() {
mfem::Vector displacementLocal;
true_to_local(*m_fem.displacementFes, m_baseDisplacementTrue, displacementLocal);
mapping::DomainMapperStateless::Workspace workspace(m_fem.mesh->Dimension());
mfem::Vector elementDisplacement;
mfem::Vector elementCompactification;
for (ElementPAData &data : m_elements) {
MFEM_VERIFY(data.integrationRule != nullptr, "Prepared pressure-force geometry has no integration rule.");
mfem::ElementTransformation *transformation = m_fem.mesh->GetElementTransformation(data.elementId);
MFEM_VERIFY(
transformation != nullptr, "Prepared pressure-force geometry received a null "
"element transformation."
);
displacementLocal.GetSubVector(data.displacementDofs, elementDisplacement);
m_fem.compactificationCoordinate->GetSubVector(data.compactificationDofs, elementCompactification);
if (data.displacementDofTransformation != nullptr) {
data.displacementDofTransformation->InvTransformPrimal(elementDisplacement);
}
if (data.compactificationDofTransformation != nullptr) {
data.compactificationDofTransformation->InvTransformPrimal(elementCompactification);
}
const mfem::FiniteElement &displacementElement = *m_fem.displacementFes->GetFE(data.elementId);
const mfem::FiniteElement &compactificationElement = *m_fem.compactificationFes->GetFE(data.elementId);
data.baseDisplacementData.emplace(
mapping::ElementDisplacementDataFromElementVDofs(displacementElement, elementDisplacement)
);
data.compactificationData.emplace(compactificationElement, elementCompactification);
const mapping::ElementMappingData mappingData{
.displacement = *data.baseDisplacementData, .compactification = *data.compactificationData
};
const int quadraturePointCount = data.integrationRule->GetNPoints();
MFEM_VERIFY(
static_cast<int>(data.referenceTestGradients.size()) == quadraturePointCount,
"Prepared pressure-force geometry has inconsistent "
"static gradient data."
);
data.quadratureWeights.SetSize(quadraturePointCount);
data.baseMappingContexts.resize(quadraturePointCount);
data.physicalTestGradients.resize(quadraturePointCount);
for (int quadraturePoint = 0; quadraturePoint < quadraturePointCount; ++quadraturePoint) {
const mfem::IntegrationPoint &integrationPoint = data.integrationRule->IntPoint(quadraturePoint);
transformation->SetIntPoint(&integrationPoint);
mapping::VolumeMappingContext &mappingContext = data.baseMappingContexts[quadraturePoint];
const mapping::MappingStatus mappingStatus = m_domainMapper.EvaluateVolume(
mappingData, *transformation, integrationPoint, workspace, mappingContext
);
MFEM_VERIFY(
mappingStatus == mapping::MappingStatus::valid,
"Stateless mapping failed while preparing "
"pressure-force geometry. Element: "
<< data.elementId << ", attribute: " << transformation->Attribute
<< ", quadrature point: " << quadraturePoint << ", status: " << static_cast<int>(mappingStatus)
);
const double quadratureWeight = mappingContext.quadrature.weight;
MFEM_VERIFY(
std::isfinite(quadratureWeight) && quadratureWeight > 0.0,
"Prepared pressure-force geometry encountered an "
"invalid quadrature weight."
);
data.quadratureWeights(quadraturePoint) = quadratureWeight;
const mfem::DenseMatrix &referenceTestGradient = data.referenceTestGradients[quadraturePoint];
mfem::DenseMatrix &physicalTestGradient = data.physicalTestGradients[quadraturePoint];
MFEM_VERIFY(
referenceTestGradient.Width() == mappingContext.quadrature.J_inv.Height() &&
mappingContext.quadrature.J_inv.Width() == m_fem.mesh->Dimension(),
"Prepared pressure-force geometry encountered "
"incompatible test-gradient and inverse-Jacobian "
"dimensions."
);
physicalTestGradient.SetSize(referenceTestGradient.Height(), mappingContext.quadrature.J_inv.Width());
mfem::Mult(referenceTestGradient, mappingContext.quadrature.J_inv, physicalTestGradient);
}
}
}
void PreparedPressureForceOperator::PrepareMaterialState() {
mfem::Vector enthalpyLocal;
true_to_local(*m_fem.enthalpyFes, m_baseEnthalpyTrue, enthalpyLocal);
mfem::Vector elementEnthalpy;
mfem::Vector quadratureEnthalpy;
const int dimension = m_fem.mesh->Dimension();
const mfem::Ordering::Type displacementOrdering = m_fem.displacementFes->GetOrdering();
for (ElementPAData &data : m_elements) {
enthalpyLocal.GetSubVector(data.enthalpyDofs, elementEnthalpy);
if (data.enthalpyDofTransformation != nullptr) {
data.enthalpyDofTransformation->InvTransformPrimal(elementEnthalpy);
}
const int quadraturePointCount = data.enthalpyBasis.Height();
const int enthalpyDofCount = data.enthalpyBasis.Width();
const mfem::FiniteElement &displacementElement = *m_fem.displacementFes->GetFE(data.elementId);
const int scalarDisplacementDofCount = displacementElement.GetDof();
const int displacementDofCount = data.displacementDofs.Size();
MFEM_VERIFY(
data.quadratureWeights.Size() == quadraturePointCount &&
static_cast<int>(data.physicalTestGradients.size()) == quadraturePointCount &&
displacementDofCount == scalarDisplacementDofCount * dimension,
"Prepared pressure-force material state has stale "
"geometry data."
);
quadratureEnthalpy.SetSize(quadraturePointCount);
data.enthalpyBasis.Mult(elementEnthalpy, quadratureEnthalpy);
data.pressure.SetSize(quadraturePointCount);
data.pressureDerivative.SetSize(quadraturePointCount);
data.elementResidual.SetSize(displacementDofCount);
data.elementResidual = 0.0;
data.enthalpyJacobian.SetSize(displacementDofCount, enthalpyDofCount);
data.enthalpyJacobian = 0.0;
for (int quadraturePoint = 0; quadraturePoint < quadraturePointCount; ++quadraturePoint) {
const double enthalpy = quadratureEnthalpy(quadraturePoint);
const double pressure = m_equationOfState.pressure_from_enthalpy(enthalpy);
const double pressureDerivative = m_equationOfState.pressure_derivative_from_enthalpy(enthalpy);
const double quadratureWeight = data.quadratureWeights(quadraturePoint);
MFEM_VERIFY(
std::isfinite(pressure) && std::isfinite(pressureDerivative),
"Prepared pressure-force material state encountered "
"a non-finite EOS value."
);
data.pressure(quadraturePoint) = pressure;
data.pressureDerivative(quadraturePoint) = pressureDerivative;
const mfem::DenseMatrix &physicalTestGradient = data.physicalTestGradients[quadraturePoint];
MFEM_VERIFY(
physicalTestGradient.Height() == scalarDisplacementDofCount &&
physicalTestGradient.Width() == dimension,
"Prepared pressure-force material state has an "
"invalid physical test-gradient matrix."
);
for (int scalarDof = 0; scalarDof < scalarDisplacementDofCount; ++scalarDof) {
for (int component = 0; component < dimension; ++component) {
const int vectorDof = vector_dof_index(
displacementOrdering, scalarDof, component, scalarDisplacementDofCount, dimension
);
const double weightedTestGradient =
quadratureWeight * physicalTestGradient(scalarDof, component);
data.elementResidual(vectorDof) -= pressure * weightedTestGradient;
for (int enthalpyDof = 0; enthalpyDof < enthalpyDofCount; ++enthalpyDof) {
data.enthalpyJacobian(vectorDof, enthalpyDof) -=
pressureDerivative * weightedTestGradient *
data.enthalpyBasis(quadraturePoint, enthalpyDof);
}
}
}
}
}
++m_enthalpyJacobianStatistics.preparations;
}
void PreparedPressureForceOperator::FinalizeDisplacementJacobianPreparation() {
const int dimension = m_fem.mesh->Dimension();
for (const ElementPAData &data : m_elements) {
const int quadraturePointCount = data.integrationRule->GetNPoints();
MFEM_VERIFY(
data.baseDisplacementData.has_value() && data.compactificationData.has_value() &&
static_cast<int>(data.baseMappingContexts.size()) == quadraturePointCount &&
static_cast<int>(data.referenceTestGradients.size()) == quadraturePointCount &&
static_cast<int>(data.physicalTestGradients.size()) == quadraturePointCount &&
data.quadratureWeights.Size() == quadraturePointCount &&
data.pressure.Size() == quadraturePointCount,
"Prepared pressure-force displacement Jacobian has "
"inconsistent frozen data."
);
for (int quadraturePoint = 0; quadraturePoint < quadraturePointCount; ++quadraturePoint) {
MFEM_VERIFY(
data.referenceTestGradients[quadraturePoint].Width() == dimension &&
data.physicalTestGradients[quadraturePoint].Width() == dimension,
"Prepared pressure-force displacement Jacobian has "
"a gradient with the wrong dimension."
);
}
}
++m_displacementJacobianStatistics.preparations;
}
void PreparedPressureForceOperator::AssembleCachedResidual() {
mfem::Vector localResidual(m_fem.displacementFes->GetVSize());
localResidual = 0.0;
mfem::Vector elementResidual;
for (const ElementPAData &data : m_elements) {
elementResidual = data.elementResidual;
if (data.displacementDofTransformation != nullptr) {
data.displacementDofTransformation->TransformDual(elementResidual);
}
localResidual.AddElementVector(data.displacementDofs, elementResidual);
}
local_to_true(*m_fem.displacementFes, localResidual, m_fullDisplacementAction);
m_cachedResidual.SetSize(m_displacementMap.reduced_size());
/*
* FieldDofMap::gather does not resize its destination.
*/
m_displacementMap.gather(m_fullDisplacementAction, m_cachedResidual);
}
void PreparedPressureForceOperator::BuildResidual(mfem::Vector &residual) const {
VerifyPrepared();
residual = m_cachedResidual;
++m_residualApplicationCount;
}
void PreparedPressureForceOperator::ApplyEnthalpyJacobianAction(
const mfem::Vector &enthalpyVariation,
mfem::Vector &action
) const {
VerifyPrepared();
MFEM_VERIFY(
enthalpyVariation.Size() == m_enthalpyMap.reduced_size(),
"Prepared pressure-force enthalpy variation has the wrong "
"supported size."
);
m_enthalpyMap.scatter(enthalpyVariation, m_enthalpyVariationTrue);
mfem::Vector enthalpyVariationLocal;
true_to_local(*m_fem.enthalpyFes, m_enthalpyVariationTrue, enthalpyVariationLocal);
mfem::Vector localAction(m_fem.displacementFes->GetVSize());
localAction = 0.0;
mfem::Vector elementVariation;
mfem::Vector elementAction;
for (const ElementPAData &data : m_elements) {
enthalpyVariationLocal.GetSubVector(data.enthalpyDofs, elementVariation);
if (data.enthalpyDofTransformation != nullptr) {
data.enthalpyDofTransformation->InvTransformPrimal(elementVariation);
}
elementAction.SetSize(data.enthalpyJacobian.Height());
data.enthalpyJacobian.Mult(elementVariation, elementAction);
if (data.displacementDofTransformation != nullptr) {
data.displacementDofTransformation->TransformDual(elementAction);
}
localAction.AddElementVector(data.displacementDofs, elementAction);
}
local_to_true(*m_fem.displacementFes, localAction, m_fullDisplacementAction);
action.SetSize(m_displacementMap.reduced_size());
m_displacementMap.gather(m_fullDisplacementAction, action);
++m_enthalpyJacobianStatistics.applications;
}
void PreparedPressureForceOperator::ApplyDisplacementJacobianAction(
const mfem::Vector &displacementVariation,
mfem::Vector &action
) const {
VerifyPrepared();
MFEM_VERIFY(
displacementVariation.Size() == m_displacementMap.reduced_size(),
"Prepared pressure-force displacement variation has the "
"wrong supported size."
);
m_displacementMap.scatter(displacementVariation, m_displacementVariationTrue);
mfem::Vector displacementVariationLocal;
true_to_local(*m_fem.displacementFes, m_displacementVariationTrue, displacementVariationLocal);
mfem::Vector localAction(m_fem.displacementFes->GetVSize());
localAction = 0.0;
mapping::DomainMapperStateless::Workspace workspace(m_fem.mesh->Dimension());
mfem::Vector elementDisplacementVariation;
mfem::Vector elementAction;
mfem::DenseMatrix physicalTestGradientVariation;
const int dimension = m_fem.mesh->Dimension();
const mfem::Ordering::Type displacementOrdering = m_fem.displacementFes->GetOrdering();
for (const ElementPAData &data : m_elements) {
MFEM_VERIFY(
data.baseDisplacementData.has_value() && data.compactificationData.has_value() &&
data.integrationRule != nullptr,
"Prepared pressure-force displacement Jacobian has "
"invalid frozen element data."
);
mfem::ElementTransformation *transformation = m_fem.mesh->GetElementTransformation(data.elementId);
MFEM_VERIFY(
transformation != nullptr, "Prepared pressure-force displacement Jacobian received "
"a null element transformation."
);
displacementVariationLocal.GetSubVector(data.displacementDofs, elementDisplacementVariation);
if (data.displacementDofTransformation != nullptr) {
data.displacementDofTransformation->InvTransformPrimal(elementDisplacementVariation);
}
const mfem::FiniteElement &displacementElement = *m_fem.displacementFes->GetFE(data.elementId);
const mapping::ElementDisplacementData directionData =
mapping::ElementDisplacementDataFromElementVDofs(displacementElement, elementDisplacementVariation);
const mapping::ElementMappingData mappingData{
.displacement = *data.baseDisplacementData, .compactification = *data.compactificationData
};
const int quadraturePointCount = data.integrationRule->GetNPoints();
const int scalarDisplacementDofCount = displacementElement.GetDof();
MFEM_VERIFY(
static_cast<int>(data.baseMappingContexts.size()) == quadraturePointCount &&
data.pressure.Size() == quadraturePointCount,
"Prepared pressure-force displacement Jacobian has "
"stale quadrature data."
);
elementAction.SetSize(data.displacementDofs.Size());
elementAction = 0.0;
physicalTestGradientVariation.SetSize(scalarDisplacementDofCount, dimension);
for (int quadraturePoint = 0; quadraturePoint < quadraturePointCount; ++quadraturePoint) {
const mfem::IntegrationPoint &integrationPoint = data.integrationRule->IntPoint(quadraturePoint);
transformation->SetIntPoint(&integrationPoint);
mapping::VolumeMappingVariation variation;
const mapping::MappingStatus mappingStatus = m_domainMapper.EvaluateVolumeVariation(
mappingData, directionData, *transformation, integrationPoint,
data.baseMappingContexts[quadraturePoint], workspace, variation
);
MFEM_VERIFY(
mappingStatus == mapping::MappingStatus::valid,
"Stateless mapping variation failed while applying "
"the prepared pressure-force displacement Jacobian. "
"Element: "
<< data.elementId << ", attribute: " << transformation->Attribute
<< ", quadrature point: " << quadraturePoint << ", status: " << static_cast<int>(mappingStatus)
);
mfem::Mult(
data.referenceTestGradients[quadraturePoint], variation.inverse_element_jacobian_variation,
physicalTestGradientVariation
);
const mfem::DenseMatrix &physicalTestGradient = data.physicalTestGradients[quadraturePoint];
for (int scalarDof = 0; scalarDof < scalarDisplacementDofCount; ++scalarDof) {
for (int component = 0; component < dimension; ++component) {
const int vectorDof = vector_dof_index(
displacementOrdering, scalarDof, component, scalarDisplacementDofCount, dimension
);
const double gradientWeightVariation =
data.quadratureWeights(quadraturePoint) *
physicalTestGradientVariation(scalarDof, component) +
variation.weight_variation * physicalTestGradient(scalarDof, component);
const double contribution = data.pressure(quadraturePoint) * gradientWeightVariation;
MFEM_VERIFY(
std::isfinite(gradientWeightVariation) && std::isfinite(contribution),
"Prepared pressure-force displacement "
"Jacobian encountered a non-finite "
"contribution."
);
elementAction(vectorDof) -= contribution;
}
}
}
if (data.displacementDofTransformation != nullptr) {
data.displacementDofTransformation->TransformDual(elementAction);
}
localAction.AddElementVector(data.displacementDofs, elementAction);
}
local_to_true(*m_fem.displacementFes, localAction, m_fullDisplacementAction);
action.SetSize(m_displacementMap.reduced_size());
m_displacementMap.gather(m_fullDisplacementAction, action);
++m_displacementJacobianStatistics.applications;
}
void PreparedPressureForceOperator::ApplyCompleteJacobianAction(
const mfem::Vector &enthalpyVariation,
const mfem::Vector &displacementVariation,
mfem::Vector &action
) const {
VerifyPrepared();
mfem::Vector displacementAction;
ApplyEnthalpyJacobianAction(enthalpyVariation, action);
ApplyDisplacementJacobianAction(displacementVariation, displacementAction);
MFEM_VERIFY(
action.Size() == displacementAction.Size(), "Prepared pressure-force complete Jacobian produced "
"incompatible column actions."
);
action += displacementAction;
++m_completeJacobianStatistics.applications;
}
bool PreparedPressureForceOperator::IsPrepared() const noexcept {
return m_isPrepared && m_context.IsPrepared();
}
int PreparedPressureForceOperator::GetEnthalpySize() const noexcept {
return m_enthalpyMap.reduced_size();
}
int PreparedPressureForceOperator::GetDisplacementSize() const noexcept {
return m_displacementMap.reduced_size();
}
const context::pressure_force::PressureForceLinearizationContext &
PreparedPressureForceOperator::GetContext() const noexcept {
return m_context;
}
const context::pressure_force::PressureForcePreparationStatistics &
PreparedPressureForceOperator::GetContextPreparationStatistics() const noexcept {
return m_context.GetPreparationStatistics();
}
std::uint64_t PreparedPressureForceOperator::GetResidualPreparationCount() const noexcept {
return m_residualPreparationCount;
}
std::uint64_t PreparedPressureForceOperator::GetResidualApplicationCount() const noexcept {
return m_residualApplicationCount;
}
const PreparedPressureForceEnthalpyJacobianStatistics &
PreparedPressureForceOperator::GetEnthalpyJacobianStatistics() const noexcept {
return m_enthalpyJacobianStatistics;
}
const PreparedPressureForceDisplacementJacobianStatistics &
PreparedPressureForceOperator::GetDisplacementJacobianStatistics() const noexcept {
return m_displacementJacobianStatistics;
}
const PreparedPressureForceCompleteJacobianStatistics &
PreparedPressureForceOperator::GetCompleteJacobianStatistics() const noexcept {
return m_completeJacobianStatistics;
}
std::size_t PreparedPressureForceOperator::GetStellarElementCount() const noexcept {
return m_elements.size();
}
const fem::FEM &PreparedPressureForceOperator::GetFEM() const noexcept {
return m_fem;
}
void PreparedPressureForceOperator::VerifyPrepared() const {
MFEM_VERIFY(
IsPrepared(), "PreparedPressureForceOperator must be prepared before "
"residual or Jacobian application."
);
}
PreparedPressureForceJacobianOperator::PreparedPressureForceJacobianOperator(
const BarotropicEquilibriumLayout &layout,
const PreparedPressureForceOperator &preparedOperator
)
: mfem::Operator(
layout.residual_offsets().Last(),
layout.value_offsets().Last()
),
m_layout(layout),
m_preparedOperator(preparedOperator) {
using Form = utils::blocks::barotropic_equilibrium_form;
constexpr auto displacementValue =
utils::blocks::get_value_block<Form>(utils::blocks::displacement_field.geometry_term);
constexpr auto enthalpyValue =
utils::blocks::get_value_block<Form>(utils::blocks::enthalpy_field.specific_term);
constexpr auto displacementResidual =
utils::blocks::get_residual_block<Form>(utils::blocks::displacement_field.geometry_term);
/*
* This adapter consumes only d and h and contributes only R_d.
*
* Do not impose GetTrueVSize() assumptions on unrelated root
* blocks. In particular rho and h may now be reduced FieldDof
* coordinates.
*/
MFEM_VERIFY(
m_layout.size(displacementValue) == m_preparedOperator.GetDisplacementSize(),
"Prepared pressure-force MFEM adapter received an "
"incompatible displacement value block."
);
MFEM_VERIFY(
m_layout.size(enthalpyValue) == m_preparedOperator.GetEnthalpySize(),
"Prepared pressure-force MFEM adapter received an "
"incompatible enthalpy value block."
);
MFEM_VERIFY(
m_layout.size(displacementResidual) == m_preparedOperator.GetDisplacementSize(),
"Prepared pressure-force MFEM adapter received an "
"incompatible displacement residual block."
);
MFEM_VERIFY(
Height() == m_layout.residual_offsets().Last() && Width() == m_layout.value_offsets().Last(),
"Prepared pressure-force MFEM adapter has inconsistent "
"operator dimensions."
);
}
void PreparedPressureForceJacobianOperator::Mult(
const mfem::Vector &direction,
mfem::Vector &action
) const {
MFEM_VERIFY(
m_preparedOperator.IsPrepared(), "Prepared pressure-force MFEM adapter requires a prepared "
"pressure-force operator."
);
MFEM_VERIFY(
direction.Size() == Width(), "Prepared pressure-force MFEM adapter received a direction "
"with the wrong size."
);
using Form = utils::blocks::barotropic_equilibrium_form;
constexpr auto displacementValue =
utils::blocks::get_value_block<Form>(utils::blocks::displacement_field.geometry_term);
constexpr auto enthalpyValue =
utils::blocks::get_value_block<Form>(utils::blocks::enthalpy_field.specific_term);
constexpr auto displacementResidual =
utils::blocks::get_residual_block<Form>(utils::blocks::displacement_field.geometry_term);
/*
* MFEM does not provide a const non-owning Vector view.
* These alias the packed direction but are passed only through
* const references.
*/
const mfem::Vector displacementVariation(
const_cast<mfem::real_t *>(direction.GetData()) + m_layout.offset(displacementValue),
m_layout.size(displacementValue)
);
const mfem::Vector enthalpyVariation(
const_cast<mfem::real_t *>(direction.GetData()) + m_layout.offset(enthalpyValue),
m_layout.size(enthalpyValue)
);
mfem::Vector displacementAction;
m_preparedOperator.ApplyCompleteJacobianAction(enthalpyVariation, displacementVariation, displacementAction);
MFEM_VERIFY(
displacementAction.Size() == m_layout.size(displacementResidual),
"Prepared pressure-force MFEM adapter produced a "
"displacement action with the wrong size."
);
action.SetSize(Height());
action = 0.0;
const int residualOffset = m_layout.offset(displacementResidual);
for (int entry = 0; entry < displacementAction.Size(); ++entry) {
action(residualOffset + entry) = displacementAction(entry);
}
}
const BarotropicEquilibriumLayout &PreparedPressureForceJacobianOperator::GetLayout() const noexcept {
return m_layout;
}
} // namespace mean_field::operators