2026-07-15 09:44:43 -04:00
module ;
2026-08-29 08:56:36 -04:00
# include <cmath>
# include <memory>
2026-07-15 09:44:43 -04:00
# include <mfem.hpp>
2026-08-29 08:56:36 -04:00
# include <stdexcept>
# include <utility>
2026-07-15 09:44:43 -04:00
module mean_field ;
2026-08-04 14:24:55 -04:00
import : mapping . types ;
2026-08-29 08:56:36 -04:00
import : mapping . compactification ;
import : utils . user ;
2026-08-04 14:24:55 -04:00
namespace {
2026-08-30 16:41:14 -04:00
bool vector_is_finite ( const mfem : : Vector & vector ) {
for ( int i = 0 ; i < vector . Size ( ) ; + + i ) {
if ( ! std : : isfinite ( vector ( i ) ) )
return false ;
}
return true ;
}
bool matrix_is_finite ( const mfem : : DenseMatrix & matrix ) {
for ( int i = 0 ; i < matrix . Height ( ) ; + + i ) {
for ( int j = 0 ; j < matrix . Width ( ) ; + + j ) {
if ( ! std : : isfinite ( matrix ( i , j ) ) )
return false ;
}
}
return true ;
}
2026-08-04 14:24:55 -04:00
} // namespace
2026-07-15 09:44:43 -04:00
namespace mean_field : : mapping {
2026-08-30 16:41:14 -04:00
ElementCompactificationData : : ElementCompactificationData (
const mfem : : FiniteElement & element ,
const mfem : : Vector & dofs
)
: m_element ( & element ) ,
m_dofs ( dofs ) {
if ( element . GetRangeType ( ) ! = mfem : : FiniteElement : : SCALAR ) {
throw std : : invalid_argument ( " Compactification coordinate requires a scalar finite element. " ) ;
}
if ( element . GetMapType ( ) ! = mfem : : FiniteElement : : VALUE ) {
throw std : : invalid_argument (
" Compactification coordinate requires a value-mapped scalar "
" finite "
" element. "
) ;
}
if ( element . GetDerivType ( ) ! = mfem : : FiniteElement : : GRAD ) {
throw std : : invalid_argument (
" Compactification coordinate finite element must provide a "
" gradient. "
) ;
}
if ( element . GetDof ( ) < = 0 ) {
throw std : : invalid_argument (
" Compactification coordinate finite element has no degrees of "
" freedom. "
) ;
}
if ( dofs . Size ( ) ! = element . GetDof ( ) ) {
throw std : : invalid_argument (
" Compactification coordinate DOF count does not match its "
" finite "
" element. "
) ;
}
}
const mfem : : FiniteElement & ElementCompactificationData : : GetElement ( ) const noexcept {
return * m_element ;
}
const mfem : : Vector & ElementCompactificationData : : GetDofs ( ) const noexcept {
return m_dofs ;
}
int ElementCompactificationData : : GetDofCount ( ) const noexcept {
return m_dofs . Size ( ) ;
}
ElementDisplacementData : : ElementDisplacementData (
const mfem : : FiniteElement & element ,
const mfem : : Vector & displacement_dofs ,
const mfem : : Ordering : : Type ordering
)
: m_element ( & element ) ,
m_dimension ( 0 ) ,
m_ordering ( ordering ) {
const int dof_count = element . GetDof ( ) ;
if ( dof_count < = 0 )
throw std : : invalid_argument (
" The displacement element must have at least one degree of "
" freedom. "
) ;
if ( displacement_dofs . Size ( ) < = 0 | | displacement_dofs . Size ( ) % dof_count ! = 0 ) {
throw std : : invalid_argument (
" The displacement vector size must be a positive multiple of "
" the "
" element degree-of-freedom count. "
) ;
}
m_dimension = displacement_dofs . Size ( ) / dof_count ;
m_dof_matrix . SetSize ( dof_count , m_dimension ) ;
if ( ordering = = mfem : : Ordering : : byNODES ) {
for ( int component = 0 ; component < m_dimension ; + + component ) {
for ( int i = 0 ; i < dof_count ; + + i ) {
m_dof_matrix ( i , component ) = displacement_dofs ( i + component * dof_count ) ;
}
}
} else if ( ordering = = mfem : : Ordering : : byVDIM ) {
for ( int i = 0 ; i < dof_count ; + + i ) {
for ( int component = 0 ; component < m_dimension ; + + component ) {
m_dof_matrix ( i , component ) = displacement_dofs ( component + i * m_dimension ) ;
}
}
} else {
throw std : : invalid_argument ( " Unsupported MFEM displacement ordering. " ) ;
}
}
const mfem : : FiniteElement & ElementDisplacementData : : GetElement ( ) const noexcept {
return * m_element ;
}
const mfem : : DenseMatrix & ElementDisplacementData : : GetDofMatrix ( ) const noexcept {
return m_dof_matrix ;
}
int ElementDisplacementData : : GetDimension ( ) const noexcept {
return m_dimension ;
}
int ElementDisplacementData : : GetDofCount ( ) const noexcept {
return m_element - > GetDof ( ) ;
}
mfem : : Ordering : : Type ElementDisplacementData : : GetOrdering ( ) const noexcept {
return m_ordering ;
}
ElementDisplacementData ElementDisplacementDataFromElementVDofs (
const mfem : : FiniteElement & element ,
const mfem : : Vector & displacement_dofs
) {
return ElementDisplacementData ( element , displacement_dofs , mfem : : Ordering : : byNODES ) ;
}
DomainMapper : : Workspace : : Workspace ( const int dimension ) {
SetDimension ( dimension ) ;
}
void DomainMapper : : Workspace : : SetDimension ( const int dimension ) {
if ( dimension < = 0 ) {
throw std : : invalid_argument ( " Domain mapping workspace dimension must be positive. " ) ;
}
m_dimension = dimension ;
m_field_value . SetSize ( dimension ) ;
m_field_jacobian . SetSize ( dimension , dimension ) ;
2026-09-10 06:50:56 -04:00
m_reference_field_jacobian . SetSize ( dimension , dimension ) ;
2026-08-30 16:41:14 -04:00
m_compactification_point . coordinate = 0.0 ;
m_compactification_point . coordinate_gradient . SetSize ( dimension ) ;
m_reference_normal . SetSize ( dimension ) ;
m_mapped_normal . SetSize ( dimension ) ;
m_full_element_jacobian . SetSize ( dimension , dimension ) ;
m_vector_temp . SetSize ( dimension ) ;
m_matrix_temp_1 . SetSize ( dimension , dimension ) ;
m_matrix_temp_2 . SetSize ( dimension , dimension ) ;
m_exterior_result . physical_position . SetSize ( dimension ) ;
m_exterior_result . mapping_jacobian . SetSize ( dimension , dimension ) ;
m_exterior_variation . physical_position_variation . SetSize ( dimension ) ;
m_exterior_variation . mapping_jacobian_variation . SetSize ( dimension , dimension ) ;
}
int DomainMapper : : Workspace : : GetDimension ( ) const noexcept {
return m_dimension ;
}
DomainMapper : : DomainMapper (
const utils : : DomainMapperOptions options ,
std : : unique_ptr < const compactification : : ExteriorDomainMap > exterior_map
)
: m_options ( options ) ,
m_exterior_map ( std : : move ( exterior_map ) ) {
if ( m_options . dimension < = 0 )
throw std : : invalid_argument ( " The domain-mapping dimension must be positive. " ) ;
if ( m_options . vacuum_element_attribute < = 0 )
throw std : : invalid_argument ( " The vacuum element attribute must be positive. " ) ;
if ( ! m_exterior_map )
throw std : : invalid_argument ( " DomainMapper requires an exterior-domain mapping. " ) ;
}
bool DomainMapper : : IsCompactifiedElement ( const mfem : : ElementTransformation & transformation ) const noexcept {
return transformation . Attribute = = m_options . vacuum_element_attribute ;
}
int DomainMapper : : GetDimension ( ) const noexcept {
return m_options . dimension ;
}
const compactification : : ExteriorDomainMap & DomainMapper : : GetExteriorMap ( ) const noexcept {
return * m_exterior_map ;
}
GridFunctionMappingEvaluator : : GridFunctionMappingEvaluator (
const DomainMapper & mapper ,
const mfem : : GridFunction & displacement ,
const mfem : : GridFunction & compactification_coordinate
)
: m_mapper ( mapper ) ,
m_displacement ( displacement ) ,
m_compactification_coordinate ( compactification_coordinate ) ,
m_displacement_space ( displacement . FESpace ( ) ) ,
m_compactification_space ( compactification_coordinate . FESpace ( ) ) ,
m_displacement_space_sequence ( m_displacement_space ! = nullptr ? m_displacement_space - > GetSequence ( ) : - 1 ) ,
m_compactification_space_sequence (
m_compactification_space ! = nullptr ? m_compactification_space - > GetSequence ( ) : - 1
) ,
m_workspace ( mapper . GetDimension ( ) ) {
if ( m_displacement_space = = nullptr ) {
throw std : : invalid_argument ( " Grid-function mapping requires a displacement finite-element space. " ) ;
}
if ( m_compactification_space = = nullptr ) {
throw std : : invalid_argument (
" Grid-function mapping requires a compactification finite-element "
" space. "
) ;
}
if ( m_displacement_space - > GetMesh ( ) ! = m_compactification_space - > GetMesh ( ) ) {
throw std : : invalid_argument ( " Grid-function mapping fields must use the same mesh. " ) ;
}
if ( m_displacement . VectorDim ( ) ! = mapper . GetDimension ( ) ) {
throw std : : invalid_argument ( " The displacement dimension does not match the domain mapper. " ) ;
}
if ( m_compactification_coordinate . VectorDim ( ) ! = 1 ) {
throw std : : invalid_argument ( " The compactification coordinate must be a scalar grid function. " ) ;
}
if ( m_displacement_space - > GetMesh ( ) - > SpaceDimension ( ) ! = mapper . GetDimension ( ) ) {
throw std : : invalid_argument ( " The mapping dimension does not match the mesh space dimension. " ) ;
}
}
void GridFunctionMappingEvaluator : : InvalidateCache ( ) noexcept {
m_displacement_data . reset ( ) ;
m_compactification_data . reset ( ) ;
m_cached_element_id = - 1 ;
}
void GridFunctionMappingEvaluator : : ValidateFieldBindings ( ) const {
if ( m_displacement . FESpace ( ) ! = m_displacement_space ) {
throw std : : invalid_argument (
" The displacement grid function was rebound after construction of "
" its mapping evaluator. "
) ;
}
if ( m_compactification_coordinate . FESpace ( ) ! = m_compactification_space ) {
throw std : : invalid_argument (
" The compactification grid function was rebound after construction "
" of its mapping evaluator. "
) ;
}
}
bool GridFunctionMappingEvaluator : : InvalidateForChangedSpaces ( ) {
const long displacement_sequence = m_displacement_space - > GetSequence ( ) ;
const long compactification_sequence = m_compactification_space - > GetSequence ( ) ;
if ( displacement_sequence = = m_displacement_space_sequence & &
compactification_sequence = = m_compactification_space_sequence ) {
return false ;
}
InvalidateCache ( ) ;
m_displacement_space_sequence = displacement_sequence ;
m_compactification_space_sequence = compactification_sequence ;
return true ;
}
void GridFunctionMappingEvaluator : : Refresh ( ) {
ValidateFieldBindings ( ) ;
if ( InvalidateForChangedSpaces ( ) ) {
return ;
}
const int element_id = m_cached_element_id ;
InvalidateCache ( ) ;
if ( element_id > = 0 ) {
LoadElement ( element_id ) ;
}
}
void GridFunctionMappingEvaluator : : LoadElement ( const int element_id ) {
ValidateFieldBindings ( ) ;
( void ) InvalidateForChangedSpaces ( ) ;
if ( element_id = = m_cached_element_id ) {
return ;
}
const mfem : : FiniteElementSpace & displacement_space = * m_displacement_space ;
const mfem : : FiniteElementSpace & compactification_space = * m_compactification_space ;
MFEM_VERIFY (
element_id > = 0 & & element_id < displacement_space . GetMesh ( ) - > GetNE ( ) ,
" Grid-function mapping received an invalid element ID. "
) ;
mfem : : DofTransformation * displacement_transformation =
displacement_space . GetElementVDofs ( element_id , m_displacement_dofs ) ;
compactification_space . GetElementDofs ( element_id , m_compactification_dofs ) ;
m_displacement . GetSubVector ( m_displacement_dofs , m_element_displacement ) ;
m_compactification_coordinate . GetSubVector ( m_compactification_dofs , m_element_compactification ) ;
if ( displacement_transformation ! = nullptr ) {
displacement_transformation - > InvTransformPrimal ( m_element_displacement ) ;
}
const mfem : : FiniteElement & displacement_element = * displacement_space . GetFE ( element_id ) ;
const mfem : : FiniteElement & compactification_element = * compactification_space . GetFE ( element_id ) ;
m_displacement_data = std : : make_unique < ElementDisplacementData > (
ElementDisplacementDataFromElementVDofs ( displacement_element , m_element_displacement )
) ;
m_compactification_data =
std : : make_unique < ElementCompactificationData > ( compactification_element , m_element_compactification ) ;
m_cached_element_id = element_id ;
}
MappingStatus GridFunctionMappingEvaluator : : EvaluatePoint (
mfem : : ElementTransformation & transformation ,
const mfem : : IntegrationPoint & integration_point ,
MappingPointContext & context
) {
LoadElement ( transformation . ElementNo ) ;
const ElementMappingData data {
. displacement = * m_displacement_data , . compactification = * m_compactification_data
} ;
return m_mapper . EvaluatePoint ( data , transformation , integration_point , m_workspace , context ) ;
}
MappingStatus GridFunctionMappingEvaluator : : EvaluateVolume (
mfem : : ElementTransformation & transformation ,
const mfem : : IntegrationPoint & integration_point ,
VolumeMappingContext & context
) {
LoadElement ( transformation . ElementNo ) ;
const ElementMappingData data {
. displacement = * m_displacement_data , . compactification = * m_compactification_data
} ;
return m_mapper . EvaluateVolume ( data , transformation , integration_point , m_workspace , context ) ;
}
MappingStatus GridFunctionMappingEvaluator : : EvaluateFace (
mfem : : FaceElementTransformations & transformation ,
const FaceElementSide side ,
const mfem : : IntegrationPoint & integration_point ,
FaceMappingContext & context
) {
mfem : : ElementTransformation * element_transformation =
side = = FaceElementSide : : element_1 ? transformation . Elem1 : transformation . Elem2 ;
MFEM_VERIFY ( element_transformation ! = nullptr , " Grid-function face mapping requires the requested element. " ) ;
LoadElement ( element_transformation - > ElementNo ) ;
const ElementMappingData data {
. displacement = * m_displacement_data , . compactification = * m_compactification_data
} ;
return m_mapper . EvaluateFace ( data , transformation , side , integration_point , m_workspace , context ) ;
}
VolumeQuadratureContext GridFunctionMappingEvaluator : : GetQuadratureContext (
mfem : : ElementTransformation & transformation ,
const mfem : : IntegrationPoint & integration_point
) {
VolumeMappingContext context ;
MFEM_VERIFY (
EvaluateVolume ( transformation , integration_point , context ) = = MappingStatus : : valid ,
" Volume quadrature encountered an invalid domain mapping. "
) ;
return context . quadrature ;
}
FaceQuadratureContext GridFunctionMappingEvaluator : : GetFaceQuadratureContext (
mfem : : FaceElementTransformations & transformation ,
const mfem : : IntegrationPoint & integration_point ,
const FaceElementSide side
) {
FaceMappingContext context ;
MFEM_VERIFY (
EvaluateFace ( transformation , side , integration_point , context ) = = MappingStatus : : valid ,
" Face quadrature encountered an invalid domain mapping. "
) ;
return context . quadrature ;
}
void GridFunctionMappingEvaluator : : GetPhysicalPoint (
mfem : : ElementTransformation & transformation ,
const mfem : : IntegrationPoint & integration_point ,
mfem : : Vector & physical_position
) {
MappingPointContext context ;
MFEM_VERIFY (
EvaluatePoint ( transformation , integration_point , context ) = = MappingStatus : : valid ,
" Physical-point evaluation encountered an invalid domain "
" mapping. "
) ;
physical_position = context . physical_position ;
}
void DomainMapper : : ValidateElementData ( const ElementMappingData & element_data ) const {
const ElementDisplacementData & displacement = element_data . displacement ;
const ElementCompactificationData & compactification = element_data . compactification ;
if ( displacement . GetDimension ( ) ! = m_options . dimension ) {
throw std : : invalid_argument (
" Displacement field dimension does not match the domain mapper "
" dimension. "
) ;
}
if ( displacement . GetElement ( ) . GetDim ( ) ! = m_options . dimension ) {
throw std : : invalid_argument (
" Displacement finite element dimension does not match the "
" domain "
" mapper dimension. "
) ;
}
if ( compactification . GetElement ( ) . GetDim ( ) ! = m_options . dimension ) {
throw std : : invalid_argument (
" Compactification finite element dimension does not match the "
" domain "
" mapper dimension. "
) ;
}
if ( displacement . GetElement ( ) . GetGeomType ( ) ! = compactification . GetElement ( ) . GetGeomType ( ) ) {
throw std : : invalid_argument (
" Displacement and compactification finite elements have "
" different "
" geometries. "
) ;
}
if ( compactification . GetElement ( ) . GetRangeType ( ) ! = mfem : : FiniteElement : : SCALAR ) {
throw std : : invalid_argument ( " Compactification coordinate requires a scalar finite element. " ) ;
}
if ( compactification . GetElement ( ) . GetMapType ( ) ! = mfem : : FiniteElement : : VALUE ) {
throw std : : invalid_argument (
" Compactification coordinate requires a value-mapped finite "
" element. "
) ;
}
if ( compactification . GetElement ( ) . GetDerivType ( ) ! = mfem : : FiniteElement : : GRAD ) {
throw std : : invalid_argument (
" Compactification coordinate finite element does not provide a "
" gradient. "
) ;
}
if ( compactification . GetDofCount ( ) ! = compactification . GetElement ( ) . GetDof ( ) ) {
throw std : : invalid_argument (
" Compactification coordinate DOF count does not match its "
" finite "
" element. "
) ;
}
}
MappingStatus DomainMapper : : EvaluateCompactificationCoordinate (
const ElementCompactificationData & compactification ,
mfem : : ElementTransformation & transformation ,
const mfem : : IntegrationPoint & integration_point ,
Workspace & workspace ,
2026-09-02 17:01:50 -04:00
CompactificationPointData & point_data ,
const mfem : : DenseMatrix * inverse_mesh_jacobian
2026-08-30 16:41:14 -04:00
) const {
const mfem : : FiniteElement & element = compactification . GetElement ( ) ;
const mfem : : Vector & dofs = compactification . GetDofs ( ) ;
const int dof_count = element . GetDof ( ) ;
if ( workspace . GetDimension ( ) ! = m_options . dimension | | transformation . GetSpaceDim ( ) ! = m_options . dimension | |
element . GetDim ( ) ! = m_options . dimension ) {
return MappingStatus : : invalid_dimension ;
}
if ( dofs . Size ( ) ! = dof_count ) {
return MappingStatus : : invalid_dimension ;
}
for ( int i = 0 ; i < dofs . Size ( ) ; + + i ) {
if ( ! std : : isfinite ( dofs ( i ) ) )
return MappingStatus : : non_finite_input ;
}
workspace . m_compactification_shape . SetSize ( dof_count ) ;
workspace . m_compactification_dshape . SetSize ( dof_count , m_options . dimension ) ;
element . CalcShape ( integration_point , workspace . m_compactification_shape ) ;
2026-09-02 17:01:50 -04:00
if ( inverse_mesh_jacobian ! = nullptr ) {
workspace . m_reference_dshape . SetSize ( dof_count , m_options . dimension ) ;
element . CalcDShape ( integration_point , workspace . m_reference_dshape ) ;
mfem : : Mult ( workspace . m_reference_dshape , * inverse_mesh_jacobian , workspace . m_compactification_dshape ) ;
} else {
element . CalcPhysDShape ( transformation , workspace . m_compactification_dshape ) ;
}
2026-08-30 16:41:14 -04:00
point_data . coordinate = dofs * workspace . m_compactification_shape ;
point_data . coordinate_gradient . SetSize ( m_options . dimension ) ;
workspace . m_compactification_dshape . MultTranspose ( dofs , point_data . coordinate_gradient ) ;
if ( ! std : : isfinite ( point_data . coordinate ) ) {
return MappingStatus : : non_finite_result ;
}
for ( int d = 0 ; d < point_data . coordinate_gradient . Size ( ) ; + + d ) {
if ( ! std : : isfinite ( point_data . coordinate_gradient ( d ) ) )
return MappingStatus : : non_finite_result ;
}
return MappingStatus : : valid ;
}
2026-09-10 06:50:56 -04:00
/**
* @ brief Evaluate a displacement field dof matrix at a given integration point and compute what the displacement of that point is and what the gradient of the the displacement is with respect to the computational coordinates / reference frame .
* @ note There is actually nothing in this function preventing some field other than displacement from being passed through here ; this should maybe be tightened .
*/
2026-08-30 16:41:14 -04:00
void DomainMapper : : EvaluateField (
const ElementDisplacementData & field ,
mfem : : ElementTransformation & transformation ,
const mfem : : IntegrationPoint & integration_point ,
Workspace & workspace ,
mfem : : Vector & value ,
2026-09-02 17:01:50 -04:00
mfem : : DenseMatrix & jacobian ,
const mfem : : DenseMatrix * inverse_mesh_jacobian
2026-08-30 16:41:14 -04:00
) const {
const mfem : : FiniteElement & element = field . GetElement ( ) ;
const mfem : : DenseMatrix & dof_matrix = field . GetDofMatrix ( ) ;
workspace . m_shape . SetSize ( element . GetDof ( ) ) ;
element . CalcShape ( integration_point , workspace . m_shape ) ;
2026-09-10 06:50:56 -04:00
value . SetSize ( m_options . dimension ) ;
dof_matrix . MultTranspose ( workspace . m_shape , value ) ;
jacobian . SetSize ( m_options . dimension , m_options . dimension ) ;
if ( inverse_mesh_jacobian ! = nullptr | | element . GetMapType ( ) = = mfem : : FiniteElement : : VALUE ) {
2026-09-02 17:01:50 -04:00
workspace . m_reference_dshape . SetSize ( element . GetDof ( ) , m_options . dimension ) ;
element . CalcDShape ( integration_point , workspace . m_reference_dshape ) ;
2026-09-10 06:50:56 -04:00
// Contract DOFs before applying fixed-mesh geometry. This is the
// same DOF^T * (Dshape * J_mesh^-1), without transforming every
// basis gradient. The scratch matrix must not alias the cached
// inverse supplied by EvaluateVolumeVariation.
mfem : : MultAtB ( dof_matrix , workspace . m_reference_dshape , workspace . m_reference_field_jacobian ) ;
const mfem : : DenseMatrix & inverseMeshJacobian =
inverse_mesh_jacobian ! = nullptr ? * inverse_mesh_jacobian : transformation . InverseJacobian ( ) ;
mfem : : Mult ( workspace . m_reference_field_jacobian , inverseMeshJacobian , jacobian ) ;
2026-09-02 17:01:50 -04:00
} else {
2026-09-10 06:50:56 -04:00
// Retain the original finite-element-specific physical-gradient
// path for mapping types without the ordinary VALUE pullback.
workspace . m_mesh_dshape . SetSize ( element . GetDof ( ) , m_options . dimension ) ;
2026-09-02 17:01:50 -04:00
element . CalcPhysDShape ( transformation , workspace . m_mesh_dshape ) ;
2026-09-10 06:50:56 -04:00
mfem : : MultAtB ( dof_matrix , workspace . m_mesh_dshape , jacobian ) ;
2026-09-02 17:01:50 -04:00
}
2026-08-30 16:41:14 -04:00
}
MappingStatus DomainMapper : : EvaluatePoint (
const ElementMappingData & element_data ,
mfem : : ElementTransformation & transformation ,
const mfem : : IntegrationPoint & integration_point ,
Workspace & workspace ,
MappingPointContext & context
) const {
ValidateElementData ( element_data ) ;
if ( workspace . GetDimension ( ) ! = m_options . dimension )
throw std : : invalid_argument ( " The mapping workspace has the wrong dimension. " ) ;
if ( transformation . GetSpaceDim ( ) ! = m_options . dimension )
throw std : : invalid_argument ( " The element transformation has the wrong spatial dimension. " ) ;
if ( transformation . GetGeometryType ( ) ! = element_data . displacement . GetElement ( ) . GetGeomType ( ) )
throw std : : invalid_argument (
" The element transformation geometry does not match the "
" supplied "
" element data. "
) ;
transformation . SetIntPoint ( & integration_point ) ;
context . reference_position . SetSize ( m_options . dimension ) ;
transformation . Transform ( integration_point , context . reference_position ) ;
2026-09-10 06:50:56 -04:00
// Get the displacement field value and its Jacobian at the integration point. Note these are in the workspace to avoid repeated allocations.
2026-08-30 16:41:14 -04:00
EvaluateField (
element_data . displacement , transformation , integration_point , workspace , workspace . m_field_value ,
2026-09-02 17:01:50 -04:00
workspace . m_field_jacobian , nullptr
2026-08-30 16:41:14 -04:00
) ;
if ( ! vector_is_finite ( context . reference_position ) | | ! vector_is_finite ( workspace . m_field_value ) | |
! matrix_is_finite ( workspace . m_field_jacobian ) ) {
return MappingStatus : : non_finite_input ;
}
context . displaced_position . SetSize ( m_options . dimension ) ;
2026-09-10 06:50:56 -04:00
// Get the position of the point in physical space by adding the displacement to the reference position. Note MFEM really dislikes raw arithmetic operators
// so we need to first assign the reference position then use the in place += operator.
2026-08-30 16:41:14 -04:00
context . displaced_position = context . reference_position ;
context . displaced_position + = workspace . m_field_value ;
context . displacement_jacobian . SetSize ( m_options . dimension , m_options . dimension ) ;
context . displacement_jacobian = workspace . m_field_jacobian ;
2026-09-10 06:50:56 -04:00
// Ensure that the diagonal of the displacement Jacobian is incremented by 1.0 to account for the identity mapping from reference to physical space.
// recall that r = x + d (where d is the workspace.m_field_value and x is context.reference_position) then we can differentiate this
// component wise to find the gradient of the displaced position wrt. the mesh coordinate (reference position). E.g as you move along
// the mesh coordinate how much does the physical coordinate change and in what direction. Lets call this F
// F = \frac{\partial r_i}{\partial x_j} where r is the displaced position and x is the mesh position.
// We then have F = \frac{\partial x_i}{\partial x_j} + \frac{\partial d_i}{x_j} where d is the displacement (recall r = x + d)
// By definition the first term is the identity matrix. The second term we get out of EvaluateField. Thus why we need to add the identity matrix here
2026-08-30 16:41:14 -04:00
for ( int i = 0 ; i < m_options . dimension ; + + i )
context . displacement_jacobian ( i , i ) + = 1.0 ;
context . compactified = IsCompactifiedElement ( transformation ) ;
2026-09-10 06:50:56 -04:00
// This branch only runs for vacuum elements
2026-08-30 16:41:14 -04:00
if ( context . compactified ) {
2026-09-10 06:50:56 -04:00
// There are two things that we need to the mapping. First is a reference coordinate which stroid embeds into the mesh at mesh generation time, this is
// parameterized from 0 - 1 where 0 is the model surface and 1 is the mesh exterior (what will becomes the compactified infinity, note also we never actually evaluate at s=1; rather we define some arbitrary small tolerance to approach s=1). Lets call this s. We also need
// the gradient of s as we move along the mesh coordinates. All of this is stashes within workspace.m_compactification_point.
2026-08-30 16:41:14 -04:00
const MappingStatus coordinate_status = EvaluateCompactificationCoordinate (
element_data . compactification , transformation , integration_point , workspace ,
2026-09-02 17:01:50 -04:00
workspace . m_compactification_point , nullptr
2026-08-30 16:41:14 -04:00
) ;
if ( coordinate_status ! = MappingStatus : : valid )
return coordinate_status ;
const compactification : : ExteriorMapInput exterior_input {
. reference_position = context . reference_position ,
. displaced_position = context . displaced_position ,
. displacement_jacobian = context . displacement_jacobian ,
. compactification_coordinate = workspace . m_compactification_point . coordinate ,
. compactification_coordinate_gradient = workspace . m_compactification_point . coordinate_gradient
} ;
2026-09-10 06:50:56 -04:00
// This apply whatever the exterior map is to generate the new physical exterior coordinate and jacobian between physical and reference space.
// In general we have only implemented a kelvin mapping; however, in future additional mappings may be implemented.
2026-08-30 16:41:14 -04:00
const MappingStatus exterior_status = m_exterior_map - > Evaluate ( exterior_input , workspace . m_exterior_result ) ;
if ( exterior_status ! = MappingStatus : : valid )
return exterior_status ;
context . physical_position = workspace . m_exterior_result . physical_position ;
context . mapping_jacobian = workspace . m_exterior_result . mapping_jacobian ;
} else {
context . physical_position = context . displaced_position ;
context . mapping_jacobian = context . displacement_jacobian ;
}
2026-09-10 06:50:56 -04:00
// Validation work
2026-08-30 16:41:14 -04:00
if ( ! vector_is_finite ( context . physical_position ) | | ! matrix_is_finite ( context . mapping_jacobian ) )
return MappingStatus : : non_finite_result ;
context . mapping_determinant = context . mapping_jacobian . Det ( ) ;
if ( ! std : : isfinite ( context . mapping_determinant ) )
return MappingStatus : : non_finite_result ;
if ( context . mapping_determinant < = 0.0 )
2026-09-10 06:50:56 -04:00
// This is the most common error we see come out of this function, specifically it is common when we try to deform the mesh too much in one step.
2026-08-30 16:41:14 -04:00
return MappingStatus : : non_positive_determinant ;
context . inverse_mapping_jacobian . SetSize ( m_options . dimension , m_options . dimension ) ;
2026-09-10 06:50:56 -04:00
// It can be useful to have the inverse jacobian, here we just use MFEM's build in inverse tooling.
2026-08-30 16:41:14 -04:00
mfem : : CalcInverse ( context . mapping_jacobian , context . inverse_mapping_jacobian ) ;
if ( ! matrix_is_finite ( context . inverse_mapping_jacobian ) )
return MappingStatus : : non_finite_result ;
return MappingStatus : : valid ;
}
MappingStatus DomainMapper : : EvaluateVolume (
const ElementMappingData & element_data ,
mfem : : ElementTransformation & transformation ,
const mfem : : IntegrationPoint & integration_point ,
Workspace & workspace ,
VolumeMappingContext & context
) const {
const MappingStatus point_status =
EvaluatePoint ( element_data , transformation , integration_point , workspace , context . mapping ) ;
if ( point_status ! = MappingStatus : : valid )
return point_status ;
mfem : : Mult ( context . mapping . mapping_jacobian , transformation . Jacobian ( ) , workspace . m_full_element_jacobian ) ;
context . quadrature . J_inv . SetSize ( m_options . dimension , m_options . dimension ) ;
mfem : : CalcInverse ( workspace . m_full_element_jacobian , context . quadrature . J_inv ) ;
context . quadrature . detJ = context . mapping . mapping_determinant ;
context . quadrature . weight =
integration_point . weight * transformation . Weight ( ) * context . mapping . mapping_determinant ;
if ( ! matrix_is_finite ( context . quadrature . J_inv ) | | ! std : : isfinite ( context . quadrature . weight ) )
return MappingStatus : : non_finite_result ;
if ( context . quadrature . weight < = 0.0 )
return MappingStatus : : non_positive_determinant ;
return MappingStatus : : valid ;
}
mfem : : ElementTransformation & DomainMapper : : SelectFaceElementTransformation (
mfem : : FaceElementTransformations & transformation ,
const FaceElementSide side
) {
if ( side = = FaceElementSide : : element_1 ) {
MFEM_VERIFY ( transformation . Elem1 ! = nullptr , " The face does not have an element-1 transformation. " ) ;
return * transformation . Elem1 ;
}
MFEM_VERIFY ( transformation . Elem2 ! = nullptr , " The face does not have an element-2 transformation. " ) ;
return * transformation . Elem2 ;
}
const mfem : : IntegrationPoint & DomainMapper : : SelectFaceElementIntegrationPoint (
mfem : : FaceElementTransformations & transformation ,
const FaceElementSide side
) {
mfem : : ElementTransformation & element_transformation = SelectFaceElementTransformation ( transformation , side ) ;
return element_transformation . GetIntPoint ( ) ;
}
MappingStatus DomainMapper : : EvaluateFace (
const ElementMappingData & element_data ,
mfem : : FaceElementTransformations & transformation ,
const FaceElementSide side ,
const mfem : : IntegrationPoint & integration_point ,
Workspace & workspace ,
FaceMappingContext & context
) const {
transformation . SetAllIntPoints ( & integration_point ) ;
mfem : : ElementTransformation & element_transformation = SelectFaceElementTransformation ( transformation , side ) ;
const mfem : : IntegrationPoint & element_integration_point =
SelectFaceElementIntegrationPoint ( transformation , side ) ;
const MappingStatus point_status =
EvaluatePoint ( element_data , element_transformation , element_integration_point , workspace , context . mapping ) ;
if ( point_status ! = MappingStatus : : valid )
return point_status ;
workspace . m_reference_normal . SetSize ( m_options . dimension ) ;
mfem : : CalcOrtho ( transformation . Jacobian ( ) , workspace . m_reference_normal ) ;
if ( side = = FaceElementSide : : element_2 )
workspace . m_reference_normal * = - 1.0 ;
const double reference_normal_magnitude = workspace . m_reference_normal . Norml2 ( ) ;
if ( ! std : : isfinite ( reference_normal_magnitude ) | | reference_normal_magnitude < = 0.0 )
return MappingStatus : : non_finite_result ;
context . reference_normal . SetSize ( m_options . dimension ) ;
context . reference_normal = workspace . m_reference_normal ;
context . reference_normal / = reference_normal_magnitude ;
context . mapping . inverse_mapping_jacobian . MultTranspose ( workspace . m_reference_normal , workspace . m_mapped_normal ) ;
workspace . m_mapped_normal * = context . mapping . mapping_determinant ;
const double mapped_normal_magnitude = workspace . m_mapped_normal . Norml2 ( ) ;
if ( ! std : : isfinite ( mapped_normal_magnitude ) | | mapped_normal_magnitude < = 0.0 )
return MappingStatus : : non_finite_result ;
context . quadrature . normal . SetSize ( m_options . dimension ) ;
context . quadrature . normal = workspace . m_mapped_normal ;
context . quadrature . normal / = mapped_normal_magnitude ;
context . reference_surface_weight = integration_point . weight * reference_normal_magnitude ;
context . physical_surface_weight = integration_point . weight * mapped_normal_magnitude ;
context . quadrature . ds = context . reference_surface_weight ;
context . quadrature . v_dot_n_scale = mapped_normal_magnitude / reference_normal_magnitude ;
if ( ! vector_is_finite ( context . quadrature . normal ) | | ! std : : isfinite ( context . reference_surface_weight ) | |
! std : : isfinite ( context . physical_surface_weight ) | | ! std : : isfinite ( context . quadrature . v_dot_n_scale ) ) {
return MappingStatus : : non_finite_result ;
}
return MappingStatus : : valid ;
}
MappingStatus DomainMapper : : EvaluatePointVariation (
const ElementMappingData & element_data ,
const ElementDisplacementData & direction ,
mfem : : ElementTransformation & transformation ,
const mfem : : IntegrationPoint & integration_point ,
const MappingPointContext & base_context ,
Workspace & workspace ,
MappingPointVariation & variation
2026-09-02 17:01:50 -04:00
) const {
return EvaluatePointVariationImpl (
element_data , direction , transformation , integration_point , base_context , workspace , variation , nullptr
) ;
}
MappingStatus DomainMapper : : EvaluatePointVariationImpl (
const ElementMappingData & element_data ,
const ElementDisplacementData & direction ,
mfem : : ElementTransformation & transformation ,
const mfem : : IntegrationPoint & integration_point ,
const MappingPointContext & base_context ,
Workspace & workspace ,
MappingPointVariation & variation ,
const mfem : : DenseMatrix * inverse_mesh_jacobian
2026-08-30 16:41:14 -04:00
) const {
ValidateElementData ( element_data ) ;
const ElementMappingData direction_data {
. displacement = direction , . compactification = element_data . compactification
} ;
ValidateElementData ( direction_data ) ;
if ( element_data . displacement . GetDofCount ( ) ! = direction . GetDofCount ( ) )
throw std : : invalid_argument (
" The displacement and direction elements have different "
" degree-of-freedom counts. "
) ;
if ( workspace . GetDimension ( ) ! = m_options . dimension )
throw std : : invalid_argument ( " The mapping workspace has the wrong dimension. " ) ;
if ( base_context . compactified ! = IsCompactifiedElement ( transformation ) )
throw std : : invalid_argument (
" The base mapping context does not match the current element "
" domain. "
) ;
2026-09-02 17:01:50 -04:00
if ( inverse_mesh_jacobian = = nullptr ) {
transformation . SetIntPoint ( & integration_point ) ;
}
2026-08-30 16:41:14 -04:00
EvaluateField (
2026-09-02 17:01:50 -04:00
direction , transformation , integration_point , workspace , workspace . m_field_value ,
workspace . m_field_jacobian , inverse_mesh_jacobian
2026-08-30 16:41:14 -04:00
) ;
if ( ! vector_is_finite ( workspace . m_field_value ) | | ! matrix_is_finite ( workspace . m_field_jacobian ) )
return MappingStatus : : non_finite_input ;
variation . displacement_variation = workspace . m_field_value ;
variation . displacement_jacobian_variation = workspace . m_field_jacobian ;
if ( base_context . compactified ) {
const MappingStatus coordinate_status = EvaluateCompactificationCoordinate (
element_data . compactification , transformation , integration_point , workspace ,
2026-09-02 17:01:50 -04:00
workspace . m_compactification_point , inverse_mesh_jacobian
2026-08-30 16:41:14 -04:00
) ;
if ( coordinate_status ! = MappingStatus : : valid )
return coordinate_status ;
const compactification : : ExteriorMapInput exterior_input {
. reference_position = base_context . reference_position ,
. displaced_position = base_context . displaced_position ,
. displacement_jacobian = base_context . displacement_jacobian ,
. compactification_coordinate = workspace . m_compactification_point . coordinate ,
. compactification_coordinate_gradient = workspace . m_compactification_point . coordinate_gradient
} ;
workspace . m_exterior_result . physical_position = base_context . physical_position ;
workspace . m_exterior_result . mapping_jacobian = base_context . mapping_jacobian ;
const compactification : : ExteriorMapDirection exterior_direction {
. displaced_position_variation = variation . displacement_variation ,
. displacement_jacobian_variation = variation . displacement_jacobian_variation
} ;
// ReSharper disable once CppTooWideScopeInitStatement
const MappingStatus exterior_status = m_exterior_map - > EvaluateVariation (
exterior_input , workspace . m_exterior_result , exterior_direction , workspace . m_exterior_variation
) ;
if ( exterior_status ! = MappingStatus : : valid ) {
return exterior_status ;
}
variation . physical_position_variation = workspace . m_exterior_variation . physical_position_variation ;
variation . mapping_jacobian_variation = workspace . m_exterior_variation . mapping_jacobian_variation ;
} else {
variation . physical_position_variation = variation . displacement_variation ;
variation . mapping_jacobian_variation = variation . displacement_jacobian_variation ;
}
mfem : : Mult (
base_context . inverse_mapping_jacobian , variation . mapping_jacobian_variation , workspace . m_matrix_temp_1
) ;
double trace = 0.0 ;
for ( int i = 0 ; i < m_options . dimension ; + + i )
trace + = workspace . m_matrix_temp_1 ( i , i ) ;
variation . mapping_determinant_variation = base_context . mapping_determinant * trace ;
variation . inverse_mapping_jacobian_variation . SetSize ( m_options . dimension , m_options . dimension ) ;
mfem : : Mult (
workspace . m_matrix_temp_1 , base_context . inverse_mapping_jacobian ,
variation . inverse_mapping_jacobian_variation
) ;
variation . inverse_mapping_jacobian_variation * = - 1.0 ;
if ( ! vector_is_finite ( variation . physical_position_variation ) | |
! matrix_is_finite ( variation . mapping_jacobian_variation ) | |
! matrix_is_finite ( variation . inverse_mapping_jacobian_variation ) | |
! std : : isfinite ( variation . mapping_determinant_variation ) ) {
return MappingStatus : : non_finite_result ;
}
return MappingStatus : : valid ;
}
MappingStatus DomainMapper : : EvaluateVolumeVariation (
const ElementMappingData & element_data ,
const ElementDisplacementData & direction ,
mfem : : ElementTransformation & transformation ,
const mfem : : IntegrationPoint & integration_point ,
const VolumeMappingContext & base_context ,
Workspace & workspace ,
VolumeMappingVariation & variation
) const {
2026-09-02 17:01:50 -04:00
mfem : : Mult ( base_context . quadrature . J_inv , base_context . mapping . mapping_jacobian , workspace . m_matrix_temp_2 ) ;
const MappingStatus point_status = EvaluatePointVariationImpl (
2026-08-30 16:41:14 -04:00
element_data , direction , transformation , integration_point , base_context . mapping , workspace ,
2026-09-02 17:01:50 -04:00
variation . mapping , & workspace . m_matrix_temp_2
2026-08-30 16:41:14 -04:00
) ;
if ( point_status ! = MappingStatus : : valid )
return point_status ;
mfem : : Mult (
2026-09-02 17:01:50 -04:00
base_context . quadrature . J_inv , variation . mapping . mapping_jacobian_variation , workspace . m_matrix_temp_1
2026-08-30 16:41:14 -04:00
) ;
variation . inverse_element_jacobian_variation . SetSize ( m_options . dimension , m_options . dimension ) ;
mfem : : Mult (
2026-09-02 17:01:50 -04:00
workspace . m_matrix_temp_1 , base_context . mapping . inverse_mapping_jacobian ,
variation . inverse_element_jacobian_variation
2026-08-30 16:41:14 -04:00
) ;
variation . inverse_element_jacobian_variation * = - 1.0 ;
2026-09-02 17:01:50 -04:00
variation . weight_variation = base_context . quadrature . weight / base_context . mapping . mapping_determinant *
variation . mapping . mapping_determinant_variation ;
2026-08-30 16:41:14 -04:00
if ( ! matrix_is_finite ( variation . inverse_element_jacobian_variation ) | |
! std : : isfinite ( variation . weight_variation ) )
return MappingStatus : : non_finite_result ;
return MappingStatus : : valid ;
}
MappingStatus DomainMapper : : EvaluateFaceVariation (
const ElementMappingData & element_data ,
const ElementDisplacementData & direction ,
mfem : : FaceElementTransformations & transformation ,
const FaceElementSide side ,
const mfem : : IntegrationPoint & integration_point ,
const FaceMappingContext & base_context ,
Workspace & workspace ,
FaceMappingVariation & variation
) const {
transformation . SetAllIntPoints ( & integration_point ) ;
mfem : : ElementTransformation & element_transformation = SelectFaceElementTransformation ( transformation , side ) ;
const mfem : : IntegrationPoint & element_integration_point =
SelectFaceElementIntegrationPoint ( transformation , side ) ;
const MappingStatus point_status = EvaluatePointVariation (
element_data , direction , element_transformation , element_integration_point , base_context . mapping , workspace ,
variation . mapping
) ;
if ( point_status ! = MappingStatus : : valid )
return point_status ;
workspace . m_reference_normal . SetSize ( m_options . dimension ) ;
mfem : : CalcOrtho ( transformation . Jacobian ( ) , workspace . m_reference_normal ) ;
if ( side = = FaceElementSide : : element_2 )
workspace . m_reference_normal * = - 1.0 ;
const double reference_normal_magnitude = workspace . m_reference_normal . Norml2 ( ) ;
if ( ! std : : isfinite ( reference_normal_magnitude ) | | reference_normal_magnitude < = 0.0 )
return MappingStatus : : non_finite_result ;
base_context . mapping . inverse_mapping_jacobian . MultTranspose (
workspace . m_reference_normal , workspace . m_vector_temp
) ;
workspace . m_mapped_normal = workspace . m_vector_temp ;
workspace . m_mapped_normal * = base_context . mapping . mapping_determinant ;
variation . physical_normal_variation . SetSize ( m_options . dimension ) ;
variation . mapping . inverse_mapping_jacobian_variation . MultTranspose (
workspace . m_reference_normal , variation . physical_normal_variation
) ;
variation . physical_normal_variation * = base_context . mapping . mapping_determinant ;
variation . physical_normal_variation . Add (
variation . mapping . mapping_determinant_variation , workspace . m_vector_temp
) ;
const double mapped_normal_magnitude = workspace . m_mapped_normal . Norml2 ( ) ;
if ( ! std : : isfinite ( mapped_normal_magnitude ) | | mapped_normal_magnitude < = 0.0 )
return MappingStatus : : non_finite_result ;
const double mapped_normal_magnitude_variation =
base_context . quadrature . normal * variation . physical_normal_variation ;
variation . physical_normal_variation . Add ( - mapped_normal_magnitude_variation , base_context . quadrature . normal ) ;
variation . physical_normal_variation / = mapped_normal_magnitude ;
variation . physical_surface_weight_variation = integration_point . weight * mapped_normal_magnitude_variation ;
variation . normal_flux_scale_variation = mapped_normal_magnitude_variation / reference_normal_magnitude ;
if ( ! vector_is_finite ( variation . physical_normal_variation ) | |
! std : : isfinite ( variation . physical_surface_weight_variation ) | |
! std : : isfinite ( variation . normal_flux_scale_variation ) ) {
return MappingStatus : : non_finite_result ;
}
return MappingStatus : : valid ;
}
2026-08-04 14:24:55 -04:00
} // namespace mean_field::mapping