restricted the unknown state vector to surface deformation and implemented one prescription, NodalRadialSurface, while the full volumetric displacment field is reconstructed analytically from that. This reduced the number of degrees of freedom in the system by a factor of 80 while also removing many null vectors from the system.
346 lines
14 KiB
C++
346 lines
14 KiB
C++
module;
|
|
|
|
#include <cmath>
|
|
#include <format>
|
|
#include <stdexcept>
|
|
#include <utility>
|
|
|
|
#include <mfem.hpp>
|
|
|
|
module mean_field;
|
|
|
|
import :deformation.nodal_radial_surface;
|
|
|
|
namespace mean_field::deformation {
|
|
namespace {
|
|
[[nodiscard]] SurfaceDeformationDescriptor nodalRadialDescriptor(const int spatialDimension) noexcept {
|
|
return {
|
|
.name = "NodalRadialSurface",
|
|
.spatialDimension = spatialDimension,
|
|
.motionKind = SurfaceMotionKind::Radial,
|
|
.linearOnReferenceGeometry = true,
|
|
.requiresStarShapedReferenceSurface = true,
|
|
.hasExactDerivativeTranspose = true,
|
|
.hasExactPullbackDerivative = true,
|
|
.translationTreatment = GeometricGaugeTreatment::Retained,
|
|
.orientationTreatment = GeometricGaugeTreatment::Retained
|
|
};
|
|
}
|
|
|
|
void requireFiniteVector(
|
|
const mfem::Vector &vector,
|
|
const char *message
|
|
) {
|
|
for (int index = 0; index < vector.Size(); ++index) {
|
|
if (!std::isfinite(vector(index))) {
|
|
throw std::invalid_argument(message);
|
|
}
|
|
}
|
|
}
|
|
} // namespace
|
|
|
|
SurfaceDeformationCompilationContext::SurfaceDeformationCompilationContext(
|
|
mfem::ParFiniteElementSpace &scalarFiniteElementSpace,
|
|
field::ScalarBoundaryDofMap surfaceDofMap
|
|
)
|
|
: m_scalarFiniteElementSpace(&scalarFiniteElementSpace),
|
|
m_surfaceDofMap(std::move(surfaceDofMap)) {
|
|
if (scalarFiniteElementSpace.Nonconforming()) {
|
|
throw std::invalid_argument(
|
|
"Surface deformation compilation currently requires a conforming scalar finite-element space."
|
|
);
|
|
}
|
|
if (scalarFiniteElementSpace.GetVDim() != 1) {
|
|
throw std::invalid_argument("Surface deformation compilation requires a scalar finite-element space.");
|
|
}
|
|
if (scalarFiniteElementSpace.GetMesh() == nullptr) {
|
|
throw std::invalid_argument("Surface deformation compilation requires a finite-element mesh.");
|
|
}
|
|
if (m_surfaceDofMap.volume_true_dof_size() != scalarFiniteElementSpace.GetTrueVSize()) {
|
|
throw std::invalid_argument(
|
|
"The surface DOF map and scalar finite-element space have incompatible true-DOF sizes."
|
|
);
|
|
}
|
|
if (m_surfaceDofMap.global_size() <= 0) {
|
|
throw std::invalid_argument("Surface deformation compilation requires at least one surface coordinate.");
|
|
}
|
|
}
|
|
|
|
mfem::ParFiniteElementSpace &SurfaceDeformationCompilationContext::scalarFiniteElementSpace() const noexcept {
|
|
return *m_scalarFiniteElementSpace;
|
|
}
|
|
|
|
const field::ScalarBoundaryDofMap &SurfaceDeformationCompilationContext::surfaceDofMap() const noexcept {
|
|
return m_surfaceDofMap;
|
|
}
|
|
|
|
NodalRadialSurface::NodalRadialSurface(mfem::Vector referenceCenter)
|
|
: m_referenceCenter(std::move(referenceCenter)) {
|
|
validate();
|
|
}
|
|
|
|
const mfem::Vector &NodalRadialSurface::referenceCenter() const noexcept {
|
|
return m_referenceCenter;
|
|
}
|
|
|
|
SurfaceDeformationDescriptor NodalRadialSurface::descriptor() const noexcept {
|
|
return nodalRadialDescriptor(m_referenceCenter.Size());
|
|
}
|
|
|
|
void NodalRadialSurface::validate() const {
|
|
if (m_referenceCenter.Size() <= 0) {
|
|
throw std::invalid_argument("NodalRadialSurface requires a non-empty reference center.");
|
|
}
|
|
requireFiniteVector(m_referenceCenter, "NodalRadialSurface reference-center coordinates must be finite.");
|
|
}
|
|
|
|
PreparedNodalRadialSurface::PreparedNodalRadialSurface(
|
|
const SurfaceDeformationDescriptor descriptor,
|
|
mfem::Vector referenceCenter,
|
|
field::ScalarBoundaryDofMap surfaceDofMap,
|
|
mfem::Vector radialDirections,
|
|
mfem::Vector referenceRadii
|
|
)
|
|
: m_descriptor(descriptor),
|
|
m_referenceCenter(std::move(referenceCenter)),
|
|
m_surfaceDofMap(std::move(surfaceDofMap)),
|
|
m_radialDirections(std::move(radialDirections)),
|
|
m_referenceRadii(std::move(referenceRadii)) {
|
|
}
|
|
|
|
SurfaceDeformationDescriptor PreparedNodalRadialSurface::descriptor() const noexcept {
|
|
return m_descriptor;
|
|
}
|
|
|
|
int PreparedNodalRadialSurface::parameterCount() const noexcept {
|
|
return m_surfaceDofMap.local_size();
|
|
}
|
|
|
|
long long PreparedNodalRadialSurface::globalParameterCount() const noexcept {
|
|
return m_surfaceDofMap.global_size();
|
|
}
|
|
|
|
long long PreparedNodalRadialSurface::globalParameterOffset() const noexcept {
|
|
return m_surfaceDofMap.global_offset();
|
|
}
|
|
|
|
int PreparedNodalRadialSurface::spatialDimension() const noexcept {
|
|
return m_descriptor.spatialDimension;
|
|
}
|
|
|
|
int PreparedNodalRadialSurface::surfaceDisplacementSize() const noexcept {
|
|
return spatialDimension() * parameterCount();
|
|
}
|
|
|
|
long long PreparedNodalRadialSurface::globalSurfaceDisplacementSize() const noexcept {
|
|
return static_cast<long long>(spatialDimension()) * globalParameterCount();
|
|
}
|
|
|
|
long long PreparedNodalRadialSurface::globalSurfaceDisplacementOffset() const noexcept {
|
|
return static_cast<long long>(spatialDimension()) * globalParameterOffset();
|
|
}
|
|
|
|
int PreparedNodalRadialSurface::surfaceDisplacementDof(
|
|
const int parameterDof,
|
|
const int component
|
|
) const {
|
|
if (parameterDof < 0 || parameterDof >= parameterCount()) {
|
|
throw std::out_of_range("Parameter DOF is outside PreparedNodalRadialSurface.");
|
|
}
|
|
if (component < 0 || component >= spatialDimension()) {
|
|
throw std::out_of_range("Surface-displacement component is outside PreparedNodalRadialSurface.");
|
|
}
|
|
return spatialDimension() * parameterDof + component;
|
|
}
|
|
|
|
double PreparedNodalRadialSurface::radialDirection(
|
|
const int parameterDof,
|
|
const int component
|
|
) const {
|
|
return m_radialDirections(surfaceDisplacementDof(parameterDof, component));
|
|
}
|
|
|
|
double PreparedNodalRadialSurface::referenceRadius(const int parameterDof) const {
|
|
if (parameterDof < 0 || parameterDof >= parameterCount()) {
|
|
throw std::out_of_range("Parameter DOF is outside PreparedNodalRadialSurface.");
|
|
}
|
|
return m_referenceRadii(parameterDof);
|
|
}
|
|
|
|
const mfem::Vector &PreparedNodalRadialSurface::referenceCenter() const noexcept {
|
|
return m_referenceCenter;
|
|
}
|
|
|
|
const field::ScalarBoundaryDofMap &PreparedNodalRadialSurface::surfaceDofMap() const noexcept {
|
|
return m_surfaceDofMap;
|
|
}
|
|
|
|
void PreparedNodalRadialSurface::buildSurfaceDisplacement(
|
|
const mfem::Vector ¶meters,
|
|
mfem::Vector &surfaceDisplacement
|
|
) const {
|
|
requireParameterSize(parameters);
|
|
requireSurfaceDisplacementSize(surfaceDisplacement);
|
|
|
|
for (int parameterDof = 0; parameterDof < parameterCount(); ++parameterDof) {
|
|
for (int component = 0; component < spatialDimension(); ++component) {
|
|
const int surfaceDof = spatialDimension() * parameterDof + component;
|
|
surfaceDisplacement(surfaceDof) = parameters(parameterDof) * m_radialDirections(surfaceDof);
|
|
}
|
|
}
|
|
}
|
|
|
|
void PreparedNodalRadialSurface::applyJacobian(
|
|
const mfem::Vector ¶meters,
|
|
const mfem::Vector ¶meterDirection,
|
|
mfem::Vector &surfaceDisplacementDirection
|
|
) const {
|
|
requireParameterSize(parameters);
|
|
requireParameterSize(parameterDirection);
|
|
requireSurfaceDisplacementSize(surfaceDisplacementDirection);
|
|
|
|
for (int parameterDof = 0; parameterDof < parameterCount(); ++parameterDof) {
|
|
for (int component = 0; component < spatialDimension(); ++component) {
|
|
const int surfaceDof = spatialDimension() * parameterDof + component;
|
|
surfaceDisplacementDirection(surfaceDof) =
|
|
parameterDirection(parameterDof) * m_radialDirections(surfaceDof);
|
|
}
|
|
}
|
|
}
|
|
|
|
void PreparedNodalRadialSurface::applyJacobianTranspose(
|
|
const mfem::Vector ¶meters,
|
|
const mfem::Vector &surfaceDisplacementDual,
|
|
mfem::Vector ¶meterDual
|
|
) const {
|
|
requireParameterSize(parameters);
|
|
requireSurfaceDisplacementSize(surfaceDisplacementDual);
|
|
requireParameterSize(parameterDual);
|
|
|
|
for (int parameterDof = 0; parameterDof < parameterCount(); ++parameterDof) {
|
|
double radialWork = 0.0;
|
|
for (int component = 0; component < spatialDimension(); ++component) {
|
|
const int surfaceDof = spatialDimension() * parameterDof + component;
|
|
radialWork += m_radialDirections(surfaceDof) * surfaceDisplacementDual(surfaceDof);
|
|
}
|
|
parameterDual(parameterDof) = radialWork;
|
|
}
|
|
}
|
|
|
|
void PreparedNodalRadialSurface::applyPullbackDerivative(
|
|
const mfem::Vector ¶meters,
|
|
const mfem::Vector ¶meterDirection,
|
|
const mfem::Vector &surfaceDisplacementDual,
|
|
mfem::Vector ¶meterDualAction
|
|
) const {
|
|
requireParameterSize(parameters);
|
|
requireParameterSize(parameterDirection);
|
|
requireSurfaceDisplacementSize(surfaceDisplacementDual);
|
|
requireParameterSize(parameterDualAction);
|
|
|
|
parameterDualAction = 0.0;
|
|
}
|
|
|
|
void PreparedNodalRadialSurface::requireParameterSize(const mfem::Vector ¶meters) const {
|
|
if (parameters.Size() != parameterCount()) {
|
|
throw std::invalid_argument(
|
|
std::format(
|
|
"Nodal radial parameter vector has size {}, but the prepared surface requires {}.",
|
|
parameters.Size(), parameterCount()
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
void PreparedNodalRadialSurface::requireSurfaceDisplacementSize(const mfem::Vector &surfaceDisplacement) const {
|
|
if (surfaceDisplacement.Size() != surfaceDisplacementSize()) {
|
|
throw std::invalid_argument(
|
|
std::format(
|
|
"Surface displacement vector has size {}, but the prepared nodal radial surface requires {}.",
|
|
surfaceDisplacement.Size(), surfaceDisplacementSize()
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
PreparedNodalRadialSurface compileSurfaceDeformationPrescription(
|
|
const NodalRadialSurface &prescription,
|
|
const SurfaceDeformationCompilationContext &context
|
|
) {
|
|
prescription.validate();
|
|
|
|
mfem::ParFiniteElementSpace &scalarSpace = context.scalarFiniteElementSpace();
|
|
const mfem::Mesh *mesh = scalarSpace.GetMesh();
|
|
|
|
if (mesh == nullptr) {
|
|
throw std::invalid_argument("Nodal radial surface compilation requires a reference mesh.");
|
|
}
|
|
if (prescription.referenceCenter().Size() != mesh->SpaceDimension()) {
|
|
throw std::invalid_argument(
|
|
std::format(
|
|
"NodalRadialSurface reference center has dimension {}, but the reference mesh has spatial "
|
|
"dimension {}.",
|
|
prescription.referenceCenter().Size(), mesh->SpaceDimension()
|
|
)
|
|
);
|
|
}
|
|
|
|
const field::ScalarBoundaryDofMap &surfaceDofMap = context.surfaceDofMap();
|
|
const int parameterCount = surfaceDofMap.local_size();
|
|
const int spatialDimension = mesh->SpaceDimension();
|
|
|
|
mfem::Vector referencePositions(spatialDimension * parameterCount);
|
|
mfem::ParGridFunction coordinateField(&scalarSpace);
|
|
|
|
for (int component = 0; component < spatialDimension; ++component) {
|
|
mfem::FunctionCoefficient coordinateCoefficient([component](const mfem::Vector &position) {
|
|
return position(component);
|
|
});
|
|
|
|
coordinateField.ProjectCoefficient(coordinateCoefficient);
|
|
|
|
mfem::Vector coordinateTrueDofs;
|
|
coordinateField.GetTrueDofs(coordinateTrueDofs);
|
|
const mfem::Vector surfaceCoordinates = surfaceDofMap.gather(coordinateTrueDofs);
|
|
|
|
for (int parameterDof = 0; parameterDof < parameterCount; ++parameterDof) {
|
|
referencePositions(spatialDimension * parameterDof + component) = surfaceCoordinates(parameterDof);
|
|
}
|
|
}
|
|
|
|
mfem::Vector radialDirections(referencePositions.Size());
|
|
mfem::Vector referenceRadii(parameterCount);
|
|
|
|
for (int parameterDof = 0; parameterDof < parameterCount; ++parameterDof) {
|
|
double radiusSquared = 0.0;
|
|
|
|
for (int component = 0; component < spatialDimension; ++component) {
|
|
const int surfaceDof = spatialDimension * parameterDof + component;
|
|
const double radialCoordinate =
|
|
referencePositions(surfaceDof) - prescription.referenceCenter()(component);
|
|
|
|
radialDirections(surfaceDof) = radialCoordinate;
|
|
radiusSquared += radialCoordinate * radialCoordinate;
|
|
}
|
|
|
|
const double radius = std::sqrt(radiusSquared);
|
|
if (!std::isfinite(radius) || radius <= 0.0) {
|
|
throw std::invalid_argument(
|
|
"Every nodal radial surface coordinate must have a finite positive distance from the reference "
|
|
"center."
|
|
);
|
|
}
|
|
|
|
referenceRadii(parameterDof) = radius;
|
|
for (int component = 0; component < spatialDimension; ++component) {
|
|
radialDirections(spatialDimension * parameterDof + component) /= radius;
|
|
}
|
|
}
|
|
|
|
return PreparedNodalRadialSurface(
|
|
nodalRadialDescriptor(spatialDimension), prescription.referenceCenter(), surfaceDofMap,
|
|
std::move(radialDirections), std::move(referenceRadii)
|
|
);
|
|
}
|
|
} // namespace mean_field::deformation
|