feat(mapper): finished porting over mapper

This commit is contained in:
2026-09-23 09:29:05 -04:00
parent 9e0e1f5c18
commit 52c42ec7cd
21 changed files with 1414 additions and 30 deletions

View File

@@ -0,0 +1,22 @@
#include "serif/mapping/coefficients.hpp"
#include "serif/utils/error/errors.hpp"
namespace serif::mapping {
using namespace serif::utils::errors;
PhysicalPositionFunctionCoefficient::PhysicalPositionFunctionCoefficient(
const DomainMapper &mapper,
const mfem::GridFunction &displacement,
const mfem::GridFunction &compactification_coordinate,
Func func
) :
m_f(std::move(func)),
m_mapping(mapper, displacement, compactification_coordinate) { }
double PhysicalPositionFunctionCoefficient::Eval(mfem::ElementTransformation &transformation, const mfem::IntegrationPoint &ip) {
transformation.SetIntPoint(&ip);
MappingPointContext context;
serif_ensure<SERiFErrorCode::INVALID_MAPPING>(m_mapping.evaluate_point(transformation, ip, context) == MappingStatus::VALID, "Mapping evaluation failed during PhysicalPositionCoefficient evaluation.");
return m_f(context.physical_position);
}
}

View File

@@ -203,6 +203,10 @@ namespace serif::mapping::compactification {
return MappingStatus::VALID;
}
std::unique_ptr<const ExteriorDomainMap> KelvinCompactification::clone() const {
return std::make_unique<const KelvinCompactification>(m_options);
}
MappingStatus KelvinCompactification::compute_radial_factors(double compactification_coordinate, RadialFactors& radial_factors) const {
if (!std::isfinite(compactification_coordinate)) {
return MappingStatus::NON_FINITE_MAPPING_INPUT;

View File

@@ -88,7 +88,7 @@ namespace serif::mapping {
}
if (element.GetDerivType() != mfem::FiniteElement::GRAD) {
serif_error<std::invalid_argument, SERiFErrorCode::INVALID_COMPACTIFICATION_DERIVATIVE_VALUE>("Compactification coordinate finite element must provide a gradient");
serif_error<std::invalid_argument, SERiFErrorCode::INVALID_COMPACTIFICATION_DERIVATIVE_TYPE>("Compactification coordinate finite element must provide a gradient");
}
if (element.GetDof() <= 0) {

View File

@@ -1,10 +1,51 @@
#include "mfem/linalg/densemat.hpp"
#include "serif/mapping/domain_mapper.hpp"
#include "serif/utils/error/errors.hpp"
#include "serif/utils/checks/finite.hpp"
#include <array>
#include <tuple>
// Anonymous namespace for translation unit-local helper functions and types
namespace {
// TODO: Put something in here using variadic args to validate multiple things satisfy some predicate
}
namespace serif::mapping {
using namespace serif::utils::errors;
using namespace serif::utils::checks;
DomainMapper::DomainMapper(
utils::user_options::DomainMapperOptions options,
std::unique_ptr<const compactification::ExteriorDomainMap> exterior_map
) :
m_geometryCache(std::make_unique<DMGeometryCache>()),
m_options(options),
m_exterior_map(std::move(exterior_map)) {
if (m_options.dimension <= 0) {
serif_error<std::invalid_argument, SERiFErrorCode::INVALID_DIMENSION>("Domain Mapper requires a positive, non-zero, dimension. Received dimension: {}", m_options.dimension);
}
if (m_options.vacuum_element_attribute_ID <= 0) {
serif_error<std::invalid_argument, SERiFErrorCode::INVALID_VACUUM_ELEMENT_ATTRIBUTE_ID>("Domain Mapper requires a positive, non-zero, vacuum element attribute ID. Received attribute ID: {}", m_options.vacuum_element_attribute_ID);
}
if (!m_exterior_map) {
serif_error<std::invalid_argument, SERiFErrorCode::MISSING_EXTERIOR_MAP>("Domain Mapper requires a valid exterior map. One was not provided or a nullptr was provided.");
}
}
std::unique_ptr<DomainMapper> DomainMapper::clone() const {
if (!m_exterior_map) {
serif_error<std::logic_error, SERiFErrorCode::MISSING_EXTERIOR_MAP>("Domain Mapper clone failed because the exterior map is missing or a nullptr.");
}
auto result = std::make_unique<DomainMapper>(m_options, m_exterior_map->clone());
result->set_geometry_cache_enabled(is_geometry_cache_enabled());
return result;
}
void DomainMapper::set_geometry_cache_enabled(const bool enabled) const {
m_geometryCache->enabled = enabled;
@@ -66,9 +107,624 @@ namespace serif::mapping {
}
const auto key = std::tuple{transformation.ElementNo, coordinates[0], coordinates[1], coordinates[2]};
const auto iter = cache.points.find(key);
const auto found = cache.points.find(key);
if (iter != cache.points.end()) {
++cache.hits;
return &iter->second;
}
DMFixedMeshGeometry geometry;
transformation.Transform(ip, geometry.position);
geometry.jacobian = transformation.Jacobian();
geometry.inverse_jacobian = transformation.InverseJacobian();
geometry.weight = transformation.Weight();
++cache.misses;
if (!is_finite(geometry.position) || !is_finite(geometry.jacobian) || !is_finite(geometry.inverse_jacobian) || !is_finite(geometry.weight)) {
return nullptr;
}
cache.points.emplace(key, std::move(geometry));
return &cache.points.at(key);
}
MappingStatus DomainMapper::evaluate_point(
const ElementMappingData &element_data,
mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &ip,
DMWorkspace &workspace,
MappingPointContext &context
) const {
const auto* meshGeometry = get_fixed_mesh_geometry(transformation, ip);
return evaluate_point_impl(element_data, transformation, ip, workspace, context, meshGeometry);
}
MappingStatus DomainMapper::evaluate_volume(
const ElementMappingData &element_data,
mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &ip,
DMWorkspace &workspace,
VolumeMappingContext &context,
const DMReferenceDisplacementSample *displacement_sample
) const {
const auto *meshGeometry = get_fixed_mesh_geometry(transformation, ip);
const MappingStatus point_status = evaluate_point_impl(
element_data,
transformation,
ip,
workspace,
context.mapping,
meshGeometry,
displacement_sample
);
if (point_status != MappingStatus::VALID) {
return point_status;
}
mfem::Mult(context.mapping.mapping_jacobian,meshGeometry ? meshGeometry->jacobian : transformation.Jacobian(), workspace.m_full_element_jacobian);
context.quadrature.J_element_to_physical_inverse.SetSize(m_options.dimension, m_options.dimension);
mfem::CalcInverse(workspace.m_full_element_jacobian, context.quadrature.J_element_to_physical_inverse);
context.quadrature.mapping_determinate = context.mapping.mapping_determinate;
context.quadrature.physical_integration_weight = ip.weight * (meshGeometry ? meshGeometry->weight : transformation.Weight()) * context.mapping.mapping_determinate;
if (!is_finite(context.quadrature.J_element_to_physical_inverse) || !std::isfinite(context.quadrature.physical_integration_weight)) {
return MappingStatus::NON_FINITE_MAPPING_RESULT;
}
if (context.quadrature.physical_integration_weight <= 0.0) {
return MappingStatus::NON_POSITIVE_DETERMINATE;
}
return MappingStatus::VALID;
}
MappingStatus DomainMapper::evaluate_face(
const ElementMappingData &element_data,
mfem::FaceElementTransformations &transformation,
const FaceElementSide side,
const mfem::IntegrationPoint &ip,
DMWorkspace &workspace,
FaceMappingContext &context
) const {
transformation.SetAllIntPoints(&ip);
mfem::ElementTransformation& element_transformation = select_face_element_transformation(transformation, side);
const mfem::IntegrationPoint& element_integration_point = select_face_element_integration_point(transformation, side);
const MappingStatus point_status = evaluate_point(element_data, element_transformation, element_integration_point, workspace, context.mapping);
if (point_status != MappingStatus::VALID) {
return point_status;
}
// Get the normal vector from the face in reference space
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; // Flip the normal for the second element (e.g. the "other" side of the face)
}
const double reference_normal_magnitude = workspace.m_reference_normal.Norml2();
if (!is_finite(reference_normal_magnitude) || reference_normal_magnitude <= 0.0) {
return MappingStatus::NON_FINITE_MAPPING_RESULT;
}
context.reference_normal.SetSize(m_options.dimension);
context.reference_normal = workspace.m_reference_normal;
context.reference_normal /= reference_normal_magnitude;
// Get the normal vector from the face in physical space
context.mapping.inverse_mapping_jacobian.MultTranspose(workspace.m_reference_normal, workspace.m_mapped_normal);
workspace.m_mapped_normal *= context.mapping.mapping_determinate;
const double mapped_normal_magnitude = workspace.m_mapped_normal.Norml2();
if (!is_finite(mapped_normal_magnitude) || mapped_normal_magnitude <= 0.0) {
return MappingStatus::NON_FINITE_MAPPING_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 = ip.weight * reference_normal_magnitude;
context.physical_surface_weight = ip.weight * mapped_normal_magnitude;
context.quadrature.ds = context.reference_surface_weight;
context.quadrature.v_dot_n_scale = mapped_normal_magnitude / reference_normal_magnitude;
if (!is_finite(context.quadrature.normal) || !is_finite(context.reference_surface_weight) || !is_finite(context.physical_surface_weight) || !is_finite(context.quadrature.v_dot_n_scale)) {
return MappingStatus::NON_FINITE_MAPPING_RESULT;
}
return MappingStatus::VALID;
}
MappingStatus DomainMapper::evaluate_point_variation(
const ElementMappingData &element_data,
const ElementDisplacementData &direction,
mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &ip,
const MappingPointContext &context,
DMWorkspace &workspace,
MappingPointVariation &variation
) const {
return evaluate_point_variation_impl(element_data, direction, transformation, ip, context, workspace, variation, nullptr);
}
MappingStatus DomainMapper::evaluate_volume_variation(
const ElementMappingData &element_data,
const ElementDisplacementData &direction,
mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &ip,
const VolumeMappingContext &context,
DMWorkspace &workspace,
VolumeMappingVariation &variation
) const {
mfem::Mult(context.quadrature.J_element_to_physical_inverse, context.mapping.mapping_jacobian, workspace.m_matrix_temp_2);
const MappingStatus point_status = evaluate_point_variation_impl(element_data, direction, transformation, ip, context.mapping, workspace, variation.mapping, &workspace.m_matrix_temp_2);
if (point_status != MappingStatus::VALID) {
return point_status;
}
mfem::Mult(context.quadrature.J_element_to_physical_inverse, 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, context.mapping.inverse_mapping_jacobian, variation.inverse_element_jacobian_variation);
variation.inverse_element_jacobian_variation *= -1.0; // Account for the negative sign in the derivative of the inverse of a matrix
variation.weight_variation = (context.quadrature.physical_integration_weight / context.mapping.mapping_determinate) * variation.mapping.mapping_determinate_variation;
if (!is_finite(variation.inverse_element_jacobian_variation) || !is_finite(variation.weight_variation)) {
return MappingStatus::NON_FINITE_MAPPING_RESULT;
}
return MappingStatus::VALID;
}
MappingStatus DomainMapper::evaluate_face_variation(
const ElementMappingData &element_data,
const ElementDisplacementData &direction,
mfem::FaceElementTransformations &transformation,
FaceElementSide side,
const mfem::IntegrationPoint &ip,
const FaceMappingContext &context,
DMWorkspace &workspace,
FaceMappingVariation &variation
) const {
transformation.SetAllIntPoints(&ip);
mfem::ElementTransformation &element_transformation = select_face_element_transformation(transformation, side);
const mfem::IntegrationPoint &element_integration_point = select_face_element_integration_point(transformation, side);
const MappingStatus point_status = evaluate_point_variation(element_data, direction, element_transformation, element_integration_point, 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; // Flip the normal for the second element (e.g. the "other" side of the face)
}
const double reference_normal_magnitude = workspace.m_reference_normal.Norml2();
if (!is_finite(reference_normal_magnitude) || reference_normal_magnitude <= 0) {
return MappingStatus::NON_FINITE_MAPPING_RESULT;
}
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 *= context.mapping.mapping_determinate;
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 *= context.mapping.mapping_determinate;
variation.physical_normal_variation.Add(variation.mapping.mapping_determinate_variation, workspace.m_vector_temp);
const double mapped_normal_magnitude = workspace.m_mapped_normal.Norml2();
if (!is_finite(mapped_normal_magnitude) || mapped_normal_magnitude <= 0) {
return MappingStatus::NON_FINITE_MAPPING_RESULT;
}
const double mapped_normal_magnitude_variation = context.quadrature.normal * variation.physical_normal_variation;
variation.physical_normal_variation.Add(-mapped_normal_magnitude_variation, context.quadrature.normal);
variation.physical_normal_variation /= mapped_normal_magnitude;
variation.physical_surface_weight_variation = ip.weight * mapped_normal_magnitude_variation;
variation.normal_flux_scale_variation = mapped_normal_magnitude_variation / reference_normal_magnitude;
if (!is_finite(variation.physical_normal_variation) || !std::isfinite(variation.physical_surface_weight_variation) || !std::isfinite(variation.normal_flux_scale_variation)) {
return MappingStatus::NON_FINITE_MAPPING_RESULT;
}
return MappingStatus::VALID;
}
bool DomainMapper::is_compactified_element(const mfem::ElementTransformation &transformation) const noexcept {
return transformation.Attribute == m_options.vacuum_element_attribute_ID;
}
int DomainMapper::get_dimension() const noexcept {
return m_options.dimension;
}
const compactification::ExteriorDomainMap& DomainMapper::get_exterior_map() const noexcept {
return *m_exterior_map;
}
MappingStatus DomainMapper::evaluate_point_impl(
const ElementMappingData &element_data,
mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &ip,
DMWorkspace &workspace,
MappingPointContext &context,
const DMFixedMeshGeometry *geometry,
const DMReferenceDisplacementSample *displacement_sample
) const {
validate_element_data(element_data);
//TODO: Factor into its own function
//TODO: Consider if these should return or throw.
if (workspace.get_dimension() != m_options.dimension) {
serif_error<std::invalid_argument, SERiFErrorCode::INVALID_DIMENSION>("Workspace dimension does not match Domain Mapper dimension. Workspace dimension: {}, Domain Mapper dimension: {}", workspace.get_dimension(), m_options.dimension);
}
if (transformation.GetSpaceDim() != m_options.dimension) {
serif_error<std::invalid_argument, SERiFErrorCode::INVALID_DIMENSION>("Element transformation space dimension does not match Domain Mapper dimension. Element transformation space dimension: {}, Domain Mapper dimension: {}", transformation.GetSpaceDim(), m_options.dimension);
}
if (transformation.GetGeometryType() != element_data.displacement.get_element().GetGeomType()) {
serif_error<std::invalid_argument, SERiFErrorCode::INVALID_ELEMENT_GEOMETRY>("Element transformation geometry type does not match element geometry type. Element transformation geometry type: {}, Element geometry type: {}", transformation.GetGeometryType(), element_data.displacement.get_element().GetGeomType());
}
context.reference_position.SetSize(m_options.dimension);
if (geometry) {
context.reference_position = geometry->position;
} else {
transformation.Transform(ip, context.reference_position);
}
// Get the displacement field value and its Jacobian at the integration point. Note these may be in current workspace to avoid repeated allocations and Multiplies.
if (displacement_sample) {
// TODO: Generally go through and refactor these large checks into their own functions, at the very least it will be easier to read
if (element_data.displacement.get_element().GetMapType() != mfem::FiniteElement::VALUE || displacement_sample->value.Size() != m_options.dimension || displacement_sample->gradient.Height() != m_options.dimension || displacement_sample->gradient.Width() != m_options.dimension) {
return MappingStatus::INVALID_DIMENSION;
}
workspace.m_field_value = displacement_sample->value;
workspace.m_field_jacobian.SetSize(m_options.dimension);
mfem::Mult(displacement_sample->gradient, geometry ? geometry->inverse_jacobian : transformation.InverseJacobian(), workspace.m_field_jacobian);
} else {
evaluate_displacement_field(element_data.displacement, transformation, ip, workspace, workspace.m_field_value, workspace.m_field_jacobian, geometry ? &geometry->inverse_jacobian : nullptr);
}
if (!is_finite(context.reference_position) || !is_finite(workspace.m_field_value) || !is_finite(workspace.m_field_jacobian)) {
return MappingStatus::NON_FINITE_MAPPING_RESULT;
}
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 = is_compactified_element(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 = evaluate_compactification_coordinate(element_data.compactification, transformation, ip, workspace, workspace.m_compactification_point, geometry ? &geometry->inverse_jacobian : 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_gradient = workspace.m_compactification_point.coordinate_gradient,
.compactification_coordinate = workspace.m_compactification_point.compactification_coordinate,
};
// 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 (!is_finite(context.physical_position) || !is_finite(context.mapping_jacobian)) {
return MappingStatus::NON_FINITE_MAPPING_RESULT;
}
context.mapping_determinate = context.mapping_jacobian.Det();
if (!std::isfinite(context.mapping_determinate)) {
return MappingStatus::NON_FINITE_MAPPING_RESULT;
}
if (context.mapping_determinate <= 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_DETERMINATE;
}
context.inverse_mapping_jacobian.SetSize(m_options.dimension, m_options.dimension);
// Finally we get the inverse jacobian.
mfem::CalcInverse(context.mapping_jacobian, context.inverse_mapping_jacobian);
if (!is_finite(context.inverse_mapping_jacobian)) {
return MappingStatus::NON_FINITE_MAPPING_RESULT;
}
return MappingStatus::VALID;
}
void DomainMapper::validate_element_data(const ElementMappingData &element_data) const {
const ElementDisplacementData &displacement = element_data.displacement;
const ElementCompactificationData &compactification = element_data.compactification;
if (displacement.get_dimension() != m_options.dimension) {
serif_error<std::invalid_argument, SERiFErrorCode::INVALID_DIMENSION>("Element displacement dimension does not match Domain Mapper dimension. Element displacement dimension: {}, Domain Mapper dimension: {}", displacement.get_dimension(), m_options.dimension);
}
if (displacement.get_element().GetDim() != m_options.dimension) {
serif_error<std::invalid_argument, SERiFErrorCode::INVALID_DIMENSION>("Element displacement finite element dimension does not match Domain Mapper dimension. Element displacement finite element dimension: {}, Domain Mapper dimension: {}", displacement.get_element().GetDim(), m_options.dimension);
}
if (compactification.get_element().GetDim() != m_options.dimension) {
serif_error<std::invalid_argument, SERiFErrorCode::INVALID_DIMENSION>("Element compactification finite element dimension does not match Domain Mapper dimension. Element compactification finite element dimension: {}, Domain Mapper dimension: {}", compactification.get_element().GetDim(), m_options.dimension);
}
if (displacement.get_element().GetGeomType() != compactification.get_element().GetGeomType()) {
serif_error<std::invalid_argument, SERiFErrorCode::INVALID_ELEMENT_GEOMETRY>("Element displacement finite element geometry type does not match element compactification finite element geometry type. Element displacement finite element geometry type: {}, Element compactification finite element geometry type: {}", displacement.get_element().GetGeomType(), compactification.get_element().GetGeomType());
}
if (compactification.get_element().GetRangeType() != mfem::FiniteElement::SCALAR) {
serif_error<std::invalid_argument, SERiFErrorCode::INVALID_COMPACTIFICATION_RANK>("Element compactification finite element range type is not scalar. Element compactification finite element range type: {}", compactification.get_element().GetRangeType());
}
if (compactification.get_element().GetMapType() != mfem::FiniteElement::VALUE) {
serif_error<std::invalid_argument, SERiFErrorCode::INVALID_COMPACTIFICATION_TYPE>("Element compactification finite element map type is not value. Element compactification finite element map type: {}", compactification.get_element().GetMapType());
}
if (compactification.get_element().GetDerivType() != mfem::FiniteElement::GRAD) {
serif_error<std::invalid_argument, SERiFErrorCode::INVALID_COMPACTIFICATION_DERIVATIVE_TYPE>("Element compactification finite element derivative type is not gradient. Element compactification finite element derivative type: {}", compactification.get_element().GetDerivType());
}
if (compactification.get_dof_count() != compactification.get_element().GetDof()) {
serif_error<std::invalid_argument, SERiFErrorCode::INVALID_DOF_COUNT>("Element compactification finite element DOF count does not match the number of DOFs in the finite element. Element compactification finite element DOF count: {}, Finite element DOF count: {}", compactification.get_dof_count(), compactification.get_element().GetDof());
}
}
// Note that there is nothing preventing this function from being used with some field other than displacement; perhaps we should tighten this?
void DomainMapper::evaluate_displacement_field(
const ElementDisplacementData &field,
mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &ip,
DMWorkspace &workspace,
mfem::Vector &value,
mfem::DenseMatrix &jacobian,
const mfem::DenseMatrix *inverse_mesh_jacobian
) const {
const mfem::FiniteElement &element = field.get_element();
const mfem::DenseMatrix &dof_matrix = field.get_dof_matrix();
workspace.m_shape.SetSize(element.GetDof());
element.CalcShape(ip, 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(ip, workspace.m_reference_dshape);
// Contract DOFs before applying fixed-mesh geometry.
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::evaluate_compactification_coordinate(
const ElementCompactificationData &compactification,
mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &ip,
DMWorkspace &workspace,
CompactificationPointData &point_data,
const mfem::DenseMatrix *inverse_mesh_jacobian
) const {
const mfem::FiniteElement& element = compactification.get_element();
const mfem::Vector& dofs = compactification.get_dofs();
const int dof_count = element.GetDof();
if (workspace.get_dimension() != 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 < dof_count; ++i) {
if (!is_finite(dofs(i))) {
return MappingStatus::NON_FINITE_MAPPING_INPUT;
}
}
workspace.m_compactification_shape.SetSize(dof_count);
workspace.m_compactification_dshape.SetSize(dof_count, m_options.dimension);
element.CalcShape(ip, workspace.m_compactification_shape);
if (inverse_mesh_jacobian != nullptr) {
workspace.m_reference_dshape.SetSize(dof_count, m_options.dimension);
element.CalcDShape(ip, 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.compactification_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 (!is_finite(point_data.compactification_coordinate)) {
return MappingStatus::NON_FINITE_MAPPING_RESULT;
}
for (int d = 0; d < point_data.coordinate_gradient.Size(); ++d) {
if (!is_finite(point_data.coordinate_gradient(d))) {
return MappingStatus::NON_FINITE_MAPPING_RESULT;
}
}
return MappingStatus::VALID;
}
MappingStatus DomainMapper::evaluate_point_variation_impl(
const ElementMappingData &element_data,
const ElementDisplacementData &direction,
mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &ip,
const MappingPointContext &context,
DMWorkspace &workspace,
MappingPointVariation &variation,
const mfem::DenseMatrix *inverse_mesh_jacobian
) const {
validate_element_data(element_data);
const ElementMappingData direction_data {
.displacement = direction,
.compactification = element_data.compactification
};
validate_element_data(direction_data);
if (element_data.displacement.get_dof_count() != direction.get_dof_count()) {
serif_error<std::invalid_argument, SERiFErrorCode::INVALID_DOF_COUNT>("Element displacement DOF count does not match direction DOF count. Element displacement DOF count: {}, Direction DOF count: {}", element_data.displacement.get_dof_count(), direction.get_dof_count());
}
if (workspace.get_dimension() != m_options.dimension) {
serif_error<std::invalid_argument, SERiFErrorCode::INVALID_DIMENSION>("Workspace dimension does not match Domain Mapper dimension. Workspace dimension: {}, Domain Mapper dimension: {}", workspace.get_dimension(), m_options.dimension);
}
if (context.compactified != is_compactified_element(transformation)) {
serif_error<std::invalid_argument, SERiFErrorCode::COMPACTIFICATION_MISMATCH>("Mapping context compactification status does not match element transformation compactification status. Mapping context compactified: {}, Element transformation compactified: {}", context.compactified, is_compactified_element(transformation));
}
if (inverse_mesh_jacobian == nullptr) {
if (const auto* geometry = get_fixed_mesh_geometry(transformation, ip)) {
inverse_mesh_jacobian = &geometry->inverse_jacobian;
}
}
evaluate_displacement_field(direction, transformation, ip, workspace, workspace.m_field_value, workspace.m_field_jacobian, inverse_mesh_jacobian);
if (!is_finite(workspace.m_field_jacobian) || !is_finite(workspace.m_field_value)) {
return MappingStatus::NON_FINITE_MAPPING_INPUT;
}
variation.displacement_variation = workspace.m_field_value;
variation.displacement_jacobian_variation = workspace.m_field_jacobian;
if (context.compactified) {
const MappingStatus coordinate_status = evaluate_compactification_coordinate(element_data.compactification, transformation, ip, workspace, workspace.m_compactification_point, inverse_mesh_jacobian);
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_gradient = workspace.m_compactification_point.coordinate_gradient,
.compactification_coordinate = workspace.m_compactification_point.compactification_coordinate,
};
workspace.m_exterior_result.physical_position = context.physical_position;
workspace.m_exterior_result.mapping_jacobian = context.mapping_jacobian;
const compactification::ExteriorMapDirection exterior_direction {
.displaced_position_variation = variation.displacement_variation,
.displacement_jacobian_variation = variation.displacement_jacobian_variation
};
const MappingStatus exterior_status = m_exterior_map->evaluate_variation(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(context.inverse_mapping_jacobian, variation.mapping_jacobian_variation, workspace.m_matrix_temp_1);
double trace = 0.0;
for (int dimID = 0; dimID < m_options.dimension; ++dimID) {
trace += workspace.m_matrix_temp_1(dimID, dimID);
}
variation.mapping_determinate_variation = context.mapping_determinate * trace;
variation.inverse_mapping_jacobian_variation.SetSize(m_options.dimension, m_options.dimension);
mfem::Mult(workspace.m_matrix_temp_1, context.inverse_mapping_jacobian, variation.inverse_mapping_jacobian_variation);
variation.inverse_mapping_jacobian_variation *= -1.0;
if (!is_finite(variation.physical_position_variation) || !is_finite(variation.mapping_jacobian_variation) || !std::isfinite(variation.mapping_determinate_variation) || !is_finite(variation.inverse_mapping_jacobian_variation)) {
return MappingStatus::NON_FINITE_MAPPING_RESULT;
}
return MappingStatus::VALID;
}
mfem::ElementTransformation &DomainMapper::select_face_element_transformation(
const mfem::FaceElementTransformations &transformations,
const FaceElementSide side
) {
if (side == FaceElementSide::ELEMENT_1) {
serif_assert<SERiFErrorCode::MISSING_FACE_ELEMENT>(transformations.Elem1 != nullptr, "Face element transformation for ELEMENT_1 is null.");
return *transformations.Elem1;
}
serif_assert<SERiFErrorCode::MISSING_FACE_ELEMENT>(transformations.Elem2 != nullptr, "Face element transformation for ELEMENT_2 is null.");
return *transformations.Elem2;
}
const mfem::IntegrationPoint &DomainMapper::select_face_element_integration_point(
const mfem::FaceElementTransformations &transformations,
const FaceElementSide side
) {
mfem::ElementTransformation& element_transformation = select_face_element_transformation(transformations, side);
return element_transformation.GetIntPoint();
}
}

View File

@@ -0,0 +1,149 @@
#include "serif/mapping/evaluator.hpp"
#include "serif/utils/error/errors.hpp"
namespace serif::mapping {
using namespace utils::errors;
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.get_dimension()) {
serif_assert<SERiFErrorCode::MISSING_DISPLACEMENT_FINITE_ELEMENT_SPACE>(m_displacement_space != nullptr, "Displacement grid function is not associated with a finite-element space.");
serif_assert<SERiFErrorCode::MISSING_DISPLACEMENT_FINITE_ELEMENT_SPACE>(m_compactification_space != nullptr, "Compactification-coordinate grid function is not associated with a finite-element space.");
serif_assert<SERiFErrorCode::MISMATCHED_MESHING_MAPS>(m_displacement_space->GetMesh() == m_compactification_space->GetMesh(), "Displacement and compactification-coordinate grid functions are not associated with the same mesh.");
serif_assert<SERiFErrorCode::INVALID_DIMENSION>(m_displacement.VectorDim() == mapper.get_dimension(), "Displacement grid function vector dimension does not match the mapper dimension. ({} vs. {})", m_displacement.VectorDim(), mapper.get_dimension());
serif_assert<SERiFErrorCode::INVALID_COMPACTIFICATION_RANK>(m_compactification_coordinate.VectorDim() == 1, "Compactification-coordinate grid function must be a scalar field. Instead it has vector dimension {}.", m_compactification_coordinate.VectorDim());
serif_assert<SERiFErrorCode::INVALID_DIMENSION>(m_displacement_space->GetMesh()->SpaceDimension() == mapper.get_dimension(), "Displacement finite-element space mesh dimension does not match the mapper dimension. ({} vs. {})", m_displacement_space->GetMesh()->SpaceDimension(), mapper.get_dimension());
}
void GridFunctionMappingEvaluator::invalidate_cache() noexcept {
m_displacement_data.reset();
m_compactification_data.reset();
m_cached_element_id = -1;
}
void GridFunctionMappingEvaluator::validate_field_bindings() const {
serif_assert<SERiFErrorCode::CACHE_FE_BINDING_ERROR>(m_displacement.FESpace() == m_displacement_space, "Displacement grid function is no longer associated with the cached finite-element space.");
serif_assert<SERiFErrorCode::CACHE_FE_BINDING_ERROR>(m_compactification_coordinate.FESpace() == m_compactification_space, "Compactification-coordinate grid function is no longer associated with the cached finite-element space.");
}
bool GridFunctionMappingEvaluator::invalidate_for_changed_spaces() {
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;
}
invalidate_cache();
m_displacement_space_sequence = displacement_sequence;
m_compactification_space_sequence = compactification_sequence;
return true;
}
void GridFunctionMappingEvaluator::refresh() {
validate_field_bindings();
if (invalidate_for_changed_spaces()) {
return;
}
const int element_id = m_cached_element_id;
invalidate_cache();
if (element_id >= 0) {
load_element(element_id);
}
}
void GridFunctionMappingEvaluator::load_element(const int element_id) {
validate_field_bindings();
(void)invalidate_for_changed_spaces();
if (element_id == m_cached_element_id) {
return;
}
const mfem::FiniteElementSpace& displacement_space = *m_displacement_space;
const mfem::FiniteElementSpace& compactification_space = *m_compactification_space;
serif_ensure<SERiFErrorCode::INVALID_ELEMENT_ID_ERROR>(element_id >= 0, "Element ID must be non-negative. Instead {} was provided.", element_id);
serif_ensure<SERiFErrorCode::INVALID_ELEMENT_ID_ERROR>(element_id < displacement_space.GetMesh()->GetNE(), "Element ID must be less than the number of elements in the mesh ({}). Instead {} was provided.", displacement_space.GetMesh()->GetNE(), 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>(element_displacement_data_from_element_vdofs(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::evaluate_point(mfem::ElementTransformation &transformation, const mfem::IntegrationPoint &integration_point, MappingPointContext &context) {
load_element(transformation.ElementNo);
const ElementMappingData data{
.displacement = *m_displacement_data,
.compactification = *m_compactification_data
};
return m_mapper.evaluate_point(data, transformation, integration_point, m_workspace, context);
}
MappingStatus GridFunctionMappingEvaluator::evaluate_volume(mfem::ElementTransformation &transformation, const mfem::IntegrationPoint &integration_point, VolumeMappingContext &context) {
load_element(transformation.ElementNo);
const ElementMappingData data{
.displacement = *m_displacement_data,
.compactification = *m_compactification_data
};
return m_mapper.evaluate_volume(data, transformation, integration_point, m_workspace, context);
}
MappingStatus GridFunctionMappingEvaluator::evaluate_face(mfem::FaceElementTransformations &transformations, FaceElementSide side, const mfem::IntegrationPoint &integration_point, FaceMappingContext &context) {
mfem::ElementTransformation *transformation = side == FaceElementSide::ELEMENT_1 ? transformations.Elem1 : transformations.Elem2;
serif_ensure<SERiFErrorCode::ELEMENT_NULLPTR_ERROR>(transformation != nullptr, "Face evaluation requested for a side that does not exist.");
load_element(transformation->ElementNo);
const ElementMappingData data{
.displacement = *m_displacement_data,
.compactification = *m_compactification_data
};
return m_mapper.evaluate_face(data, transformations, side, integration_point, m_workspace, context);
}
VolumeQuadratureContext GridFunctionMappingEvaluator::get_quadrature_context(mfem::ElementTransformation &transformation, const mfem::IntegrationPoint &integration_point) {
VolumeMappingContext context;
serif_ensure<SERiFErrorCode::INVALID_DOMAIN_ERROR>(evaluate_volume(transformation, integration_point, context) == MappingStatus::VALID, "Failed to evaluate the mapping at the given volume quadrature point.");
return context.quadrature;
}
FaceQuadratureContext GridFunctionMappingEvaluator::get_face_quadrature_context(mfem::FaceElementTransformations &transformation, const mfem::IntegrationPoint &integration_point, FaceElementSide side) {
FaceMappingContext context;
serif_ensure<SERiFErrorCode::INVALID_DOMAIN_ERROR>(evaluate_face(transformation, side, integration_point, context) == MappingStatus::VALID, "Failed to evaluate the mapping at the given face quadrature point.");
return context.quadrature;
}
void GridFunctionMappingEvaluator::get_physical_point(mfem::ElementTransformation &transformation, const mfem::IntegrationPoint &integration_point, mfem::Vector &physical_position) {
MappingPointContext context;
serif_ensure<SERiFErrorCode::INVALID_DOMAIN_ERROR>(evaluate_point(transformation, integration_point, context) == MappingStatus::VALID, "Failed to evaluate the mapping at the given point.");
physical_position = context.physical_position;
}
}

View File

@@ -0,0 +1,136 @@
#include "serif/mapping/transformations.hpp"
#include "serif/utils/error/errors.hpp"
#include "mfem/linalg/vector.hpp"
#include "mfem/linalg/densemat.hpp"
#include "serif/utils/checks/finite.hpp"
namespace serif::mapping {
using namespace serif::utils::errors;
void map_hdiv_flux_to_physical(const MappingPointContext &context, const mfem::Vector &reference_flux, mfem::Vector &physical_flux) {
serif_assert<SERiFErrorCode::INVALID_DIMENSION>(reference_flux.Size() == context.mapping_jacobian.Width(), "Reference flux vector has incompatible dimension with the mapping jacobian ({} <=> {}).", reference_flux.Size(), context.mapping_jacobian.Width());
physical_flux.SetSize(reference_flux.Size());
context.mapping_jacobian.Mult(reference_flux, physical_flux);
physical_flux /= context.mapping_determinate;
}
void map_physical_flux_to_hdiv_reference(const MappingPointContext &context, const mfem::Vector &physical_flux, mfem::Vector &reference_flux) {
serif_assert<SERiFErrorCode::INVALID_DIMENSION>(physical_flux.Size() == context.inverse_mapping_jacobian.Height(), "Physical flux vector has incompatible dimension with the inverse mapping jacobian ({} <=> {}).", physical_flux.Size(), context.inverse_mapping_jacobian.Height());
reference_flux.SetSize(physical_flux.Size());
context.inverse_mapping_jacobian.Mult(physical_flux, reference_flux);
reference_flux /= context.mapping_determinate;
}
void map_reference_gradient_to_physical(const MappingPointContext &context, const mfem::Vector &reference_gradient, mfem::Vector &physical_gradient) {
serif_assert<SERiFErrorCode::INVALID_DIMENSION>(reference_gradient.Size() == context.inverse_mapping_jacobian.Height(), "Reference gradient vector has incompatible dimension with the inverse mapping jacobian ({} <=> {}).", reference_gradient.Size(), context.inverse_mapping_jacobian.Height());
physical_gradient.SetSize(reference_gradient.Size());
context.inverse_mapping_jacobian.Mult(reference_gradient, physical_gradient);
}
void map_physical_gradient_to_reference(const MappingPointContext &context, const mfem::Vector &physical_gradient, mfem::Vector &reference_gradient) {
serif_assert<SERiFErrorCode::INVALID_DIMENSION>(physical_gradient.Size() == context.mapping_jacobian.Height(), "Physical gradient vector has incompatible dimension with the mapping jacobian ({} <=> {}).", physical_gradient.Size(), context.mapping_jacobian.Height());
reference_gradient.SetSize(physical_gradient.Size());
context.mapping_jacobian.MultTranspose(physical_gradient, reference_gradient);
}
void map_reference_vector_gradient_to_physical(const MappingPointContext &context, const mfem::DenseMatrix &reference_vector_gradient, mfem::DenseMatrix &physical_vector_gradient) {
serif_assert<SERiFErrorCode::INVALID_DIMENSION>(reference_vector_gradient.Width() == context.inverse_mapping_jacobian.Height(), "Reference vector gradient matrix has incompatible dimension with the inverse mapping jacobian ({} <=> {}).", reference_vector_gradient.Width(), context.inverse_mapping_jacobian.Height());
physical_vector_gradient.SetSize(reference_vector_gradient.Height(), context.inverse_mapping_jacobian.Width());
mfem::Mult(reference_vector_gradient, context.inverse_mapping_jacobian, physical_vector_gradient);
}
void map_physical_vector_gradient_to_reference(const MappingPointContext &context, const mfem::DenseMatrix &physical_vector_gradient, mfem::DenseMatrix &reference_vector_gradient) {
serif_assert<SERiFErrorCode::INVALID_DIMENSION>(physical_vector_gradient.Width() == context.mapping_jacobian.Height(), "Physical vector gradient matrix has incompatible dimension with the mapping jacobian ({} <=> {}).", physical_vector_gradient.Width(), context.mapping_jacobian.Height());
reference_vector_gradient.SetSize(physical_vector_gradient.Height(), context.mapping_jacobian.Width());
mfem::Mult(physical_vector_gradient, context.mapping_jacobian, reference_vector_gradient);
}
double map_hdiv_divergence_to_physical(const MappingPointContext &context, double reference_divergence) {
return reference_divergence / context.mapping_determinate;
}
void compute_hdiv_mass_tensor(const MappingPointContext &context, mfem::DenseMatrix &mass_tensor) {
const int dimension = context.mapping_jacobian.Height();
serif_assert<SERiFErrorCode::JACOBIAN_SHAPE_ERROR>((context.mapping_jacobian.Width() == dimension) && (context.mapping_jacobian.Height() == dimension), "Mapping jacobian is not square ({} x {}). It is expected to have shape {}x{}", context.mapping_jacobian.Height(), context.mapping_jacobian.Width(), dimension, dimension);
serif_assert<SERiFErrorCode::INVERTED_JACOBIAN>(context.mapping_determinate > 0.0, "Mapping jacobian determinant is non-positive ({}). The mapping may be inverted or degenerate.", context.mapping_determinate);
mass_tensor.SetSize(dimension, dimension);
mfem::MultAtB(context.mapping_jacobian, context.mapping_jacobian, mass_tensor);
mass_tensor *= 1/context.mapping_determinate;
}
void compute_scalar_diffusion_tensor(const MappingPointContext &context, mfem::DenseMatrix &diffusion_tensor) {
const int dimension = context.mapping_jacobian.Height();
serif_assert<SERiFErrorCode::JACOBIAN_SHAPE_ERROR>((context.inverse_mapping_jacobian.Width() == dimension) && (context.inverse_mapping_jacobian.Height() == dimension), "Inverse mapping jacobian is not square ({} x {}). It is expected to have shape {}x{}", context.inverse_mapping_jacobian.Height(), context.inverse_mapping_jacobian.Width(), dimension, dimension);
serif_assert<SERiFErrorCode::INVERTED_JACOBIAN>(context.mapping_determinate > 0.0, "Mapping jacobian determinant is non-positive ({}). The mapping may be inverted or degenerate.", context.mapping_determinate);
diffusion_tensor.SetSize(dimension, dimension);
mfem::MultAtB(context.inverse_mapping_jacobian, context.inverse_mapping_jacobian, diffusion_tensor);
diffusion_tensor *= context.mapping_determinate;
}
void map_hcurl_field_to_physical(const MappingPointContext &context, const mfem::Vector &reference_field, mfem::Vector &physical_field) {
serif_assert<SERiFErrorCode::INVALID_DIMENSION>(reference_field.Size() == context.inverse_mapping_jacobian.Height(), "Reference field vector has incompatible dimension with the inverse mapping jacobian ({} <=> {}).", reference_field.Size(), context.inverse_mapping_jacobian.Height());
physical_field.SetSize(reference_field.Size());
context.inverse_mapping_jacobian.MultTranspose(reference_field, physical_field);
}
void map_physical_field_to_hcurl_reference(const MappingPointContext &context, const mfem::Vector &physical_field, mfem::Vector &reference_field) {
serif_assert<SERiFErrorCode::INVALID_DIMENSION>(physical_field.Size() == context.mapping_jacobian.Height(), "Physical field vector has incompatible dimension with the mapping jacobian ({} <=> {}).", physical_field.Size(), context.mapping_jacobian.Height());
reference_field.SetSize(physical_field.Size());
context.mapping_jacobian.MultTranspose(physical_field, reference_field);
}
void map_hcurl_curl_to_physical(const MappingPointContext &context, const mfem::Vector &reference_curl, mfem::Vector &physical_curl) {
serif_assert<SERiFErrorCode::INVALID_DIMENSION>(reference_curl.Size() == context.mapping_jacobian.Width(), "Reference curl vector has incompatible dimension with the mapping jacobian ({} <=> {}).", reference_curl.Size(), context.mapping_jacobian.Height());
physical_curl.SetSize(reference_curl.Size());
context.mapping_jacobian.Mult(reference_curl, physical_curl);
physical_curl /= context.mapping_determinate;
}
void map_physical_curl_to_hcurl_reference(const MappingPointContext &context, const mfem::Vector &physical_curl, mfem::Vector &reference_curl) {
serif_assert<SERiFErrorCode::INVALID_DIMENSION>(physical_curl.Size() == context.inverse_mapping_jacobian.Width(), "Physical curl vector has incompatible dimension with the inverse mapping jacobian ({} <=> {}).", physical_curl.Size(), context.inverse_mapping_jacobian.Height());
reference_curl.SetSize(physical_curl.Size());
context.inverse_mapping_jacobian.Mult(physical_curl, reference_curl);
reference_curl *= context.mapping_determinate;
}
void compute_hdiv_mass_tensor_variation(const MappingPointContext &context, const MappingPointVariation &variation, mfem::DenseMatrix &mass_tensor_variation) {
const double determinate = context.mapping_determinate;
const double determinate_variation = variation.mapping_determinate_variation;
const int dimension = context.inverse_mapping_jacobian.Width();
mass_tensor_variation.SetSize(dimension, dimension);
serif_assert<SERiFErrorCode::INVERTED_JACOBIAN>(determinate > 0.0, "Mapping jacobian determinant is non-positive ({}). The mapping may be inverted or degenerate.", determinate);
serif_assert<SERiFErrorCode::NON_FINITE_DETERMINATE>(utils::checks::is_finite(determinate), "Mapping jacobian determinant is non-finite ({}). The mapping may be inverted or degenerate.", determinate);
const mfem::DenseMatrix& jacobian = context.mapping_jacobian;
const mfem::DenseMatrix& jacobian_variation = variation.mapping_jacobian_variation;
const double inverse_determinate = 1.0 / determinate;
const double determinate_scale = determinate_variation * inverse_determinate;
for (int row = 0; row < dimension; ++row) {
for (int col = 0; col < dimension; ++col) {
double gram{0.0};
double gram_variation{0.0};
for (int inner = 0; inner < dimension; ++inner) {
gram += jacobian(inner, row) * jacobian(inner, col);
gram_variation += jacobian(inner, row) * jacobian_variation(inner, col) + jacobian_variation(inner, row) * jacobian(inner, col);
}
mass_tensor_variation(row, col) = inverse_determinate * (gram_variation - determinate_scale * gram);
}
}
}
}

View File

@@ -0,0 +1,112 @@
#include "serif/mapping/volume_cache.hpp"
#include "serif/utils/error/errors.hpp"
#include "mfem/linalg/densemat.hpp"
#include "mfem/linalg/vector.hpp"
namespace serif::mapping {
using namespace utils::errors;
namespace {
// Replace all of these with a fast serialization class at some point. Probably in something like a serif/serialize module
void pack_vector(double*& destination, const mfem::Vector& vector) {
const int dim = vector.Size();
std::copy_n(vector.HostRead(), dim, destination);
destination += dim;
}
void pack_matrix(double*& destination, const mfem::DenseMatrix& matrix) {
serif_assert<SERiFErrorCode::MATRIX_SHAPE_ERROR>(matrix.Height() == matrix.Width(), "Matrix must be square to pack");
const int dim = matrix.Height();
std::copy_n(matrix.HostRead(), dim * dim, destination);
destination += dim * dim;
}
void unpack_vector(const double*& source, mfem::Vector& vector, const int dim) {
vector.SetSize(dim);
std::copy_n(source, dim, vector.HostWrite());
source += dim;
}
void unpack_matrix(const double*& source, mfem::DenseMatrix& matrix, const int dim) {
matrix.SetSize(dim, dim);
std::copy_n(source, dim * dim, matrix.HostWrite());
source += dim * dim;
}
}
void VolumeMappingCache::set_size(int point_count, int dimension) {
serif_assert<SERiFErrorCode::INVALID_DIMENSION>(point_count < 0 || dimension < 1 || dimension > 3, "Invalid point count or dimension for volume mapping cache. Point count: {}, dimension: {}", point_count, dimension);
const int stride = 3 * dimension + 4 * dimension * dimension + 4;
m_data.resize(static_cast<std::size_t>(point_count) * stride);
m_point_count = point_count;
m_dimension = dimension;
m_point_stride = stride;
}
void VolumeMappingCache::validate_point(const int point) const {
serif_assert<SERiFErrorCode::OUT_OF_RANGE_ERROR>(point < 0 || point >= m_point_count, "Point index {} is out of range for volume mapping cache with {} points", point, m_point_count);
}
const double *VolumeMappingCache::get_point_data(const int point) const {
validate_point(point);
return m_data.data() + static_cast<std::size_t>(point) * m_point_stride;
}
void VolumeMappingCache::store(int point, VolumeMappingContext &context) {
validate_point(point);
double *data = m_data.data() + static_cast<std::size_t>(point) * m_point_stride;
// What we build here is a FIFO data structure. we pack
// all the raw data from the mapping context into a single contiguous array of doubles.
// This allows us to avoid repeated allocations as the vector will grow to some appropriate size and then stay roughly there.
pack_vector(data, context.mapping.reference_position);
pack_vector(data, context.mapping.displaced_position);
pack_vector(data, context.mapping.physical_position);
pack_matrix(data, context.mapping.displacement_jacobian);
pack_matrix(data, context.mapping.mapping_jacobian);
pack_matrix(data, context.mapping.inverse_mapping_jacobian);
pack_matrix(data, context.quadrature.J_element_to_physical_inverse);
*data++ = context.mapping.mapping_determinate;
*data++ = context.mapping.compactified ? 1.0 : 0.0; // its possible this could cause issues, have not run into but when unpacked I worry that exact equality to some double value could be a problem. If there are wierd issues this may be a place to look,
*data++ = context.quadrature.mapping_determinate;
*data = context.quadrature.physical_integration_weight;
}
void VolumeMappingCache::load(int point, VolumeMappingContext &context) const {
const double *data = get_point_data(point);
// FIFO data structure so need to unpack in the same order as we packed.
// TODO: Redo this with some sort of fast serialization class so we do not need to worry about ordering
unpack_vector(data, context.mapping.reference_position, m_dimension);
unpack_vector(data, context.mapping.displaced_position, m_dimension);
unpack_vector(data, context.mapping.physical_position, m_dimension);
unpack_matrix(data, context.mapping.displacement_jacobian, m_dimension);
unpack_matrix(data, context.mapping.mapping_jacobian, m_dimension);
unpack_matrix(data, context.mapping.inverse_mapping_jacobian, m_dimension);
unpack_matrix(data, context.quadrature.J_element_to_physical_inverse, m_dimension);
context.mapping.mapping_determinate = *data++;
context.mapping.compactified = *data++ != 0.0; // See comment in store()
context.quadrature.mapping_determinate = *data++;
context.quadrature.physical_integration_weight = *data;
}
void VolumeMappingCache::load_inverse_jacobian(int point, mfem::DenseMatrix &inverse) const {
// Again this is unsafe, and really should be replaced. This was a quick cache scheme that solved the direct issue I had; however, it should be replaced
// with something that has at least a modicum of safety built in (we are pretty good about this in other parts of SERiF but its about 8pm and
// Im quite tired....
const double *data = get_point_data(point) + 3 * m_dimension + 3 * m_dimension * m_dimension; // skip to the inverse jacobian, this is just pointer math...Think back to CS-101. Do that have that in the future, are we still learning about memory like this is a PDP-11?
unpack_matrix(data, inverse, m_dimension);
}
int VolumeMappingCache::get_point_count() const noexcept {
return m_point_count;
}
int VolumeMappingCache::get_dimension() const noexcept {
return m_dimension;
}
}