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
|