This commit uses global pre allocated work space to dramatically reduce memory usage and allocation time
1037 lines
46 KiB
C++
1037 lines
46 KiB
C++
module;
|
|
|
|
#include <cmath>
|
|
#include <memory>
|
|
#include <mfem.hpp>
|
|
#include <stdexcept>
|
|
#include <utility>
|
|
|
|
module mean_field;
|
|
import :mapping.types;
|
|
import :mapping.compactification;
|
|
import :utils.user;
|
|
|
|
namespace {
|
|
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;
|
|
}
|
|
} // namespace
|
|
|
|
namespace mean_field::mapping {
|
|
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);
|
|
m_reference_field_jacobian.SetSize(dimension, dimension);
|
|
|
|
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,
|
|
CompactificationPointData &point_data,
|
|
const mfem::DenseMatrix *inverse_mesh_jacobian
|
|
) 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);
|
|
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);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* @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.
|
|
*/
|
|
void DomainMapper::EvaluateField(
|
|
const ElementDisplacementData &field,
|
|
mfem::ElementTransformation &transformation,
|
|
const mfem::IntegrationPoint &integration_point,
|
|
Workspace &workspace,
|
|
mfem::Vector &value,
|
|
mfem::DenseMatrix &jacobian,
|
|
const mfem::DenseMatrix *inverse_mesh_jacobian
|
|
) 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);
|
|
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) {
|
|
workspace.m_reference_dshape.SetSize(element.GetDof(), m_options.dimension);
|
|
element.CalcDShape(integration_point, workspace.m_reference_dshape);
|
|
// 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);
|
|
} else {
|
|
// 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);
|
|
element.CalcPhysDShape(transformation, workspace.m_mesh_dshape);
|
|
mfem::MultAtB(dof_matrix, workspace.m_mesh_dshape, jacobian);
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
// Get the displacement field value and its Jacobian at the integration point. Note these are in the workspace to avoid repeated allocations.
|
|
EvaluateField(
|
|
element_data.displacement, transformation, integration_point, workspace, workspace.m_field_value,
|
|
workspace.m_field_jacobian, nullptr
|
|
);
|
|
|
|
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);
|
|
|
|
// 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.
|
|
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;
|
|
|
|
// 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
|
|
for (int i = 0; i < m_options.dimension; ++i)
|
|
context.displacement_jacobian(i, i) += 1.0;
|
|
|
|
context.compactified = IsCompactifiedElement(transformation);
|
|
|
|
// This branch only runs for vacuum elements
|
|
if (context.compactified) {
|
|
// 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.
|
|
const MappingStatus coordinate_status = EvaluateCompactificationCoordinate(
|
|
element_data.compactification, transformation, integration_point, workspace,
|
|
workspace.m_compactification_point, nullptr
|
|
);
|
|
|
|
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
|
|
};
|
|
|
|
// 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.
|
|
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;
|
|
}
|
|
|
|
// Validation work
|
|
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)
|
|
// 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.
|
|
return MappingStatus::non_positive_determinant;
|
|
|
|
context.inverse_mapping_jacobian.SetSize(m_options.dimension, m_options.dimension);
|
|
|
|
// It can be useful to have the inverse jacobian, here we just use MFEM's build in inverse tooling.
|
|
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
|
|
) 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
|
|
) 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."
|
|
);
|
|
|
|
if (inverse_mesh_jacobian == nullptr) {
|
|
transformation.SetIntPoint(&integration_point);
|
|
}
|
|
|
|
EvaluateField(
|
|
direction, transformation, integration_point, workspace, workspace.m_field_value,
|
|
workspace.m_field_jacobian, inverse_mesh_jacobian
|
|
);
|
|
|
|
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,
|
|
workspace.m_compactification_point, inverse_mesh_jacobian
|
|
);
|
|
|
|
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 {
|
|
mfem::Mult(base_context.quadrature.J_inv, base_context.mapping.mapping_jacobian, workspace.m_matrix_temp_2);
|
|
|
|
const MappingStatus point_status = EvaluatePointVariationImpl(
|
|
element_data, direction, transformation, integration_point, base_context.mapping, workspace,
|
|
variation.mapping, &workspace.m_matrix_temp_2
|
|
);
|
|
if (point_status != MappingStatus::valid)
|
|
return point_status;
|
|
|
|
mfem::Mult(
|
|
base_context.quadrature.J_inv, variation.mapping.mapping_jacobian_variation, workspace.m_matrix_temp_1
|
|
);
|
|
|
|
variation.inverse_element_jacobian_variation.SetSize(m_options.dimension, m_options.dimension);
|
|
mfem::Mult(
|
|
workspace.m_matrix_temp_1, base_context.mapping.inverse_mapping_jacobian,
|
|
variation.inverse_element_jacobian_variation
|
|
);
|
|
variation.inverse_element_jacobian_variation *= -1.0;
|
|
|
|
variation.weight_variation = base_context.quadrature.weight / base_context.mapping.mapping_determinant *
|
|
variation.mapping.mapping_determinant_variation;
|
|
|
|
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;
|
|
}
|
|
} // namespace mean_field::mapping
|