perf(allocations): reduced overall allocations by 95%, increaseed jacobian applicatin by 2x
This commit uses global pre allocated work space to dramatically reduce memory usage and allocation time
This commit is contained in:
@@ -54,6 +54,7 @@ namespace mean_field::mapping::compactification {
|
||||
if (!std::isfinite(compactification_coordinate))
|
||||
return MappingStatus::non_finite_input;
|
||||
|
||||
// How close we will allow the code to get to compactified infinity
|
||||
const double tolerance = m_options.coordinate_tolerance;
|
||||
|
||||
if (compactification_coordinate < -tolerance || compactification_coordinate > 1.0 + tolerance) {
|
||||
@@ -68,13 +69,21 @@ namespace mean_field::mapping::compactification {
|
||||
return MappingStatus::at_compactified_infinity;
|
||||
}
|
||||
|
||||
// Here we need to do some transformations from the options defined on the mesh to useful computational coordinates
|
||||
// r_inf_ref is the computational / reference radius of the infinity surface (the edge of the entire domain) and r_star_ref is the radius of the spherical
|
||||
// stellar model inscribed within. Therefore radial extent is the computational radial distance between the stellar surface and the infinity surface.
|
||||
// Note that this is separate from the parameterize compactification coordinate.
|
||||
const double radial_extent = m_options.r_inf_ref - m_options.r_star_ref;
|
||||
|
||||
// This places us at the correct spot in computational space given the current compactification coordinate. Say you have compactification = 0.5,
|
||||
// an r_star_ref of 2 and a radial extent of 3, this this will place you at 2 + 0.5 * 3 = 3.5 in computational space, which is half way between the stellar surface and the infinity surface.
|
||||
const double computational_radius = m_options.r_star_ref + coordinate * radial_extent;
|
||||
|
||||
if (!std::isfinite(computational_radius) || computational_radius <= 0.0) {
|
||||
return MappingStatus::invalid_reference_radius;
|
||||
}
|
||||
|
||||
// Invert the exterior coordinate so it runs from 0 at the star to 1 at compactified infinity
|
||||
const double one_minus_coordinate = 1.0 - coordinate;
|
||||
const double denominator = computational_radius * one_minus_coordinate;
|
||||
|
||||
@@ -82,6 +91,11 @@ namespace mean_field::mapping::compactification {
|
||||
return MappingStatus::non_finite_result;
|
||||
}
|
||||
|
||||
// The scale here is the factor which stretches the finite computational domain into the infinite physical domain. Properties we need this to have
|
||||
// include that it should go to 1 at the stellar surface and go to infinity at the compactified infinity.
|
||||
// Mathematically this is scale = |r|/|x| where r is the physical radius and x is the computational radius.
|
||||
// Put another way, scale is the ratio of the target physical radius for the current compactification coordinate
|
||||
// to the current mesh radius.
|
||||
const double scale = m_options.r_star_ref / denominator;
|
||||
const double scale_derivative = scale * (1.0 / one_minus_coordinate - radial_extent / computational_radius);
|
||||
|
||||
@@ -119,6 +133,8 @@ namespace mean_field::mapping::compactification {
|
||||
}
|
||||
|
||||
RadialFactors factors;
|
||||
|
||||
// The key radial factors we use are the scale (which stretches the finite computational domain into the infinite physical domain) and the scale derivative (which is used to compute the mapping jacobian).
|
||||
const MappingStatus factor_status = ComputeRadialFactors(input.compactification_coordinate, factors);
|
||||
if (factor_status != MappingStatus::valid)
|
||||
return factor_status;
|
||||
@@ -127,12 +143,21 @@ namespace mean_field::mapping::compactification {
|
||||
result.mapping_jacobian.SetSize(dimension, dimension);
|
||||
|
||||
for (int i = 0; i < dimension; ++i) {
|
||||
// Note how the physical position is just the product of the displaced position and the scale factor.
|
||||
result.physical_position(i) = factors.scale * input.displaced_position(i);
|
||||
|
||||
// The mapping jacobian comes from trivial application of the product rule
|
||||
// recall: r_i = scale * x_i where r is the physical position and x is the displaced position.
|
||||
// then we can differentiate wrt. X_j holding nothing fixed. Note the capital X here, this is the mesh coordinate not the displaced position.
|
||||
// Lets call this jacobian F
|
||||
// F = \frac{\partial r_i}{\partial X_{j}}
|
||||
// F then tells us how the physical position changes as we move along mesh coordinates
|
||||
// Lets then apply this to the function we have for the kelvin compactification
|
||||
// F = scale * \frac{\partial x_i}{\partial X_j} + x_i * \frac{\partial scale}{\partial X_j}
|
||||
// Below you can see the displacement jacobian (\frac{\partial x_i}{\partial X_j}) and the scale derivative (\frac{\partial scale}{\partial X_j}) being applied to compute the mapping jacobian.
|
||||
for (int j = 0; j < dimension; ++j) {
|
||||
const double scale_gradient = factors.scale_derivative * input.compactification_coordinate_gradient(j);
|
||||
result.mapping_jacobian(i, j) =
|
||||
factors.scale * input.displacement_jacobian(i, j) + input.displaced_position(i) * scale_gradient;
|
||||
result.mapping_jacobian(i, j) = factors.scale * input.displacement_jacobian(i, j) + input.displaced_position(i) * scale_gradient;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -167,6 +167,7 @@ namespace mean_field::mapping {
|
||||
|
||||
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);
|
||||
@@ -538,6 +539,10 @@ namespace mean_field::mapping {
|
||||
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,
|
||||
@@ -551,22 +556,30 @@ namespace mean_field::mapping {
|
||||
const mfem::DenseMatrix &dof_matrix = field.GetDofMatrix();
|
||||
|
||||
workspace.m_shape.SetSize(element.GetDof());
|
||||
workspace.m_mesh_dshape.SetSize(element.GetDof(), m_options.dimension);
|
||||
|
||||
element.CalcShape(integration_point, workspace.m_shape);
|
||||
if (inverse_mesh_jacobian != nullptr) {
|
||||
workspace.m_reference_dshape.SetSize(element.GetDof(), m_options.dimension);
|
||||
element.CalcDShape(integration_point, workspace.m_reference_dshape);
|
||||
mfem::Mult(workspace.m_reference_dshape, *inverse_mesh_jacobian, workspace.m_mesh_dshape);
|
||||
} else {
|
||||
element.CalcPhysDShape(transformation, workspace.m_mesh_dshape);
|
||||
}
|
||||
|
||||
value.SetSize(m_options.dimension);
|
||||
dof_matrix.MultTranspose(workspace.m_shape, value);
|
||||
|
||||
jacobian.SetSize(m_options.dimension, m_options.dimension);
|
||||
mfem::MultAtB(dof_matrix, workspace.m_mesh_dshape, jacobian);
|
||||
|
||||
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(
|
||||
@@ -594,6 +607,7 @@ namespace mean_field::mapping {
|
||||
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
|
||||
@@ -605,17 +619,32 @@ namespace mean_field::mapping {
|
||||
}
|
||||
|
||||
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
|
||||
@@ -632,6 +661,8 @@ namespace mean_field::mapping {
|
||||
.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;
|
||||
@@ -643,6 +674,7 @@ namespace mean_field::mapping {
|
||||
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;
|
||||
|
||||
@@ -650,9 +682,12 @@ namespace mean_field::mapping {
|
||||
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))
|
||||
|
||||
128
libmeanfield/impl/mapping/prepared_cache.cpp
Normal file
128
libmeanfield/impl/mapping/prepared_cache.cpp
Normal file
@@ -0,0 +1,128 @@
|
||||
module;
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <mfem.hpp>
|
||||
#include <stdexcept>
|
||||
|
||||
module mean_field;
|
||||
import :mapping.prepared_cache;
|
||||
import :mapping.types;
|
||||
|
||||
namespace mean_field::mapping {
|
||||
namespace {
|
||||
void pack_vector(
|
||||
double *&destination,
|
||||
const mfem::Vector &vector,
|
||||
const int dimension
|
||||
) {
|
||||
if (vector.Size() != dimension)
|
||||
throw std::invalid_argument("Prepared mapping vector dimension mismatch.");
|
||||
std::copy_n(vector.HostRead(), dimension, destination);
|
||||
destination += dimension;
|
||||
}
|
||||
|
||||
void pack_matrix(
|
||||
double *&destination,
|
||||
const mfem::DenseMatrix &matrix,
|
||||
const int dimension
|
||||
) {
|
||||
if (matrix.Height() != dimension || matrix.Width() != dimension)
|
||||
throw std::invalid_argument("Prepared mapping matrix dimension mismatch.");
|
||||
std::copy_n(matrix.HostRead(), dimension * dimension, destination);
|
||||
destination += dimension * dimension;
|
||||
}
|
||||
|
||||
void unpack_vector(
|
||||
const double *&source,
|
||||
mfem::Vector &vector,
|
||||
const int dimension
|
||||
) {
|
||||
vector.SetSize(dimension);
|
||||
std::copy_n(source, dimension, vector.HostWrite());
|
||||
source += dimension;
|
||||
}
|
||||
|
||||
void unpack_matrix(
|
||||
const double *&source,
|
||||
mfem::DenseMatrix &matrix,
|
||||
const int dimension
|
||||
) {
|
||||
matrix.SetSize(dimension);
|
||||
std::copy_n(source, dimension * dimension, matrix.HostWrite());
|
||||
source += dimension * dimension;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void VolumeMappingCache::SetSize(
|
||||
const int point_count,
|
||||
const int dimension
|
||||
) {
|
||||
if (point_count < 0 || dimension < 1 || dimension > 3)
|
||||
throw std::invalid_argument("Prepared mapping storage requires nonnegative point count and dimension 1-3.");
|
||||
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;
|
||||
}
|
||||
|
||||
const double *VolumeMappingCache::GetPointData(const int point) const {
|
||||
if (point < 0 || point >= m_point_count)
|
||||
throw std::out_of_range("Prepared mapping quadrature point is out of range.");
|
||||
return m_data.data() + static_cast<std::size_t>(point) * m_point_stride;
|
||||
}
|
||||
|
||||
void VolumeMappingCache::Store(
|
||||
const int point,
|
||||
const VolumeMappingContext &context
|
||||
) {
|
||||
// Validate the index through the same checked accessor used by readers.
|
||||
(void)GetPointData(point);
|
||||
double *data = m_data.data() + static_cast<std::size_t>(point) * m_point_stride;
|
||||
pack_vector(data, context.mapping.reference_position, m_dimension);
|
||||
pack_vector(data, context.mapping.displaced_position, m_dimension);
|
||||
pack_vector(data, context.mapping.physical_position, m_dimension);
|
||||
pack_matrix(data, context.mapping.displacement_jacobian, m_dimension);
|
||||
pack_matrix(data, context.mapping.mapping_jacobian, m_dimension);
|
||||
pack_matrix(data, context.mapping.inverse_mapping_jacobian, m_dimension);
|
||||
pack_matrix(data, context.quadrature.J_inv, m_dimension);
|
||||
*data++ = context.mapping.mapping_determinant;
|
||||
*data++ = context.mapping.compactified ? 1.0 : 0.0;
|
||||
*data++ = context.quadrature.detJ;
|
||||
*data = context.quadrature.weight;
|
||||
}
|
||||
|
||||
void VolumeMappingCache::Load(
|
||||
const int point,
|
||||
VolumeMappingContext &context
|
||||
) const {
|
||||
const double *data = GetPointData(point);
|
||||
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_inv, m_dimension);
|
||||
context.mapping.mapping_determinant = *data++;
|
||||
context.mapping.compactified = *data++ != 0.0;
|
||||
context.quadrature.detJ = *data++;
|
||||
context.quadrature.weight = *data;
|
||||
}
|
||||
|
||||
void VolumeMappingCache::LoadInverseJacobian(
|
||||
const int point,
|
||||
mfem::DenseMatrix &inverse
|
||||
) const {
|
||||
const double *data = GetPointData(point) + 3 * m_dimension + 3 * m_dimension * m_dimension;
|
||||
unpack_matrix(data, inverse, m_dimension);
|
||||
}
|
||||
|
||||
int VolumeMappingCache::GetPointCount() const {
|
||||
return m_point_count;
|
||||
}
|
||||
int VolumeMappingCache::GetDimension() const {
|
||||
return m_dimension;
|
||||
}
|
||||
} // namespace mean_field::mapping
|
||||
Reference in New Issue
Block a user