feat(libmeanfield): centrifugal + pressure

This commit is contained in:
2026-08-04 14:24:55 -04:00
parent 9bc4f2758a
commit dc912fd15e
115 changed files with 260058 additions and 163261 deletions

View File

@@ -0,0 +1,811 @@
module;
#include <array>
#include <cmath>
#include <limits>
#include <mfem.hpp>
module mean_field;
import :operators.kernels.barotropic_closure;
namespace {
enum class ClosureAction { residual, density, enthalpy };
void true_to_local(
const mfem::ParFiniteElementSpace &finiteElementSpace,
const mfem::Vector &trueVector,
mfem::Vector &localVector
) {
MFEM_VERIFY(
trueVector.Size() == finiteElementSpace.GetTrueVSize(),
"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(),
"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;
}
}
int get_eos_extra_order(
const mean_field::physics::PolytropicBarotrope &barotrope
) {
const double extraOrder =
(barotrope.polytropic_index() - 1.0) *
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 EOS effective polynomial order is invalid."
);
return static_cast<int>(std::ceil(extraOrder));
}
const mfem::IntegrationRule &get_eos_rule(
const mean_field::fem::FEM &f,
const mean_field::physics::PolytropicBarotrope &barotrope,
const mfem::FiniteElement &densityElement,
const mfem::FiniteElement &enthalpyElement,
const mfem::ElementTransformation &transformation
) {
using EnthalpyField =
mean_field::field::Field<mean_field::field::Enthalpy>;
MFEM_VERIFY(
densityElement.GetOrder() ==
mean_field::field::Density::Scalar::familyOrder,
"The EOS test element does not match the "
"registered density field."
);
MFEM_VERIFY(
enthalpyElement.GetOrder() ==
mean_field::field::Enthalpy::Scalar::familyOrder,
"The EOS trial element does not match the "
"registered enthalpy field."
);
const mean_field::quadrature::Query query = EnthalpyField::make_query<
mean_field::field::Enthalpy::Form::EosClosureSource>(
mean_field::quadrature::QuadratureRole::discretization,
transformation.OrderW(),
std::array<int, 1>{get_eos_extra_order(barotrope)},
mean_field::utils::DOMAINS::STELLAR,
mean_field::quadrature::MappingKind::general
);
const auto resolution =
f.quadratureFactory->get(query, transformation.GetGeometryType());
MFEM_VERIFY(
resolution.integration_rule != nullptr,
"The quadrature policy did not return an "
"EOS-closure integration rule."
);
return *resolution.integration_rule;
}
void validate_common_inputs(
const mean_field::fem::FEM &f,
const mean_field::mapping::DomainMapperStateless &domainMapper,
const mfem::Vector &displacementTrue
) {
MFEM_VERIFY(
f.mesh != nullptr, "The EOS closure kernel requires a mesh."
);
MFEM_VERIFY(
f.densityFes != nullptr,
"The EOS closure kernel requires the density "
"finite-element space."
);
MFEM_VERIFY(
f.enthalpyFes != nullptr,
"The EOS closure kernel requires the enthalpy "
"finite-element space."
);
MFEM_VERIFY(
f.displacementFes != nullptr,
"The EOS closure kernel requires the displacement "
"finite-element space."
);
MFEM_VERIFY(
f.compactificationFes != nullptr,
"The EOS closure kernel requires the "
"compactification finite-element space."
);
MFEM_VERIFY(
f.compactificationCoordinate != nullptr,
"The EOS closure kernel requires the "
"compactification coordinate."
);
MFEM_VERIFY(
f.quadratureFactory != nullptr,
"The EOS closure kernel requires the quadrature "
"rule factory."
);
MFEM_VERIFY(
displacementTrue.Size() == f.displacementFes->GetTrueVSize(),
"The displacement vector has the wrong size."
);
MFEM_VERIFY(
domainMapper.GetDimension() == f.mesh->Dimension(),
"The domain-mapper dimension does not match "
"the mesh dimension."
);
}
void apply_closure_action(
const mean_field::fem::FEM &f,
const mean_field::mapping::DomainMapperStateless &domainMapper,
const mean_field::physics::PolytropicBarotrope &barotrope,
const ClosureAction closureAction,
const mfem::Vector *densityInputTrue,
const mfem::Vector *baseEnthalpyTrue,
const mfem::Vector *enthalpyVariationTrue,
const mfem::Vector &displacementTrue,
mfem::Vector &action
) {
validate_common_inputs(f, domainMapper, displacementTrue);
if (closureAction == ClosureAction::residual ||
closureAction == ClosureAction::density) {
MFEM_VERIFY(
densityInputTrue != nullptr &&
densityInputTrue->Size() == f.densityFes->GetTrueVSize(),
"The density input has the wrong size."
);
}
if (closureAction == ClosureAction::residual ||
closureAction == ClosureAction::enthalpy) {
MFEM_VERIFY(
baseEnthalpyTrue != nullptr &&
baseEnthalpyTrue->Size() == f.enthalpyFes->GetTrueVSize(),
"The base enthalpy has the wrong size."
);
}
if (closureAction == ClosureAction::enthalpy) {
MFEM_VERIFY(
enthalpyVariationTrue != nullptr &&
enthalpyVariationTrue->Size() ==
f.enthalpyFes->GetTrueVSize(),
"The enthalpy variation has the wrong size."
);
}
mfem::Vector densityInputLocal;
mfem::Vector baseEnthalpyLocal;
mfem::Vector enthalpyVariationLocal;
mfem::Vector displacementLocal;
if (densityInputTrue != nullptr) {
true_to_local(*f.densityFes, *densityInputTrue, densityInputLocal);
}
if (baseEnthalpyTrue != nullptr) {
true_to_local(*f.enthalpyFes, *baseEnthalpyTrue, baseEnthalpyLocal);
}
if (enthalpyVariationTrue != nullptr) {
true_to_local(
*f.enthalpyFes, *enthalpyVariationTrue, enthalpyVariationLocal
);
}
true_to_local(*f.displacementFes, displacementTrue, displacementLocal);
mfem::Vector localAction(f.densityFes->GetVSize());
localAction = 0.0;
mean_field::mapping::DomainMapperStateless::Workspace workspace(
f.mesh->Dimension()
);
mfem::Array<int> densityDofs;
mfem::Array<int> enthalpyDofs;
mfem::Array<int> displacementDofs;
mfem::Array<int> compactificationDofs;
mfem::Vector elementDensityInput;
mfem::Vector elementBaseEnthalpy;
mfem::Vector elementEnthalpyVariation;
mfem::Vector elementDisplacement;
mfem::Vector elementCompactification;
mfem::Vector elementAction;
mfem::Vector densityShape;
mfem::Vector enthalpyShape;
const int vacuumAttribute = domainMapper.GetVacuumElementAttribute();
for (int elementId = 0; elementId < f.mesh->GetNE(); ++elementId) {
mfem::ElementTransformation *transformation =
f.mesh->GetElementTransformation(elementId);
MFEM_VERIFY(
transformation != nullptr,
"The EOS closure kernel received a null "
"element transformation."
);
if (transformation->Attribute == vacuumAttribute) {
continue;
}
const mfem::FiniteElement &densityElement =
*f.densityFes->GetFE(elementId);
const mfem::FiniteElement &enthalpyElement =
*f.enthalpyFes->GetFE(elementId);
const mfem::FiniteElement &displacementElement =
*f.displacementFes->GetFE(elementId);
const mfem::FiniteElement &compactificationElement =
*f.compactificationFes->GetFE(elementId);
mfem::DofTransformation *densityDofTransformation =
f.densityFes->GetElementDofs(elementId, densityDofs);
mfem::DofTransformation *enthalpyDofTransformation =
f.enthalpyFes->GetElementDofs(elementId, enthalpyDofs);
mfem::DofTransformation *displacementDofTransformation =
f.displacementFes->GetElementVDofs(elementId, displacementDofs);
mfem::DofTransformation *compactificationDofTransformation =
f.compactificationFes->GetElementDofs(
elementId, compactificationDofs
);
if (densityInputTrue != nullptr) {
densityInputLocal.GetSubVector(
densityDofs, elementDensityInput
);
if (densityDofTransformation != nullptr) {
densityDofTransformation->InvTransformPrimal(
elementDensityInput
);
}
}
if (baseEnthalpyTrue != nullptr) {
baseEnthalpyLocal.GetSubVector(
enthalpyDofs, elementBaseEnthalpy
);
if (enthalpyDofTransformation != nullptr) {
enthalpyDofTransformation->InvTransformPrimal(
elementBaseEnthalpy
);
}
}
if (enthalpyVariationTrue != nullptr) {
enthalpyVariationLocal.GetSubVector(
enthalpyDofs, elementEnthalpyVariation
);
if (enthalpyDofTransformation != nullptr) {
enthalpyDofTransformation->InvTransformPrimal(
elementEnthalpyVariation
);
}
}
displacementLocal.GetSubVector(
displacementDofs, elementDisplacement
);
f.compactificationCoordinate->GetSubVector(
compactificationDofs, elementCompactification
);
if (displacementDofTransformation != nullptr) {
displacementDofTransformation->InvTransformPrimal(
elementDisplacement
);
}
if (compactificationDofTransformation != nullptr) {
compactificationDofTransformation->InvTransformPrimal(
elementCompactification
);
}
const mean_field::mapping::ElementDisplacementData
displacementData = mean_field::mapping::
ElementDisplacementDataFromElementVDofs(
displacementElement, elementDisplacement
);
const mean_field::mapping::ElementCompactificationData
compactificationData(
compactificationElement, elementCompactification
);
const mean_field::mapping::ElementMappingData mappingData{
.displacement = displacementData,
.compactification = compactificationData
};
densityShape.SetSize(densityElement.GetDof());
enthalpyShape.SetSize(enthalpyElement.GetDof());
elementAction.SetSize(densityElement.GetDof());
elementAction = 0.0;
const mfem::IntegrationRule &integrationRule = get_eos_rule(
f, barotrope, densityElement, enthalpyElement, *transformation
);
for (int quadratureIndex = 0;
quadratureIndex < integrationRule.GetNPoints();
++quadratureIndex) {
const mfem::IntegrationPoint &integrationPoint =
integrationRule.IntPoint(quadratureIndex);
transformation->SetIntPoint(&integrationPoint);
mean_field::mapping::VolumeMappingContext mappingContext;
const mean_field::mapping::MappingStatus mappingStatus =
domainMapper.EvaluateVolume(
mappingData, *transformation, integrationPoint,
workspace, mappingContext
);
MFEM_VERIFY(
mappingStatus == mean_field::mapping::MappingStatus::valid,
"Stateless mapping failed in the EOS "
"closure kernel. Element: "
<< elementId
<< ", attribute: " << transformation->Attribute
<< ", quadrature point: " << quadratureIndex
<< ", status: " << static_cast<int>(mappingStatus)
);
densityElement.CalcShape(integrationPoint, densityShape);
double integrand = 0.0;
if (closureAction == ClosureAction::density) {
integrand = elementDensityInput * densityShape;
} else {
enthalpyElement.CalcShape(integrationPoint, enthalpyShape);
const double baseEnthalpy =
elementBaseEnthalpy * enthalpyShape;
if (closureAction == ClosureAction::residual) {
const double density =
elementDensityInput * densityShape;
integrand =
density -
barotrope.density_from_enthalpy(baseEnthalpy);
} else {
const double enthalpyVariation =
elementEnthalpyVariation * enthalpyShape;
integrand = -barotrope.density_derivative_from_enthalpy(
baseEnthalpy
) *
enthalpyVariation;
}
}
const double weightedIntegrand =
mappingContext.quadrature.weight * integrand;
for (int densityDof = 0; densityDof < densityElement.GetDof();
++densityDof) {
elementAction(densityDof) +=
weightedIntegrand * densityShape(densityDof);
}
}
if (densityDofTransformation != nullptr) {
densityDofTransformation->TransformDual(elementAction);
}
localAction.AddElementVector(densityDofs, elementAction);
}
local_to_true(*f.densityFes, localAction, action);
}
} // namespace
namespace mean_field::operators::kernels {
void apply_barotropic_closure(
const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper,
const physics::PolytropicBarotrope &barotrope,
const mfem::Vector &densityTrue,
const mfem::Vector &enthalpyTrue,
const mfem::Vector &displacementTrue,
mfem::Vector &residual
) {
apply_closure_action(
f, domainMapper, barotrope, ClosureAction::residual, &densityTrue,
&enthalpyTrue, nullptr, displacementTrue, residual
);
}
void apply_barotropic_closure_density_action(
const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper,
const physics::PolytropicBarotrope &barotrope,
const mfem::Vector &densityVariationTrue,
const mfem::Vector &displacementTrue,
mfem::Vector &action
) {
apply_closure_action(
f, domainMapper, barotrope, ClosureAction::density,
&densityVariationTrue, nullptr, nullptr, displacementTrue, action
);
}
void apply_barotropic_closure_enthalpy_action(
const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper,
const physics::PolytropicBarotrope &barotrope,
const mfem::Vector &baseEnthalpyTrue,
const mfem::Vector &enthalpyVariationTrue,
const mfem::Vector &displacementTrue,
mfem::Vector &action
) {
apply_closure_action(
f, domainMapper, barotrope, ClosureAction::enthalpy, nullptr,
&baseEnthalpyTrue, &enthalpyVariationTrue, displacementTrue, action
);
}
void apply_barotropic_closure_displacement_action(
const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper,
const physics::PolytropicBarotrope &barotrope,
const mfem::Vector &baseDensityTrue,
const mfem::Vector &baseEnthalpyTrue,
const mfem::Vector &displacementTrue,
const mfem::Vector &displacementVariationTrue,
mfem::Vector &action
) {
MFEM_VERIFY(
f.mesh != nullptr, "The barotropic-closure displacement action "
"requires a mesh."
);
MFEM_VERIFY(
f.densityFes != nullptr,
"The barotropic-closure displacement action "
"requires the density finite-element space."
);
MFEM_VERIFY(
f.enthalpyFes != nullptr,
"The barotropic-closure displacement action "
"requires the enthalpy finite-element space."
);
MFEM_VERIFY(
f.displacementFes != nullptr,
"The barotropic-closure displacement action "
"requires the displacement finite-element space."
);
MFEM_VERIFY(
f.compactificationFes != nullptr,
"The barotropic-closure displacement action "
"requires the compactification finite-element space."
);
MFEM_VERIFY(
f.compactificationCoordinate != nullptr,
"The barotropic-closure displacement action "
"requires the compactification coordinate."
);
MFEM_VERIFY(
f.quadratureFactory != nullptr,
"The barotropic-closure displacement action "
"requires the quadrature-rule factory."
);
MFEM_VERIFY(
baseDensityTrue.Size() == f.densityFes->GetTrueVSize(),
"The base-density vector has the wrong size."
);
MFEM_VERIFY(
baseEnthalpyTrue.Size() == f.enthalpyFes->GetTrueVSize(),
"The base-enthalpy vector has the wrong size."
);
MFEM_VERIFY(
displacementTrue.Size() == f.displacementFes->GetTrueVSize(),
"The displacement vector has the wrong size."
);
MFEM_VERIFY(
displacementVariationTrue.Size() ==
f.displacementFes->GetTrueVSize(),
"The displacement-variation vector has the wrong size."
);
MFEM_VERIFY(
domainMapper.GetDimension() == f.mesh->Dimension(),
"The domain-mapper dimension does not match the "
"mesh dimension."
);
mfem::Vector baseDensityLocal;
mfem::Vector baseEnthalpyLocal;
mfem::Vector displacementLocal;
mfem::Vector displacementVariationLocal;
true_to_local(*f.densityFes, baseDensityTrue, baseDensityLocal);
true_to_local(*f.enthalpyFes, baseEnthalpyTrue, baseEnthalpyLocal);
true_to_local(*f.displacementFes, displacementTrue, displacementLocal);
true_to_local(
*f.displacementFes, displacementVariationTrue,
displacementVariationLocal
);
mfem::Vector localAction(f.densityFes->GetVSize());
localAction = 0.0;
mapping::DomainMapperStateless::Workspace workspace(
f.mesh->Dimension()
);
mfem::Array<int> densityDofs;
mfem::Array<int> enthalpyDofs;
mfem::Array<int> displacementDofs;
mfem::Array<int> compactificationDofs;
mfem::Vector elementBaseDensity;
mfem::Vector elementBaseEnthalpy;
mfem::Vector elementDisplacement;
mfem::Vector elementDisplacementVariation;
mfem::Vector elementCompactification;
mfem::Vector densityShape;
mfem::Vector enthalpyShape;
mfem::Vector elementAction;
mapping::VolumeMappingContext mappingContext;
mapping::VolumeMappingVariation mappingVariation;
const int vacuumAttribute = domainMapper.GetVacuumElementAttribute();
for (int elementId = 0; elementId < f.mesh->GetNE(); ++elementId) {
mfem::ElementTransformation *transformation =
f.mesh->GetElementTransformation(elementId);
MFEM_VERIFY(
transformation != nullptr,
"The barotropic-closure displacement action "
"received a null element transformation."
);
if (transformation->Attribute == vacuumAttribute) {
continue;
}
const mfem::FiniteElement &densityElement =
*f.densityFes->GetFE(elementId);
const mfem::FiniteElement &enthalpyElement =
*f.enthalpyFes->GetFE(elementId);
const mfem::FiniteElement &displacementElement =
*f.displacementFes->GetFE(elementId);
const mfem::FiniteElement &compactificationElement =
*f.compactificationFes->GetFE(elementId);
mfem::DofTransformation *densityDofTransformation =
f.densityFes->GetElementDofs(elementId, densityDofs);
mfem::DofTransformation *enthalpyDofTransformation =
f.enthalpyFes->GetElementDofs(elementId, enthalpyDofs);
mfem::DofTransformation *displacementDofTransformation =
f.displacementFes->GetElementVDofs(elementId, displacementDofs);
mfem::DofTransformation *compactificationDofTransformation =
f.compactificationFes->GetElementDofs(
elementId, compactificationDofs
);
baseDensityLocal.GetSubVector(densityDofs, elementBaseDensity);
baseEnthalpyLocal.GetSubVector(enthalpyDofs, elementBaseEnthalpy);
displacementLocal.GetSubVector(
displacementDofs, elementDisplacement
);
displacementVariationLocal.GetSubVector(
displacementDofs, elementDisplacementVariation
);
f.compactificationCoordinate->GetSubVector(
compactificationDofs, elementCompactification
);
if (densityDofTransformation != nullptr) {
densityDofTransformation->InvTransformPrimal(
elementBaseDensity
);
}
if (enthalpyDofTransformation != nullptr) {
enthalpyDofTransformation->InvTransformPrimal(
elementBaseEnthalpy
);
}
if (displacementDofTransformation != nullptr) {
displacementDofTransformation->InvTransformPrimal(
elementDisplacement
);
displacementDofTransformation->InvTransformPrimal(
elementDisplacementVariation
);
}
if (compactificationDofTransformation != nullptr) {
compactificationDofTransformation->InvTransformPrimal(
elementCompactification
);
}
const mapping::ElementDisplacementData displacementData =
mapping::ElementDisplacementDataFromElementVDofs(
displacementElement, elementDisplacement
);
const mapping::ElementDisplacementData displacementVariationData =
mapping::ElementDisplacementDataFromElementVDofs(
displacementElement, elementDisplacementVariation
);
const mapping::ElementCompactificationData compactificationData(
compactificationElement, elementCompactification
);
const mapping::ElementMappingData mappingData{
.displacement = displacementData,
.compactification = compactificationData
};
densityShape.SetSize(densityElement.GetDof());
enthalpyShape.SetSize(enthalpyElement.GetDof());
elementAction.SetSize(densityElement.GetDof());
elementAction = 0.0;
const mfem::IntegrationRule &integrationRule = get_eos_rule(
f, barotrope, densityElement, enthalpyElement, *transformation
);
for (int quadraturePoint = 0;
quadraturePoint < integrationRule.GetNPoints();
++quadraturePoint) {
const mfem::IntegrationPoint &integrationPoint =
integrationRule.IntPoint(quadraturePoint);
transformation->SetIntPoint(&integrationPoint);
const mapping::MappingStatus mappingStatus =
domainMapper.EvaluateVolume(
mappingData, *transformation, integrationPoint,
workspace, mappingContext
);
MFEM_VERIFY(
mappingStatus == mapping::MappingStatus::valid,
"The base mapping is invalid while applying "
"the barotropic-closure displacement action. "
"Element: "
<< elementId
<< ", attribute: " << transformation->Attribute
<< ", quadrature point: " << quadraturePoint
<< ", status: " << static_cast<int>(mappingStatus)
);
const mapping::MappingStatus variationStatus =
domainMapper.EvaluateVolumeVariation(
mappingData, displacementVariationData, *transformation,
integrationPoint, mappingContext, workspace,
mappingVariation
);
MFEM_VERIFY(
variationStatus == mapping::MappingStatus::valid,
"The mapping variation is invalid while "
"applying the barotropic-closure "
"displacement action. Element: "
<< elementId
<< ", attribute: " << transformation->Attribute
<< ", quadrature point: " << quadraturePoint
<< ", status: " << static_cast<int>(variationStatus)
);
densityElement.CalcShape(integrationPoint, densityShape);
enthalpyElement.CalcShape(integrationPoint, enthalpyShape);
const double densityValue = elementBaseDensity * densityShape;
const double enthalpyValue =
elementBaseEnthalpy * enthalpyShape;
const double closureValue =
densityValue -
barotrope.density_from_enthalpy(enthalpyValue);
const double geometryActionValue =
closureValue * mappingVariation.weight_variation;
MFEM_VERIFY(
std::isfinite(closureValue) &&
std::isfinite(geometryActionValue),
"The barotropic-closure displacement action "
"encountered a non-finite quadrature value."
);
elementAction.Add(geometryActionValue, densityShape);
}
if (densityDofTransformation != nullptr) {
densityDofTransformation->TransformDual(elementAction);
}
localAction.AddElementVector(densityDofs, elementAction);
}
local_to_true(*f.densityFes, localAction, action);
}
} // namespace mean_field::operators::kernels

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,775 @@
module;
#include <algorithm>
#include <array>
#include <cmath>
#include <mfem.hpp>
#include <optional>
module mean_field;
import :operators.kernels.hydrostatic_equilibrium;
namespace {
void true_to_local(
const mfem::ParFiniteElementSpace &finiteElementSpace,
const mfem::Vector &trueVector,
mfem::Vector &localVector
) {
MFEM_VERIFY(
trueVector.Size() == finiteElementSpace.GetTrueVSize(),
"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(),
"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;
}
}
void validate_fem(
const mean_field::fem::FEM &f,
const mean_field::mapping::DomainMapperStateless &domainMapper
) {
MFEM_VERIFY(
f.mesh != nullptr, "The hydrostatic kernel requires a mesh."
);
MFEM_VERIFY(
f.enthalpyFes != nullptr, "The hydrostatic kernel requires the "
"enthalpy finite-element space."
);
MFEM_VERIFY(
f.gravityPotentialFes != nullptr,
"The hydrostatic kernel requires the "
"gravity-potential finite-element space."
);
MFEM_VERIFY(
f.displacementFes != nullptr, "The hydrostatic kernel requires the "
"displacement finite-element space."
);
MFEM_VERIFY(
f.compactificationFes != nullptr,
"The hydrostatic kernel requires the "
"compactification finite-element space."
);
MFEM_VERIFY(
f.compactificationCoordinate != nullptr,
"The hydrostatic kernel requires the "
"compactification coordinate."
);
MFEM_VERIFY(
f.quadratureFactory != nullptr,
"The hydrostatic kernel requires the "
"quadrature-rule factory."
);
MFEM_VERIFY(
f.mesh->Dimension() == 3,
"The rigid-rotation hydrostatic kernel "
"currently requires a three-dimensional mesh."
);
MFEM_VERIFY(
domainMapper.GetDimension() == f.mesh->Dimension(),
"The domain-mapper dimension does not match "
"the mesh dimension."
);
}
const mfem::IntegrationRule &get_hydrostatic_rule(
const mean_field::fem::FEM &f,
const mfem::FiniteElement &enthalpyElement,
const mfem::FiniteElement &potentialElement,
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 hydrostatic test element does not match "
"the registered enthalpy field."
);
MFEM_VERIFY(
potentialElement.GetOrder() ==
mean_field::field::Gravity::Potential::familyOrder,
"The hydrostatic potential element does not "
"match the registered gravity-potential field."
);
const auto enthalpyQuery = EnthalpyField::make_query<
mean_field::field::Enthalpy::Form::EquilibriumEnthalpy>(
mean_field::quadrature::QuadratureRole::discretization,
transformation.OrderW(), {}, mean_field::utils::DOMAINS::STELLAR,
mean_field::quadrature::MappingKind::general
);
const auto gravityQuery = EnthalpyField::make_query<
mean_field::field::Enthalpy::Form::EquilibriumGravity>(
mean_field::quadrature::QuadratureRole::discretization,
transformation.OrderW(), {}, mean_field::utils::DOMAINS::STELLAR,
mean_field::quadrature::MappingKind::general
);
const auto rotationQuery = EnthalpyField::make_query<
mean_field::field::Enthalpy::Form::EquilibriumRotation>(
mean_field::quadrature::QuadratureRole::discretization,
transformation.OrderW(), std::array<int, 1>{2},
mean_field::utils::DOMAINS::STELLAR,
mean_field::quadrature::MappingKind::general
);
const auto constantQuery = EnthalpyField::make_query<
mean_field::field::Enthalpy::Form::EquilibriumConstant>(
mean_field::quadrature::QuadratureRole::discretization,
transformation.OrderW(), {}, mean_field::utils::DOMAINS::STELLAR,
mean_field::quadrature::MappingKind::general
);
int integrationOrder = 0;
const auto update_order = [&f, &transformation, &integrationOrder](
const mean_field::quadrature::Query &query
) {
const auto rule = f.quadratureFactory->get(
query, transformation.GetGeometryType()
);
MFEM_VERIFY(
rule.integration_rule != nullptr,
"The quadrature policy did not return "
"a hydrostatic-equilibrium rule."
);
integrationOrder =
std::max(integrationOrder, rule.resolution.order);
};
update_order(enthalpyQuery);
update_order(gravityQuery);
update_order(rotationQuery);
update_order(constantQuery);
return mfem::IntRules.Get(
transformation.GetGeometryType(), integrationOrder
);
}
struct HydrostaticAssemblyRequest {
const mean_field::physics::RigidRotation *rotation{nullptr};
const mfem::Vector *baseEnthalpyTrue{nullptr};
const mfem::Vector *basePotentialTrue{nullptr};
const mfem::Vector *enthalpyVariationTrue{nullptr};
const mfem::Vector *potentialVariationTrue{nullptr};
const mfem::Vector *displacementVariationTrue{nullptr};
double bernoulliConstant{0.0};
double constantVariation{0.0};
bool buildResidual{false};
};
void assemble_hydrostatic_form(
const mean_field::fem::FEM &f,
const mean_field::mapping::DomainMapperStateless &domainMapper,
const mfem::Vector &displacementTrue,
const HydrostaticAssemblyRequest &request,
mfem::Vector &result
) {
validate_fem(f, domainMapper);
MFEM_VERIFY(
displacementTrue.Size() == f.displacementFes->GetTrueVSize(),
"The hydrostatic displacement vector has "
"the wrong size."
);
MFEM_VERIFY(
std::isfinite(request.bernoulliConstant),
"The Bernoulli constant is non-finite."
);
MFEM_VERIFY(
std::isfinite(request.constantVariation),
"The Bernoulli-constant variation is non-finite."
);
const bool requiresBaseState =
request.buildResidual ||
request.displacementVariationTrue != nullptr;
if (requiresBaseState) {
MFEM_VERIFY(
request.rotation != nullptr,
"The hydrostatic residual or geometry "
"action requires the rotation model."
);
MFEM_VERIFY(
request.baseEnthalpyTrue != nullptr,
"The hydrostatic residual or geometry "
"action requires the base enthalpy."
);
MFEM_VERIFY(
request.basePotentialTrue != nullptr,
"The hydrostatic residual or geometry "
"action requires the base potential."
);
}
if (request.baseEnthalpyTrue != nullptr) {
MFEM_VERIFY(
request.baseEnthalpyTrue->Size() ==
f.enthalpyFes->GetTrueVSize(),
"The base enthalpy vector has the wrong size."
);
}
if (request.basePotentialTrue != nullptr) {
MFEM_VERIFY(
request.basePotentialTrue->Size() ==
f.gravityPotentialFes->GetTrueVSize(),
"The base potential vector has the wrong size."
);
}
if (request.enthalpyVariationTrue != nullptr) {
MFEM_VERIFY(
request.enthalpyVariationTrue->Size() ==
f.enthalpyFes->GetTrueVSize(),
"The enthalpy variation has the wrong size."
);
}
if (request.potentialVariationTrue != nullptr) {
MFEM_VERIFY(
request.potentialVariationTrue->Size() ==
f.gravityPotentialFes->GetTrueVSize(),
"The potential variation has the wrong size."
);
}
if (request.displacementVariationTrue != nullptr) {
MFEM_VERIFY(
request.displacementVariationTrue->Size() ==
f.displacementFes->GetTrueVSize(),
"The displacement variation has the wrong size."
);
}
mfem::Vector displacementLocal;
true_to_local(*f.displacementFes, displacementTrue, displacementLocal);
mfem::Vector baseEnthalpyLocal;
mfem::Vector basePotentialLocal;
mfem::Vector enthalpyVariationLocal;
mfem::Vector potentialVariationLocal;
mfem::Vector displacementVariationLocal;
if (request.baseEnthalpyTrue != nullptr) {
true_to_local(
*f.enthalpyFes, *request.baseEnthalpyTrue, baseEnthalpyLocal
);
}
if (request.basePotentialTrue != nullptr) {
true_to_local(
*f.gravityPotentialFes, *request.basePotentialTrue,
basePotentialLocal
);
}
if (request.enthalpyVariationTrue != nullptr) {
true_to_local(
*f.enthalpyFes, *request.enthalpyVariationTrue,
enthalpyVariationLocal
);
}
if (request.potentialVariationTrue != nullptr) {
true_to_local(
*f.gravityPotentialFes, *request.potentialVariationTrue,
potentialVariationLocal
);
}
if (request.displacementVariationTrue != nullptr) {
true_to_local(
*f.displacementFes, *request.displacementVariationTrue,
displacementVariationLocal
);
}
mfem::Vector localResult(f.enthalpyFes->GetVSize());
localResult = 0.0;
mean_field::mapping::DomainMapperStateless::Workspace workspace(
f.mesh->Dimension()
);
mfem::Array<int> enthalpyDofs;
mfem::Array<int> potentialDofs;
mfem::Array<int> displacementDofs;
mfem::Array<int> compactificationDofs;
mfem::Vector elementBaseEnthalpy;
mfem::Vector elementBasePotential;
mfem::Vector elementEnthalpyVariation;
mfem::Vector elementPotentialVariation;
mfem::Vector elementDisplacement;
mfem::Vector elementDisplacementVariation;
mfem::Vector elementCompactification;
mfem::Vector elementResult;
mfem::Vector enthalpyShape;
mfem::Vector potentialShape;
const int vacuumAttribute = domainMapper.GetVacuumElementAttribute();
for (int elementId = 0; elementId < f.mesh->GetNE(); ++elementId) {
mfem::ElementTransformation *transformation =
f.mesh->GetElementTransformation(elementId);
MFEM_VERIFY(
transformation != nullptr,
"The hydrostatic kernel received a null "
"element transformation."
);
if (transformation->Attribute == vacuumAttribute) {
continue;
}
const mfem::FiniteElement &enthalpyElement =
*f.enthalpyFes->GetFE(elementId);
const mfem::FiniteElement &potentialElement =
*f.gravityPotentialFes->GetFE(elementId);
const mfem::FiniteElement &displacementElement =
*f.displacementFes->GetFE(elementId);
const mfem::FiniteElement &compactificationElement =
*f.compactificationFes->GetFE(elementId);
mfem::DofTransformation *enthalpyDofTransformation =
f.enthalpyFes->GetElementDofs(elementId, enthalpyDofs);
mfem::DofTransformation *potentialDofTransformation =
f.gravityPotentialFes->GetElementDofs(elementId, potentialDofs);
mfem::DofTransformation *displacementDofTransformation =
f.displacementFes->GetElementVDofs(elementId, displacementDofs);
mfem::DofTransformation *compactificationDofTransformation =
f.compactificationFes->GetElementDofs(
elementId, compactificationDofs
);
displacementLocal.GetSubVector(
displacementDofs, elementDisplacement
);
f.compactificationCoordinate->GetSubVector(
compactificationDofs, elementCompactification
);
if (request.baseEnthalpyTrue != nullptr) {
baseEnthalpyLocal.GetSubVector(
enthalpyDofs, elementBaseEnthalpy
);
}
if (request.basePotentialTrue != nullptr) {
basePotentialLocal.GetSubVector(
potentialDofs, elementBasePotential
);
}
if (request.enthalpyVariationTrue != nullptr) {
enthalpyVariationLocal.GetSubVector(
enthalpyDofs, elementEnthalpyVariation
);
}
if (request.potentialVariationTrue != nullptr) {
potentialVariationLocal.GetSubVector(
potentialDofs, elementPotentialVariation
);
}
if (request.displacementVariationTrue != nullptr) {
displacementVariationLocal.GetSubVector(
displacementDofs, elementDisplacementVariation
);
}
if (enthalpyDofTransformation != nullptr) {
if (request.baseEnthalpyTrue != nullptr) {
enthalpyDofTransformation->InvTransformPrimal(
elementBaseEnthalpy
);
}
if (request.enthalpyVariationTrue != nullptr) {
enthalpyDofTransformation->InvTransformPrimal(
elementEnthalpyVariation
);
}
}
if (potentialDofTransformation != nullptr) {
if (request.basePotentialTrue != nullptr) {
potentialDofTransformation->InvTransformPrimal(
elementBasePotential
);
}
if (request.potentialVariationTrue != nullptr) {
potentialDofTransformation->InvTransformPrimal(
elementPotentialVariation
);
}
}
if (displacementDofTransformation != nullptr) {
displacementDofTransformation->InvTransformPrimal(
elementDisplacement
);
if (request.displacementVariationTrue != nullptr) {
displacementDofTransformation->InvTransformPrimal(
elementDisplacementVariation
);
}
}
if (compactificationDofTransformation != nullptr) {
compactificationDofTransformation->InvTransformPrimal(
elementCompactification
);
}
const mean_field::mapping::ElementDisplacementData
displacementData = mean_field::mapping::
ElementDisplacementDataFromElementVDofs(
displacementElement, elementDisplacement
);
const mean_field::mapping::ElementCompactificationData
compactificationData(
compactificationElement, elementCompactification
);
const mean_field::mapping::ElementMappingData mappingData{
.displacement = displacementData,
.compactification = compactificationData
};
std::optional<mean_field::mapping::ElementDisplacementData>
displacementVariationData;
if (request.displacementVariationTrue != nullptr) {
displacementVariationData.emplace(
mean_field::mapping::
ElementDisplacementDataFromElementVDofs(
displacementElement, elementDisplacementVariation
)
);
}
elementResult.SetSize(enthalpyElement.GetDof());
elementResult = 0.0;
enthalpyShape.SetSize(enthalpyElement.GetDof());
potentialShape.SetSize(potentialElement.GetDof());
const mfem::IntegrationRule &integrationRule = get_hydrostatic_rule(
f, enthalpyElement, potentialElement, *transformation
);
for (int quadraturePoint = 0;
quadraturePoint < integrationRule.GetNPoints();
++quadraturePoint) {
const mfem::IntegrationPoint &integrationPoint =
integrationRule.IntPoint(quadraturePoint);
transformation->SetIntPoint(&integrationPoint);
mean_field::mapping::VolumeMappingContext mappingContext;
const mean_field::mapping::MappingStatus mappingStatus =
domainMapper.EvaluateVolume(
mappingData, *transformation, integrationPoint,
workspace, mappingContext
);
MFEM_VERIFY(
mappingStatus == mean_field::mapping::MappingStatus::valid,
"The base mapping is invalid in the "
"hydrostatic kernel. Element: "
<< elementId
<< ", quadrature point: " << quadraturePoint
<< ", status: " << static_cast<int>(mappingStatus)
);
enthalpyElement.CalcShape(integrationPoint, enthalpyShape);
potentialElement.CalcShape(integrationPoint, potentialShape);
double baseIntegrand = 0.0;
if (requiresBaseState) {
const double enthalpyValue =
elementBaseEnthalpy * enthalpyShape;
const double potentialValue =
elementBasePotential * potentialShape;
const double rotationPotential =
request.rotation->potential(
mappingContext.mapping.physical_position
);
baseIntegrand = enthalpyValue + potentialValue -
rotationPotential -
request.bernoulliConstant;
}
if (request.buildResidual) {
elementResult.Add(
mappingContext.quadrature.weight * baseIntegrand,
enthalpyShape
);
continue;
}
double materialVariation = -request.constantVariation;
if (request.enthalpyVariationTrue != nullptr) {
materialVariation +=
elementEnthalpyVariation * enthalpyShape;
}
if (request.potentialVariationTrue != nullptr) {
materialVariation +=
elementPotentialVariation * potentialShape;
}
double weightedVariation =
mappingContext.quadrature.weight * materialVariation;
if (request.displacementVariationTrue != nullptr) {
mean_field::mapping::VolumeMappingVariation
mappingVariation;
const mean_field::mapping::MappingStatus variationStatus =
domainMapper.EvaluateVolumeVariation(
mappingData, *displacementVariationData,
*transformation, integrationPoint, mappingContext,
workspace, mappingVariation
);
MFEM_VERIFY(
variationStatus ==
mean_field::mapping::MappingStatus::valid,
"The mapping variation is invalid "
"in the hydrostatic kernel."
);
const double rotationVariation =
request.rotation->potential_directional_derivative(
mappingContext.mapping.physical_position,
mappingVariation.mapping.physical_position_variation
);
weightedVariation +=
baseIntegrand * mappingVariation.weight_variation -
rotationVariation * mappingContext.quadrature.weight;
}
elementResult.Add(weightedVariation, enthalpyShape);
}
if (enthalpyDofTransformation != nullptr) {
enthalpyDofTransformation->TransformDual(elementResult);
}
localResult.AddElementVector(enthalpyDofs, elementResult);
}
local_to_true(*f.enthalpyFes, localResult, result);
}
} // namespace
namespace mean_field::operators::kernels {
void apply_hydrostatic_equilibrium(
const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper,
const physics::RigidRotation &rotation,
const mfem::Vector &enthalpyTrue,
const mfem::Vector &potentialTrue,
const mfem::Vector &displacementTrue,
const double bernoulliConstant,
mfem::Vector &residual
) {
HydrostaticAssemblyRequest request;
request.rotation = &rotation;
request.baseEnthalpyTrue = &enthalpyTrue;
request.basePotentialTrue = &potentialTrue;
request.bernoulliConstant = bernoulliConstant;
request.buildResidual = true;
assemble_hydrostatic_form(
f, domainMapper, displacementTrue, request, residual
);
}
void apply_hydrostatic_equilibrium_enthalpy_action(
const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper,
const mfem::Vector &enthalpyVariationTrue,
const mfem::Vector &displacementTrue,
mfem::Vector &action
) {
HydrostaticAssemblyRequest request;
request.enthalpyVariationTrue = &enthalpyVariationTrue;
assemble_hydrostatic_form(
f, domainMapper, displacementTrue, request, action
);
}
void apply_hydrostatic_equilibrium_potential_action(
const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper,
const mfem::Vector &potentialVariationTrue,
const mfem::Vector &displacementTrue,
mfem::Vector &action
) {
HydrostaticAssemblyRequest request;
request.potentialVariationTrue = &potentialVariationTrue;
assemble_hydrostatic_form(
f, domainMapper, displacementTrue, request, action
);
}
void apply_hydrostatic_equilibrium_constant_action(
const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper,
const double constantVariation,
const mfem::Vector &displacementTrue,
mfem::Vector &action
) {
HydrostaticAssemblyRequest request;
request.constantVariation = constantVariation;
assemble_hydrostatic_form(
f, domainMapper, displacementTrue, request, action
);
}
void apply_hydrostatic_equilibrium_displacement_action(
const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper,
const physics::RigidRotation &rotation,
const mfem::Vector &baseEnthalpyTrue,
const mfem::Vector &basePotentialTrue,
const mfem::Vector &baseDisplacementTrue,
const double baseBernoulliConstant,
const mfem::Vector &displacementVariationTrue,
mfem::Vector &action
) {
HydrostaticAssemblyRequest request;
request.rotation = &rotation;
request.baseEnthalpyTrue = &baseEnthalpyTrue;
request.basePotentialTrue = &basePotentialTrue;
request.displacementVariationTrue = &displacementVariationTrue;
request.bernoulliConstant = baseBernoulliConstant;
assemble_hydrostatic_form(
f, domainMapper, baseDisplacementTrue, request, action
);
}
void apply_hydrostatic_equilibrium_action(
const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper,
const physics::RigidRotation &rotation,
const mfem::Vector &baseEnthalpyTrue,
const mfem::Vector &basePotentialTrue,
const mfem::Vector &baseDisplacementTrue,
const double baseBernoulliConstant,
const mfem::Vector &enthalpyVariationTrue,
const mfem::Vector &potentialVariationTrue,
const double constantVariation,
const mfem::Vector &displacementVariationTrue,
mfem::Vector &action
) {
HydrostaticAssemblyRequest request;
request.rotation = &rotation;
request.baseEnthalpyTrue = &baseEnthalpyTrue;
request.basePotentialTrue = &basePotentialTrue;
request.enthalpyVariationTrue = &enthalpyVariationTrue;
request.potentialVariationTrue = &potentialVariationTrue;
request.displacementVariationTrue = &displacementVariationTrue;
request.bernoulliConstant = baseBernoulliConstant;
request.constantVariation = constantVariation;
assemble_hydrostatic_form(
f, domainMapper, baseDisplacementTrue, request, action
);
}
} // namespace mean_field::operators::kernels

View File

@@ -0,0 +1,464 @@
module;
#include <array>
#include <cmath>
#include <limits>
#include <mfem.hpp>
module mean_field;
import :operators.kernels.pressure_force;
namespace {
void true_to_local(
const mfem::ParFiniteElementSpace &finiteElementSpace,
const mfem::Vector &trueVector,
mfem::Vector &localVector
) {
MFEM_VERIFY(
trueVector.Size() == finiteElementSpace.GetTrueVSize(),
"The 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(),
"The 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 component + scalarDof * dimension;
}
MFEM_ABORT("The displacement space uses an unsupported ordering.");
return -1;
}
[[nodiscard]] int get_pressure_extra_order(
const mean_field::physics::PolytropicBarotrope &barotrope
) {
/*
* Pressure has the enthalpy dependence
*
* P(h) proportional to h^(n + 1).
*
* The registered enthalpy operand already contributes one factor
* of the enthalpy polynomial order. The remaining dynamic
* contribution is therefore n times that order.
*/
const double extraOrder =
barotrope.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 pressure 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::physics::PolytropicBarotrope &barotrope,
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 pressure-force enthalpy element does not match the "
"registered enthalpy field."
);
MFEM_VERIFY(
displacementElement.GetOrder() ==
mean_field::field::Displacement::Vector::familyOrder,
"The pressure-force test element does not match the "
"registered displacement field."
);
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(barotrope)},
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 pressure-force "
"integration rule."
);
return *rule.integration_rule;
}
void validate_inputs(
const mean_field::fem::FEM &f,
const mean_field::mapping::DomainMapperStateless &domainMapper,
const mfem::Vector &enthalpyTrue,
const mfem::Vector &displacementTrue
) {
MFEM_VERIFY(
f.mesh != nullptr, "The pressure-force kernel requires a mesh."
);
MFEM_VERIFY(
f.enthalpyFes != nullptr,
"The pressure-force kernel requires the enthalpy "
"finite-element space."
);
MFEM_VERIFY(
f.displacementFes != nullptr,
"The pressure-force kernel requires the displacement "
"finite-element space."
);
MFEM_VERIFY(
f.compactificationFes != nullptr,
"The pressure-force kernel requires the compactification "
"finite-element space."
);
MFEM_VERIFY(
f.compactificationCoordinate != nullptr,
"The pressure-force kernel requires the compactification "
"coordinate."
);
MFEM_VERIFY(
f.quadratureFactory != nullptr,
"The pressure-force kernel requires the quadrature "
"rule factory."
);
MFEM_VERIFY(
enthalpyTrue.Size() == f.enthalpyFes->GetTrueVSize(),
"The pressure-force enthalpy vector has the wrong size."
);
MFEM_VERIFY(
displacementTrue.Size() == f.displacementFes->GetTrueVSize(),
"The pressure-force displacement vector has the wrong size."
);
MFEM_VERIFY(
domainMapper.GetDimension() == f.mesh->Dimension(),
"The pressure-force domain-mapper dimension does not match "
"the mesh dimension."
);
MFEM_VERIFY(
f.displacementFes->GetVDim() == f.mesh->Dimension(),
"The displacement vector dimension does not match the "
"mesh dimension."
);
MFEM_VERIFY(
f.displacementFes->GetOrdering() == mfem::Ordering::byNODES,
"The pressure-force kernel requires the registered byNODES "
"displacement ordering."
);
}
} // namespace
namespace mean_field::operators::kernels {
void apply_pressure_force_residual(
const fem::FEM &f,
const mapping::DomainMapperStateless &domainMapper,
const physics::PolytropicBarotrope &barotrope,
const mfem::Vector &enthalpyTrue,
const mfem::Vector &displacementTrue,
mfem::Vector &residualTrue
) {
validate_inputs(f, domainMapper, enthalpyTrue, displacementTrue);
mfem::Vector enthalpyLocal;
mfem::Vector displacementLocal;
true_to_local(*f.enthalpyFes, enthalpyTrue, enthalpyLocal);
true_to_local(*f.displacementFes, displacementTrue, displacementLocal);
mfem::Vector localResidual(f.displacementFes->GetVSize());
localResidual = 0.0;
mapping::DomainMapperStateless::Workspace workspace(
f.mesh->Dimension()
);
mfem::Array<int> enthalpyDofs;
mfem::Array<int> displacementDofs;
mfem::Array<int> compactificationDofs;
mfem::Vector elementEnthalpy;
mfem::Vector elementDisplacement;
mfem::Vector elementCompactification;
mfem::Vector elementResidual;
mfem::Vector enthalpyShape;
mfem::DenseMatrix displacementDShapeReference;
mfem::DenseMatrix displacementDShapePhysical;
mapping::VolumeMappingContext mappingContext;
const int dimension = f.mesh->Dimension();
const int vacuumAttribute = domainMapper.GetVacuumElementAttribute();
const mfem::Ordering::Type displacementOrdering =
f.displacementFes->GetOrdering();
for (int elementId = 0; elementId < f.mesh->GetNE(); ++elementId) {
mfem::ElementTransformation *transformation =
f.mesh->GetElementTransformation(elementId);
MFEM_VERIFY(
transformation != nullptr,
"The pressure-force kernel received a null element "
"transformation."
);
/*
* Skip vacuum before constructing or evaluating any mapping
* data for the element.
*/
if (transformation->Attribute == vacuumAttribute) {
continue;
}
const mfem::FiniteElement &enthalpyElement =
*f.enthalpyFes->GetFE(elementId);
const mfem::FiniteElement &displacementElement =
*f.displacementFes->GetFE(elementId);
const mfem::FiniteElement &compactificationElement =
*f.compactificationFes->GetFE(elementId);
mfem::DofTransformation *enthalpyDofTransformation =
f.enthalpyFes->GetElementDofs(elementId, enthalpyDofs);
mfem::DofTransformation *displacementDofTransformation =
f.displacementFes->GetElementVDofs(elementId, displacementDofs);
mfem::DofTransformation *compactificationDofTransformation =
f.compactificationFes->GetElementDofs(
elementId, compactificationDofs
);
enthalpyLocal.GetSubVector(enthalpyDofs, elementEnthalpy);
displacementLocal.GetSubVector(
displacementDofs, elementDisplacement
);
f.compactificationCoordinate->GetSubVector(
compactificationDofs, elementCompactification
);
if (enthalpyDofTransformation != nullptr) {
enthalpyDofTransformation->InvTransformPrimal(elementEnthalpy);
}
if (displacementDofTransformation != nullptr) {
displacementDofTransformation->InvTransformPrimal(
elementDisplacement
);
}
if (compactificationDofTransformation != nullptr) {
compactificationDofTransformation->InvTransformPrimal(
elementCompactification
);
}
const mapping::ElementDisplacementData displacementData =
mapping::ElementDisplacementDataFromElementVDofs(
displacementElement, elementDisplacement
);
const mapping::ElementCompactificationData compactificationData(
compactificationElement, elementCompactification
);
const mapping::ElementMappingData mappingData{
.displacement = displacementData,
.compactification = compactificationData
};
const int scalarDisplacementDofCount = displacementElement.GetDof();
MFEM_VERIFY(
displacementDofs.Size() ==
scalarDisplacementDofCount * dimension,
"The pressure-force element displacement vector has "
"the wrong size."
);
enthalpyShape.SetSize(enthalpyElement.GetDof());
displacementDShapeReference.SetSize(
scalarDisplacementDofCount, dimension
);
displacementDShapePhysical.SetSize(
scalarDisplacementDofCount, dimension
);
elementResidual.SetSize(displacementDofs.Size());
elementResidual = 0.0;
const mfem::IntegrationRule &integrationRule =
get_pressure_force_rule(
f, barotrope, enthalpyElement, displacementElement,
*transformation
);
for (int quadratureIndex = 0;
quadratureIndex < integrationRule.GetNPoints();
++quadratureIndex) {
const mfem::IntegrationPoint &integrationPoint =
integrationRule.IntPoint(quadratureIndex);
transformation->SetIntPoint(&integrationPoint);
const mapping::MappingStatus mappingStatus =
domainMapper.EvaluateVolume(
mappingData, *transformation, integrationPoint,
workspace, mappingContext
);
MFEM_VERIFY(
mappingStatus == mapping::MappingStatus::valid,
"Stateless mapping failed in the pressure-force "
"kernel. Element: "
<< elementId
<< ", attribute: " << transformation->Attribute
<< ", quadrature point: " << quadratureIndex
<< ", status: " << static_cast<int>(mappingStatus)
);
enthalpyElement.CalcShape(integrationPoint, enthalpyShape);
const double enthalpyValue = elementEnthalpy * enthalpyShape;
const double pressureValue =
barotrope.pressure_from_enthalpy(enthalpyValue);
displacementElement.CalcDShape(
integrationPoint, displacementDShapeReference
);
/*
* Row i of DShape is grad_reference(N_i). Multiplication
* by the complete inverse element Jacobian gives
*
* grad_physical(N_i)
* = grad_reference(N_i) J^{-1}.
*/
mfem::Mult(
displacementDShapeReference,
mappingContext.quadrature.J_inv, displacementDShapePhysical
);
const double weightedPressure =
pressureValue * mappingContext.quadrature.weight;
MFEM_VERIFY(
std::isfinite(pressureValue) &&
std::isfinite(weightedPressure),
"The pressure-force kernel encountered a non-finite "
"quadrature value."
);
/*
* For the vector basis N_i e_c,
*
* div(N_i e_c) = partial_c N_i.
*
* Therefore
*
* R_(i,c)
* = -integral P partial_c N_i dV.
*/
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
);
elementResidual(vectorDof) -=
weightedPressure *
displacementDShapePhysical(scalarDof, component);
}
}
}
if (displacementDofTransformation != nullptr) {
displacementDofTransformation->TransformDual(elementResidual);
}
localResidual.AddElementVector(displacementDofs, elementResidual);
}
local_to_true(*f.displacementFes, localResidual, residualTrue);
}
} // namespace mean_field::operators::kernels