Files

1062 lines
52 KiB
C++
Raw Permalink Normal View History

module;
#include <algorithm>
#include <cmath>
#include <format>
#include <limits>
#include <optional>
#include <stdexcept>
#include <utility>
#include <vector>
#include <mfem.hpp>
#include <mpi.h>
module mean_field;
import :deformation.radial_extensions;
namespace mean_field::deformation {
namespace {
struct LocatedBoundaryPoint final {
int boundaryElement{-1};
mfem::IntegrationPoint integrationPoint;
double residualNorm{std::numeric_limits<double>::infinity()};
};
[[nodiscard]] InteriorDeformationExtensionDescriptor
powerLawInteriorDescriptor(const int spatialDimension) noexcept {
return {
.name = "PowerLawRadialInteriorExtension",
.spatialDimension = spatialDimension,
.linearOnReferenceGeometry = true,
.requiresRadialFoliation = true,
.requiresAuxiliarySolve = false,
.hasExactDerivativeTranspose = true,
.hasExactPullbackDerivative = true,
.centerBehavior = InteriorCenterBehavior::FixedAtReferenceCenter
};
}
[[nodiscard]] VacuumDeformationExtensionDescriptor
fixedInfinityVacuumDescriptor(const int spatialDimension) noexcept {
return {
.name = "FixedInfinityRadialVacuumExtension",
.spatialDimension = spatialDimension,
.linearOnReferenceGeometry = true,
.requiresRadialFoliation = true,
.requiresAuxiliarySolve = false,
.hasExactDerivativeTranspose = true,
.hasExactPullbackDerivative = true,
.outerBoundaryBehavior = VacuumOuterBoundaryBehavior::FixedAtReferenceInfinity
};
}
[[nodiscard]] double logicalInfinityRadius(const mfem::Vector &position) {
double radius = 0.0;
for (int component = 0; component < position.Size(); ++component) {
radius = std::max(radius, std::abs(position(component)));
}
return radius;
}
[[nodiscard]] double euclideanDistance(
const mfem::Vector &first,
const mfem::Vector &second
) {
double squaredDistance = 0.0;
for (int component = 0; component < first.Size(); ++component) {
const double difference = first(component) - second(component);
squaredDistance += difference * difference;
}
return std::sqrt(squaredDistance);
}
[[nodiscard]] mfem::Array<int> buildTrueDofSupport(
mfem::ParFiniteElementSpace &scalarFiniteElementSpace,
const mfem::Array<int> &materialMarker
) {
const mfem::Mesh *mesh = scalarFiniteElementSpace.GetMesh();
if (mesh == nullptr) {
throw std::invalid_argument("Logical radial extension support compilation requires a mesh.");
}
if (materialMarker.Size() != mesh->attributes.Max()) {
throw std::invalid_argument(
"A logical radial extension material marker does not cover every material attribute."
);
}
mfem::Array<int> localDofMarker(scalarFiniteElementSpace.GetVSize());
localDofMarker = 0;
mfem::Array<int> elementDofs;
for (int element = 0; element < mesh->GetNE(); ++element) {
const int attribute = mesh->GetAttribute(element);
if (attribute <= 0 || attribute > materialMarker.Size() || materialMarker[attribute - 1] == 0) {
continue;
}
scalarFiniteElementSpace.GetElementDofs(element, elementDofs);
for (const int encodedDof : elementDofs) {
localDofMarker[mfem::FiniteElementSpace::DecodeDof(encodedDof)] = 1;
}
}
scalarFiniteElementSpace.Synchronize(localDofMarker);
mfem::Array<int> trueDofMarker(scalarFiniteElementSpace.GetTrueVSize());
trueDofMarker = 0;
for (int localDof = 0; localDof < localDofMarker.Size(); ++localDof) {
if (localDofMarker[localDof] == 0) {
continue;
}
const int trueDof = scalarFiniteElementSpace.GetLocalTDofNumber(localDof);
if (trueDof >= 0) {
trueDofMarker[trueDof] = 1;
}
}
return trueDofMarker;
}
[[nodiscard]] mfem::Vector buildLogicalTrueDofPositions(
mfem::ParFiniteElementSpace &scalarFiniteElementSpace,
mfem::ParMesh &logicalReferenceMesh
) {
const mfem::Mesh *physicalMesh = scalarFiniteElementSpace.GetMesh();
if (physicalMesh == nullptr || physicalMesh->GetNE() != logicalReferenceMesh.GetNE()) {
throw std::invalid_argument(
"Logical radial extension compilation requires paired physical and logical elements."
);
}
const int spatialDimension = logicalReferenceMesh.SpaceDimension();
const int trueDofCount = scalarFiniteElementSpace.GetTrueVSize();
mfem::Vector logicalPositions(spatialDimension * trueDofCount);
mfem::Array<int> processed(trueDofCount);
processed = 0;
mfem::Array<int> elementDofs;
mfem::Vector logicalPosition(spatialDimension);
for (int element = 0; element < physicalMesh->GetNE(); ++element) {
if (physicalMesh->GetElementGeometry(element) != logicalReferenceMesh.GetElementGeometry(element) ||
physicalMesh->GetAttribute(element) != logicalReferenceMesh.GetAttribute(element)) {
throw std::invalid_argument("A physical element does not match its logical reference element.");
}
const mfem::FiniteElement &finiteElement = *scalarFiniteElementSpace.GetFE(element);
const mfem::IntegrationRule &nodes = finiteElement.GetNodes();
scalarFiniteElementSpace.GetElementDofs(element, elementDofs);
if (nodes.GetNPoints() != elementDofs.Size()) {
throw std::invalid_argument(
"The scalar companion basis is not nodal on a logical reference element."
);
}
mfem::ElementTransformation *logicalTransformation =
logicalReferenceMesh.GetElementTransformation(element);
if (logicalTransformation == nullptr) {
throw std::invalid_argument("A logical reference element has no transformation.");
}
for (int localElementDof = 0; localElementDof < elementDofs.Size(); ++localElementDof) {
const int localDof = mfem::FiniteElementSpace::DecodeDof(elementDofs[localElementDof]);
const int trueDof = scalarFiniteElementSpace.GetLocalTDofNumber(localDof);
if (trueDof < 0) {
continue;
}
logicalTransformation->Transform(nodes.IntPoint(localElementDof), logicalPosition);
if (processed[trueDof] != 0) {
mfem::Vector existingPosition(
logicalPositions.GetData() + spatialDimension * trueDof, spatialDimension
);
const double scale = std::max(1.0, logicalInfinityRadius(logicalPosition));
if (euclideanDistance(existingPosition, logicalPosition) > 1.0e-12 * scale) {
throw std::invalid_argument(
"A shared scalar DOF has inconsistent logical reference coordinates."
);
}
continue;
}
for (int component = 0; component < spatialDimension; ++component) {
logicalPositions(spatialDimension * trueDof + component) = logicalPosition(component);
}
processed[trueDof] = 1;
}
}
for (int trueDof = 0; trueDof < trueDofCount; ++trueDof) {
if (processed[trueDof] == 0) {
throw std::invalid_argument("An owned scalar DOF has no logical reference coordinate.");
}
}
return logicalPositions;
}
[[nodiscard]] double boundaryLogicalRadius(
mfem::ParMesh &logicalReferenceMesh,
const int boundaryAttribute
) {
double minimumRadius = std::numeric_limits<double>::infinity();
double maximumRadius = 0.0;
int sampleCount = 0;
mfem::Vector position(logicalReferenceMesh.SpaceDimension());
for (int boundaryElement = 0; boundaryElement < logicalReferenceMesh.GetNBE(); ++boundaryElement) {
if (logicalReferenceMesh.GetBdrAttribute(boundaryElement) != boundaryAttribute) {
continue;
}
mfem::ElementTransformation *transformation =
logicalReferenceMesh.GetBdrElementTransformation(boundaryElement);
const int geometry = logicalReferenceMesh.GetBdrElementGeometry(boundaryElement);
const mfem::IntegrationRule *vertices = mfem::Geometries.GetVertices(geometry);
if (transformation == nullptr || vertices == nullptr) {
throw std::invalid_argument("A logical radial boundary element is incomplete.");
}
for (int vertex = 0; vertex < vertices->GetNPoints(); ++vertex) {
transformation->Transform(vertices->IntPoint(vertex), position);
const double radius = logicalInfinityRadius(position);
minimumRadius = std::min(minimumRadius, radius);
maximumRadius = std::max(maximumRadius, radius);
++sampleCount;
}
}
if (sampleCount == 0 || !(minimumRadius > 0.0)) {
throw std::invalid_argument("A required logical radial boundary is absent.");
}
if (maximumRadius - minimumRadius > 1.0e-12 * std::max(1.0, maximumRadius)) {
throw std::invalid_argument("A logical radial boundary is not a constant L-infinity-radius surface.");
}
return 0.5 * (minimumRadius + maximumRadius);
}
[[nodiscard]] LocatedBoundaryPoint locateLogicalBoundaryPoint(
mfem::ParMesh &logicalReferenceMesh,
const int boundaryAttribute,
const mfem::Vector &target
) {
LocatedBoundaryPoint best;
const double residualTolerance = 2.0e-11 * std::max(1.0, logicalInfinityRadius(target));
for (int boundaryElement = 0; boundaryElement < logicalReferenceMesh.GetNBE(); ++boundaryElement) {
if (logicalReferenceMesh.GetBdrAttribute(boundaryElement) != boundaryAttribute) {
continue;
}
const int geometry = logicalReferenceMesh.GetBdrElementGeometry(boundaryElement);
if (geometry != mfem::Geometry::SQUARE) {
throw std::invalid_argument(
"The STROID logical radial foliation currently requires quadrilateral boundary elements."
);
}
mfem::ElementTransformation *transformation =
logicalReferenceMesh.GetBdrElementTransformation(boundaryElement);
if (transformation == nullptr) {
throw std::invalid_argument("A logical boundary element has no transformation.");
}
mfem::IntegrationPoint point = mfem::Geometries.GetCenter(geometry);
mfem::Vector transformed(target.Size());
mfem::Vector residual(target.Size());
bool nonsingular = true;
for (int iteration = 0; iteration < 8; ++iteration) {
transformation->Transform(point, transformed);
residual = target;
residual -= transformed;
if (residual.Norml2() <= residualTolerance) {
break;
}
transformation->SetIntPoint(&point);
const mfem::DenseMatrix &jacobian = transformation->Jacobian();
double normal00 = 0.0;
double normal01 = 0.0;
double normal11 = 0.0;
double right0 = 0.0;
double right1 = 0.0;
for (int component = 0; component < target.Size(); ++component) {
normal00 += jacobian(component, 0) * jacobian(component, 0);
normal01 += jacobian(component, 0) * jacobian(component, 1);
normal11 += jacobian(component, 1) * jacobian(component, 1);
right0 += jacobian(component, 0) * residual(component);
right1 += jacobian(component, 1) * residual(component);
}
const double determinant = normal00 * normal11 - normal01 * normal01;
if (!(std::abs(determinant) > std::numeric_limits<double>::epsilon())) {
nonsingular = false;
break;
}
point.x += (normal11 * right0 - normal01 * right1) / determinant;
point.y += (normal00 * right1 - normal01 * right0) / determinant;
}
if (!nonsingular || !mfem::Geometry::CheckPoint(geometry, point, 1.0e-10)) {
continue;
}
transformation->Transform(point, transformed);
const double residualNorm = euclideanDistance(transformed, target);
if (residualNorm <= residualTolerance && residualNorm < best.residualNorm) {
best.boundaryElement = boundaryElement;
best.integrationPoint = point;
best.residualNorm = residualNorm;
}
}
if (best.boundaryElement < 0) {
throw std::invalid_argument(
std::format(
"No logical boundary element contains a projected foliation point on boundary attribute {}.",
boundaryAttribute
)
);
}
return best;
}
void appendSurfaceInterpolationRow(
mfem::ParFiniteElementSpace &scalarFiniteElementSpace,
const field::ScalarBoundaryDofMap &stellarSurfaceDofMap,
const LocatedBoundaryPoint &locatedPoint,
std::vector<int> &globalCoordinates,
std::vector<double> &weights
) {
mfem::Array<int> boundaryDofs;
scalarFiniteElementSpace.GetBdrElementDofs(locatedPoint.boundaryElement, boundaryDofs);
const mfem::FiniteElement *boundaryElement = scalarFiniteElementSpace.GetBE(locatedPoint.boundaryElement);
if (boundaryElement == nullptr || boundaryElement->GetDof() != boundaryDofs.Size()) {
throw std::invalid_argument("The physical surface trace basis is incompatible with its boundary DOFs.");
}
mfem::Vector shape(boundaryDofs.Size());
boundaryElement->CalcShape(locatedPoint.integrationPoint, shape);
double partitionOfUnity = 0.0;
for (int localShapeDof = 0; localShapeDof < boundaryDofs.Size(); ++localShapeDof) {
partitionOfUnity += shape(localShapeDof);
if (std::abs(shape(localShapeDof)) <= 64.0 * std::numeric_limits<double>::epsilon()) {
continue;
}
const int localDof = mfem::FiniteElementSpace::DecodeDof(boundaryDofs[localShapeDof]);
const int trueDof = scalarFiniteElementSpace.GetLocalTDofNumber(localDof);
if (trueDof < 0) {
throw std::invalid_argument(
"Logical surface interpolation across MPI ownership is not implemented yet."
);
}
const std::optional<int> surfaceDof = stellarSurfaceDofMap.local_boundary_dof(trueDof);
if (!surfaceDof.has_value()) {
throw std::invalid_argument(
"A logical surface interpolation basis DOF is absent from the compact surface map."
);
}
const long long globalCoordinate = stellarSurfaceDofMap.global_boundary_dof(*surfaceDof);
if (globalCoordinate > std::numeric_limits<int>::max()) {
throw std::overflow_error("A global surface coordinate exceeds supported integer indexing.");
}
globalCoordinates.push_back(static_cast<int>(globalCoordinate));
weights.push_back(shape(localShapeDof));
}
if (std::abs(partitionOfUnity - 1.0) > 2.0e-12) {
throw std::invalid_argument("A logical surface interpolation row does not preserve constants.");
}
}
[[nodiscard]] std::vector<int> gatherCounts(
const int localCount,
MPI_Comm communicator
) {
int communicatorSize = 0;
MPI_Comm_size(communicator, &communicatorSize);
std::vector<int> counts(static_cast<std::size_t>(communicatorSize));
MPI_Allgather(&localCount, 1, MPI_INT, counts.data(), 1, MPI_INT, communicator);
return counts;
}
[[nodiscard]] std::vector<int> prefixOffsets(const std::vector<int> &counts) {
std::vector<int> offsets(counts.size());
int offset = 0;
for (std::size_t rank = 0; rank < counts.size(); ++rank) {
offsets[rank] = offset;
offset += counts[rank];
}
return offsets;
}
void requireVectorSize(
const mfem::Vector &vector,
const int requiredSize,
const char *name
) {
if (vector.Size() != requiredSize) {
throw std::invalid_argument(
std::format(
"{} has size {}, but the prepared logical radial extension requires {}.", name, vector.Size(),
requiredSize
)
);
}
}
void gatherSurfaceDisplacement(
const mfem::Vector &localSurfaceDisplacement,
mfem::Vector &globalSurfaceDisplacement,
const std::vector<int> &counts,
const std::vector<int> &offsets,
MPI_Comm communicator
) {
MPI_Allgatherv(
localSurfaceDisplacement.GetData(), localSurfaceDisplacement.Size(), MPI_DOUBLE,
globalSurfaceDisplacement.GetData(), counts.data(), offsets.data(), MPI_DOUBLE, communicator
);
}
void reduceSurfaceDual(
const mfem::Vector &localGlobalSurfaceDual,
mfem::Vector &globalSurfaceDual,
mfem::Vector &surfaceDisplacementDual,
const int globalSurfaceDisplacementOffset,
MPI_Comm communicator
) {
MPI_Allreduce(
localGlobalSurfaceDual.GetData(), globalSurfaceDual.GetData(), localGlobalSurfaceDual.Size(),
MPI_DOUBLE, MPI_SUM, communicator
);
for (int localDof = 0; localDof < surfaceDisplacementDual.Size(); ++localDof) {
surfaceDisplacementDual(localDof) = globalSurfaceDual(globalSurfaceDisplacementOffset + localDof);
}
}
[[nodiscard]] int interpolationEntryIndex(
const std::vector<int> &rowOffsets,
const int scalarTrueDof,
const int interpolationEntry
) {
if (scalarTrueDof < 0 || scalarTrueDof + 1 >= static_cast<int>(rowOffsets.size())) {
throw std::out_of_range("Scalar true DOF is outside the logical radial extension.");
}
const int entryCount = rowOffsets[scalarTrueDof + 1] - rowOffsets[scalarTrueDof];
if (interpolationEntry < 0 || interpolationEntry >= entryCount) {
throw std::out_of_range("Surface interpolation entry is outside the logical radial extension row.");
}
return rowOffsets[scalarTrueDof] + interpolationEntry;
}
[[nodiscard]] int mfemByNodesVectorDof(
const int scalarTrueDof,
const int component,
const int scalarTrueDofCount
) noexcept {
return scalarTrueDof + component * scalarTrueDofCount;
}
void applySparseForward(
const int spatialDimension,
const mfem::Array<int> &support,
const mfem::Vector &radialWeights,
const std::vector<int> &rowOffsets,
const std::vector<int> &surfaceCoordinates,
const std::vector<double> &surfaceWeights,
const mfem::Vector &globalSurfaceDisplacement,
mfem::Vector &volumeDisplacement
) {
volumeDisplacement = 0.0;
for (int scalarTrueDof = 0; scalarTrueDof < support.Size(); ++scalarTrueDof) {
if (support[scalarTrueDof] == 0 || radialWeights(scalarTrueDof) == 0.0) {
continue;
}
for (int component = 0; component < spatialDimension; ++component) {
double interpolatedSurfaceDisplacement = 0.0;
for (int entry = rowOffsets[scalarTrueDof]; entry < rowOffsets[scalarTrueDof + 1]; ++entry) {
interpolatedSurfaceDisplacement +=
surfaceWeights[entry] *
globalSurfaceDisplacement(spatialDimension * surfaceCoordinates[entry] + component);
}
volumeDisplacement(mfemByNodesVectorDof(scalarTrueDof, component, support.Size())) =
radialWeights(scalarTrueDof) * interpolatedSurfaceDisplacement;
}
}
}
void applySparseTranspose(
const int spatialDimension,
const mfem::Array<int> &support,
const mfem::Vector &radialWeights,
const std::vector<int> &rowOffsets,
const std::vector<int> &surfaceCoordinates,
const std::vector<double> &surfaceWeights,
const mfem::Vector &volumeDual,
mfem::Vector &globalSurfaceDual
) {
globalSurfaceDual = 0.0;
for (int scalarTrueDof = 0; scalarTrueDof < support.Size(); ++scalarTrueDof) {
if (support[scalarTrueDof] == 0 || radialWeights(scalarTrueDof) == 0.0) {
continue;
}
for (int entry = rowOffsets[scalarTrueDof]; entry < rowOffsets[scalarTrueDof + 1]; ++entry) {
for (int component = 0; component < spatialDimension; ++component) {
globalSurfaceDual(spatialDimension * surfaceCoordinates[entry] + component) +=
radialWeights(scalarTrueDof) * surfaceWeights[entry] *
volumeDual(mfemByNodesVectorDof(scalarTrueDof, component, support.Size()));
}
}
}
}
} // namespace
RadialDeformationExtensionCompilationContext::RadialDeformationExtensionCompilationContext(
mfem::ParFiniteElementSpace &scalarFiniteElementSpace,
mfem::ParFiniteElementSpace &vectorFiniteElementSpace,
mfem::ParMesh &logicalReferenceMesh,
field::ScalarBoundaryDofMap stellarSurfaceDofMap,
field::ScalarBoundaryDofMap infinitySurfaceDofMap,
mfem::Array<int> stellarMaterialMarker,
mfem::Array<int> vacuumMaterialMarker,
const int stellarSurfaceBoundaryAttribute,
const int infinitySurfaceBoundaryAttribute
)
: m_communicator(scalarFiniteElementSpace.GetComm()) {
const mfem::Mesh *physicalMesh = scalarFiniteElementSpace.GetMesh();
if (physicalMesh == nullptr || vectorFiniteElementSpace.GetMesh() != physicalMesh) {
throw std::invalid_argument(
"Logical radial extension scalar and vector spaces must share one physical mesh."
);
}
int communicatorSize = 0;
MPI_Comm_size(m_communicator, &communicatorSize);
if (communicatorSize != 1) {
throw std::invalid_argument(
"Logical radial surface interpolation currently supports one MPI rank; shared interpolation-row "
"ownership remains deferred."
);
}
if (scalarFiniteElementSpace.Nonconforming() || vectorFiniteElementSpace.Nonconforming() ||
logicalReferenceMesh.Nonconforming()) {
throw std::invalid_argument("Logical radial extension compilation currently requires conforming meshes.");
}
m_spatialDimension = physicalMesh->SpaceDimension();
m_scalarTrueDofCount = scalarFiniteElementSpace.GetTrueVSize();
m_volumeDisplacementSize = vectorFiniteElementSpace.GetTrueVSize();
if (logicalReferenceMesh.SpaceDimension() != m_spatialDimension ||
logicalReferenceMesh.GetNE() != physicalMesh->GetNE() ||
logicalReferenceMesh.GetNBE() != physicalMesh->GetNBE()) {
throw std::invalid_argument("The STROID logical reference mesh does not match the physical mesh topology.");
}
for (int boundaryElement = 0; boundaryElement < physicalMesh->GetNBE(); ++boundaryElement) {
if (logicalReferenceMesh.GetBdrAttribute(boundaryElement) !=
physicalMesh->GetBdrAttribute(boundaryElement) ||
logicalReferenceMesh.GetBdrElementGeometry(boundaryElement) !=
physicalMesh->GetBdrElementGeometry(boundaryElement)) {
throw std::invalid_argument("The STROID logical and physical boundary elements do not correspond.");
}
}
if (scalarFiniteElementSpace.GetVDim() != 1 || vectorFiniteElementSpace.GetVDim() != m_spatialDimension ||
vectorFiniteElementSpace.GetOrdering() != mfem::Ordering::byNODES ||
m_volumeDisplacementSize != m_spatialDimension * m_scalarTrueDofCount ||
vectorFiniteElementSpace.FEColl() != scalarFiniteElementSpace.FEColl()) {
throw std::invalid_argument(
"Logical radial extension compilation requires an MFEM byNODES vector space made from its scalar "
"companion basis."
);
}
if (stellarSurfaceDofMap.volume_true_dof_size() != m_scalarTrueDofCount ||
infinitySurfaceDofMap.volume_true_dof_size() != m_scalarTrueDofCount) {
throw std::invalid_argument("Logical radial boundary maps do not match the scalar companion space.");
}
m_surfaceDisplacementSize = m_spatialDimension * stellarSurfaceDofMap.local_size();
const long long globalSurfaceSize = m_spatialDimension * stellarSurfaceDofMap.global_size();
const long long globalOffset = m_spatialDimension * stellarSurfaceDofMap.global_offset();
if (globalSurfaceSize > std::numeric_limits<int>::max() || globalOffset > std::numeric_limits<int>::max()) {
throw std::overflow_error("The logical radial extension surface vector exceeds MPI integer indexing.");
}
m_globalSurfaceDisplacementSize = static_cast<int>(globalSurfaceSize);
m_globalSurfaceDisplacementOffset = static_cast<int>(globalOffset);
m_surfaceDisplacementCounts = gatherCounts(m_surfaceDisplacementSize, m_communicator);
m_surfaceDisplacementOffsets = prefixOffsets(m_surfaceDisplacementCounts);
m_stellarSupport = buildTrueDofSupport(scalarFiniteElementSpace, stellarMaterialMarker);
m_vacuumSupport = buildTrueDofSupport(scalarFiniteElementSpace, vacuumMaterialMarker);
const mfem::Vector logicalPositions =
buildLogicalTrueDofPositions(scalarFiniteElementSpace, logicalReferenceMesh);
m_stellarSurfaceLogicalRadius = boundaryLogicalRadius(logicalReferenceMesh, stellarSurfaceBoundaryAttribute);
m_infinitySurfaceLogicalRadius = boundaryLogicalRadius(logicalReferenceMesh, infinitySurfaceBoundaryAttribute);
if (!(m_infinitySurfaceLogicalRadius > m_stellarSurfaceLogicalRadius)) {
throw std::invalid_argument("Logical reference infinity must lie outside the logical stellar surface.");
}
m_logicalRadius.SetSize(m_scalarTrueDofCount);
m_surfaceInterpolationRowOffsets.resize(static_cast<std::size_t>(m_scalarTrueDofCount + 1));
mfem::Vector logicalPosition(m_spatialDimension);
mfem::Vector stellarSurfaceTarget(m_spatialDimension);
mfem::Vector infinitySurfaceTarget(m_spatialDimension);
const double centerTolerance = 64.0 * std::numeric_limits<double>::epsilon() * m_infinitySurfaceLogicalRadius;
const double radialTolerance = 128.0 * std::numeric_limits<double>::epsilon() * m_infinitySurfaceLogicalRadius;
for (int scalarTrueDof = 0; scalarTrueDof < m_scalarTrueDofCount; ++scalarTrueDof) {
m_surfaceInterpolationRowOffsets[scalarTrueDof] = static_cast<int>(m_surfaceInterpolationWeights.size());
for (int component = 0; component < m_spatialDimension; ++component) {
logicalPosition(component) = logicalPositions(m_spatialDimension * scalarTrueDof + component);
}
const double radius = logicalInfinityRadius(logicalPosition);
m_logicalRadius(scalarTrueDof) = radius;
if (radius <= centerTolerance) {
continue;
}
if (m_stellarSupport[scalarTrueDof] != 0 && radius > m_stellarSurfaceLogicalRadius + radialTolerance) {
throw std::invalid_argument("A stellar DOF lies outside the logical stellar surface.");
}
if (m_vacuumSupport[scalarTrueDof] != 0 && (radius < m_stellarSurfaceLogicalRadius - radialTolerance ||
radius > m_infinitySurfaceLogicalRadius + radialTolerance)) {
throw std::invalid_argument("A vacuum DOF lies outside the logical exterior interval.");
}
for (int component = 0; component < m_spatialDimension; ++component) {
stellarSurfaceTarget(component) = logicalPosition(component) * m_stellarSurfaceLogicalRadius / radius;
}
const LocatedBoundaryPoint stellarSurfacePoint =
locateLogicalBoundaryPoint(logicalReferenceMesh, stellarSurfaceBoundaryAttribute, stellarSurfaceTarget);
appendSurfaceInterpolationRow(
scalarFiniteElementSpace, stellarSurfaceDofMap, stellarSurfacePoint,
m_surfaceInterpolationGlobalCoordinates, m_surfaceInterpolationWeights
);
if (m_vacuumSupport[scalarTrueDof] != 0) {
for (int component = 0; component < m_spatialDimension; ++component) {
infinitySurfaceTarget(component) =
logicalPosition(component) * m_infinitySurfaceLogicalRadius / radius;
}
static_cast<void>(locateLogicalBoundaryPoint(
logicalReferenceMesh, infinitySurfaceBoundaryAttribute, infinitySurfaceTarget
));
}
}
m_surfaceInterpolationRowOffsets[m_scalarTrueDofCount] = static_cast<int>(m_surfaceInterpolationWeights.size());
}
int RadialDeformationExtensionCompilationContext::spatialDimension() const noexcept {
return m_spatialDimension;
}
int RadialDeformationExtensionCompilationContext::surfaceDisplacementSize() const noexcept {
return m_surfaceDisplacementSize;
}
int RadialDeformationExtensionCompilationContext::volumeDisplacementSize() const noexcept {
return m_volumeDisplacementSize;
}
int RadialDeformationExtensionCompilationContext::scalarTrueDofCount() const noexcept {
return m_scalarTrueDofCount;
}
double RadialDeformationExtensionCompilationContext::logicalRadius(const int scalarTrueDof) const {
if (scalarTrueDof < 0 || scalarTrueDof >= scalarTrueDofCount()) {
throw std::out_of_range("Scalar true DOF is outside the logical radial compilation context.");
}
return m_logicalRadius(scalarTrueDof);
}
double RadialDeformationExtensionCompilationContext::stellarSurfaceLogicalRadius() const noexcept {
return m_stellarSurfaceLogicalRadius;
}
double RadialDeformationExtensionCompilationContext::infinitySurfaceLogicalRadius() const noexcept {
return m_infinitySurfaceLogicalRadius;
}
int RadialDeformationExtensionCompilationContext::surfaceInterpolationEntryCount(const int scalarTrueDof) const {
if (scalarTrueDof < 0 || scalarTrueDof >= scalarTrueDofCount()) {
throw std::out_of_range("Scalar true DOF is outside the logical radial compilation context.");
}
return m_surfaceInterpolationRowOffsets[scalarTrueDof + 1] - m_surfaceInterpolationRowOffsets[scalarTrueDof];
}
int RadialDeformationExtensionCompilationContext::surfaceGlobalCoordinate(
const int scalarTrueDof,
const int interpolationEntry
) const {
return m_surfaceInterpolationGlobalCoordinates[interpolationEntryIndex(
m_surfaceInterpolationRowOffsets, scalarTrueDof, interpolationEntry
)];
}
double RadialDeformationExtensionCompilationContext::surfaceInterpolationWeight(
const int scalarTrueDof,
const int interpolationEntry
) const {
return m_surfaceInterpolationWeights[interpolationEntryIndex(
m_surfaceInterpolationRowOffsets, scalarTrueDof, interpolationEntry
)];
}
PowerLawRadialInteriorExtension::PowerLawRadialInteriorExtension(const double radialPower)
: m_radialPower(radialPower) {
validate();
}
double PowerLawRadialInteriorExtension::radialPower() const noexcept {
return m_radialPower;
}
InteriorDeformationExtensionDescriptor PowerLawRadialInteriorExtension::descriptor() const noexcept {
return powerLawInteriorDescriptor(3);
}
void PowerLawRadialInteriorExtension::validate() const {
if (!std::isfinite(m_radialPower) || m_radialPower < 1.0) {
throw std::invalid_argument("PowerLawRadialInteriorExtension requires a finite radial power at least one.");
}
}
PreparedPowerLawRadialInteriorExtension::PreparedPowerLawRadialInteriorExtension(
const PowerLawRadialInteriorExtension &extension,
const RadialDeformationExtensionCompilationContext &context
)
: m_descriptor(powerLawInteriorDescriptor(context.m_spatialDimension)),
m_radialPower(extension.radialPower()),
m_surfaceDisplacementSize(context.m_surfaceDisplacementSize),
m_interiorDisplacementSize(context.m_volumeDisplacementSize),
m_spatialDimension(context.m_spatialDimension),
m_globalSurfaceDisplacementSize(context.m_globalSurfaceDisplacementSize),
m_globalSurfaceDisplacementOffset(context.m_globalSurfaceDisplacementOffset),
m_communicator(context.m_communicator),
m_stellarSupport(context.m_stellarSupport),
m_radialWeights(context.m_scalarTrueDofCount),
m_surfaceInterpolationRowOffsets(context.m_surfaceInterpolationRowOffsets),
m_surfaceInterpolationGlobalCoordinates(context.m_surfaceInterpolationGlobalCoordinates),
m_surfaceInterpolationWeights(context.m_surfaceInterpolationWeights),
m_surfaceDisplacementCounts(context.m_surfaceDisplacementCounts),
m_surfaceDisplacementOffsets(context.m_surfaceDisplacementOffsets),
m_globalSurfaceDisplacementWorkspace(context.m_globalSurfaceDisplacementSize),
m_localGlobalSurfaceDualWorkspace(context.m_globalSurfaceDisplacementSize),
m_globalSurfaceDualWorkspace(context.m_globalSurfaceDisplacementSize) {
for (int scalarTrueDof = 0; scalarTrueDof < scalarTrueDofCount(); ++scalarTrueDof) {
if (m_stellarSupport[scalarTrueDof] == 0 || context.m_logicalRadius(scalarTrueDof) == 0.0) {
m_radialWeights(scalarTrueDof) = 0.0;
continue;
}
m_radialWeights(scalarTrueDof) =
std::pow(context.m_logicalRadius(scalarTrueDof) / context.m_stellarSurfaceLogicalRadius, m_radialPower);
}
}
InteriorDeformationExtensionDescriptor PreparedPowerLawRadialInteriorExtension::descriptor() const noexcept {
return m_descriptor;
}
int PreparedPowerLawRadialInteriorExtension::surfaceDisplacementSize() const noexcept {
return m_surfaceDisplacementSize;
}
int PreparedPowerLawRadialInteriorExtension::interiorDisplacementSize() const noexcept {
return m_interiorDisplacementSize;
}
int PreparedPowerLawRadialInteriorExtension::scalarTrueDofCount() const noexcept {
return m_stellarSupport.Size();
}
double PreparedPowerLawRadialInteriorExtension::radialPower() const noexcept {
return m_radialPower;
}
bool PreparedPowerLawRadialInteriorExtension::hasStellarSupport(const int scalarTrueDof) const {
if (scalarTrueDof < 0 || scalarTrueDof >= scalarTrueDofCount()) {
throw std::out_of_range("Scalar true DOF is outside the prepared interior extension.");
}
return m_stellarSupport[scalarTrueDof] != 0;
}
double PreparedPowerLawRadialInteriorExtension::radialWeight(const int scalarTrueDof) const {
if (scalarTrueDof < 0 || scalarTrueDof >= scalarTrueDofCount()) {
throw std::out_of_range("Scalar true DOF is outside the prepared interior extension.");
}
return m_radialWeights(scalarTrueDof);
}
int PreparedPowerLawRadialInteriorExtension::surfaceInterpolationEntryCount(const int scalarTrueDof) const {
if (scalarTrueDof < 0 || scalarTrueDof >= scalarTrueDofCount()) {
throw std::out_of_range("Scalar true DOF is outside the prepared interior extension.");
}
return m_surfaceInterpolationRowOffsets[scalarTrueDof + 1] - m_surfaceInterpolationRowOffsets[scalarTrueDof];
}
int PreparedPowerLawRadialInteriorExtension::surfaceGlobalCoordinate(
const int scalarTrueDof,
const int interpolationEntry
) const {
return m_surfaceInterpolationGlobalCoordinates[interpolationEntryIndex(
m_surfaceInterpolationRowOffsets, scalarTrueDof, interpolationEntry
)];
}
double PreparedPowerLawRadialInteriorExtension::surfaceInterpolationWeight(
const int scalarTrueDof,
const int interpolationEntry
) const {
return m_surfaceInterpolationWeights[interpolationEntryIndex(
m_surfaceInterpolationRowOffsets, scalarTrueDof, interpolationEntry
)];
}
void PreparedPowerLawRadialInteriorExtension::requireSurfaceSize(const mfem::Vector &vector) const {
requireVectorSize(vector, surfaceDisplacementSize(), "Surface displacement");
}
void PreparedPowerLawRadialInteriorExtension::requireInteriorSize(const mfem::Vector &vector) const {
requireVectorSize(vector, interiorDisplacementSize(), "Interior displacement");
}
void PreparedPowerLawRadialInteriorExtension::applyForward(
const mfem::Vector &surfaceDisplacement,
mfem::Vector &interiorDisplacement
) const {
gatherSurfaceDisplacement(
surfaceDisplacement, m_globalSurfaceDisplacementWorkspace, m_surfaceDisplacementCounts,
m_surfaceDisplacementOffsets, m_communicator
);
applySparseForward(
m_spatialDimension, m_stellarSupport, m_radialWeights, m_surfaceInterpolationRowOffsets,
m_surfaceInterpolationGlobalCoordinates, m_surfaceInterpolationWeights,
m_globalSurfaceDisplacementWorkspace, interiorDisplacement
);
}
void PreparedPowerLawRadialInteriorExtension::applyTranspose(
const mfem::Vector &interiorDisplacementDual,
mfem::Vector &surfaceDisplacementDual
) const {
applySparseTranspose(
m_spatialDimension, m_stellarSupport, m_radialWeights, m_surfaceInterpolationRowOffsets,
m_surfaceInterpolationGlobalCoordinates, m_surfaceInterpolationWeights, interiorDisplacementDual,
m_localGlobalSurfaceDualWorkspace
);
reduceSurfaceDual(
m_localGlobalSurfaceDualWorkspace, m_globalSurfaceDualWorkspace, surfaceDisplacementDual,
m_globalSurfaceDisplacementOffset, m_communicator
);
}
void PreparedPowerLawRadialInteriorExtension::buildInteriorDisplacement(
const mfem::Vector &surfaceDisplacement,
mfem::Vector &interiorDisplacement
) const {
requireSurfaceSize(surfaceDisplacement);
requireInteriorSize(interiorDisplacement);
applyForward(surfaceDisplacement, interiorDisplacement);
}
void PreparedPowerLawRadialInteriorExtension::applyJacobian(
const mfem::Vector &surfaceDisplacement,
const mfem::Vector &surfaceDisplacementDirection,
mfem::Vector &interiorDisplacementDirection
) const {
requireSurfaceSize(surfaceDisplacement);
requireSurfaceSize(surfaceDisplacementDirection);
requireInteriorSize(interiorDisplacementDirection);
applyForward(surfaceDisplacementDirection, interiorDisplacementDirection);
}
void PreparedPowerLawRadialInteriorExtension::applyJacobianTranspose(
const mfem::Vector &surfaceDisplacement,
const mfem::Vector &interiorDisplacementDual,
mfem::Vector &surfaceDisplacementDual
) const {
requireSurfaceSize(surfaceDisplacement);
requireInteriorSize(interiorDisplacementDual);
requireSurfaceSize(surfaceDisplacementDual);
applyTranspose(interiorDisplacementDual, surfaceDisplacementDual);
}
void PreparedPowerLawRadialInteriorExtension::applyPullbackDerivative(
const mfem::Vector &surfaceDisplacement,
const mfem::Vector &surfaceDisplacementDirection,
const mfem::Vector &interiorDisplacementDual,
mfem::Vector &surfaceDisplacementDualAction
) const {
requireSurfaceSize(surfaceDisplacement);
requireSurfaceSize(surfaceDisplacementDirection);
requireInteriorSize(interiorDisplacementDual);
requireSurfaceSize(surfaceDisplacementDualAction);
surfaceDisplacementDualAction = 0.0;
}
PreparedPowerLawRadialInteriorExtension compileInteriorDeformationExtension(
const PowerLawRadialInteriorExtension &extension,
const RadialDeformationExtensionCompilationContext &context
) {
extension.validate();
return PreparedPowerLawRadialInteriorExtension(extension, context);
}
VacuumDeformationExtensionDescriptor FixedInfinityRadialVacuumExtension::descriptor() const noexcept {
return fixedInfinityVacuumDescriptor(3);
}
void FixedInfinityRadialVacuumExtension::validate() const {
}
PreparedFixedInfinityRadialVacuumExtension::PreparedFixedInfinityRadialVacuumExtension(
const FixedInfinityRadialVacuumExtension &extension,
const RadialDeformationExtensionCompilationContext &context
)
: m_descriptor(fixedInfinityVacuumDescriptor(context.m_spatialDimension)),
m_surfaceDisplacementSize(context.m_surfaceDisplacementSize),
m_vacuumDisplacementSize(context.m_volumeDisplacementSize),
m_spatialDimension(context.m_spatialDimension),
m_globalSurfaceDisplacementSize(context.m_globalSurfaceDisplacementSize),
m_globalSurfaceDisplacementOffset(context.m_globalSurfaceDisplacementOffset),
m_communicator(context.m_communicator),
m_vacuumSupport(context.m_vacuumSupport),
m_radialWeights(context.m_scalarTrueDofCount),
m_surfaceInterpolationRowOffsets(context.m_surfaceInterpolationRowOffsets),
m_surfaceInterpolationGlobalCoordinates(context.m_surfaceInterpolationGlobalCoordinates),
m_surfaceInterpolationWeights(context.m_surfaceInterpolationWeights),
m_surfaceDisplacementCounts(context.m_surfaceDisplacementCounts),
m_surfaceDisplacementOffsets(context.m_surfaceDisplacementOffsets),
m_globalSurfaceDisplacementWorkspace(context.m_globalSurfaceDisplacementSize),
m_localGlobalSurfaceDualWorkspace(context.m_globalSurfaceDisplacementSize),
m_globalSurfaceDualWorkspace(context.m_globalSurfaceDisplacementSize) {
static_cast<void>(extension);
for (int scalarTrueDof = 0; scalarTrueDof < scalarTrueDofCount(); ++scalarTrueDof) {
if (m_vacuumSupport[scalarTrueDof] == 0) {
m_radialWeights(scalarTrueDof) = 0.0;
continue;
}
m_radialWeights(scalarTrueDof) =
(context.m_infinitySurfaceLogicalRadius - context.m_logicalRadius(scalarTrueDof)) /
(context.m_infinitySurfaceLogicalRadius - context.m_stellarSurfaceLogicalRadius);
}
}
VacuumDeformationExtensionDescriptor PreparedFixedInfinityRadialVacuumExtension::descriptor() const noexcept {
return m_descriptor;
}
int PreparedFixedInfinityRadialVacuumExtension::surfaceDisplacementSize() const noexcept {
return m_surfaceDisplacementSize;
}
int PreparedFixedInfinityRadialVacuumExtension::vacuumDisplacementSize() const noexcept {
return m_vacuumDisplacementSize;
}
int PreparedFixedInfinityRadialVacuumExtension::scalarTrueDofCount() const noexcept {
return m_vacuumSupport.Size();
}
bool PreparedFixedInfinityRadialVacuumExtension::hasVacuumSupport(const int scalarTrueDof) const {
if (scalarTrueDof < 0 || scalarTrueDof >= scalarTrueDofCount()) {
throw std::out_of_range("Scalar true DOF is outside the prepared vacuum extension.");
}
return m_vacuumSupport[scalarTrueDof] != 0;
}
double PreparedFixedInfinityRadialVacuumExtension::radialWeight(const int scalarTrueDof) const {
if (scalarTrueDof < 0 || scalarTrueDof >= scalarTrueDofCount()) {
throw std::out_of_range("Scalar true DOF is outside the prepared vacuum extension.");
}
return m_radialWeights(scalarTrueDof);
}
int PreparedFixedInfinityRadialVacuumExtension::surfaceInterpolationEntryCount(const int scalarTrueDof) const {
if (scalarTrueDof < 0 || scalarTrueDof >= scalarTrueDofCount()) {
throw std::out_of_range("Scalar true DOF is outside the prepared vacuum extension.");
}
return m_surfaceInterpolationRowOffsets[scalarTrueDof + 1] - m_surfaceInterpolationRowOffsets[scalarTrueDof];
}
int PreparedFixedInfinityRadialVacuumExtension::surfaceGlobalCoordinate(
const int scalarTrueDof,
const int interpolationEntry
) const {
return m_surfaceInterpolationGlobalCoordinates[interpolationEntryIndex(
m_surfaceInterpolationRowOffsets, scalarTrueDof, interpolationEntry
)];
}
double PreparedFixedInfinityRadialVacuumExtension::surfaceInterpolationWeight(
const int scalarTrueDof,
const int interpolationEntry
) const {
return m_surfaceInterpolationWeights[interpolationEntryIndex(
m_surfaceInterpolationRowOffsets, scalarTrueDof, interpolationEntry
)];
}
void PreparedFixedInfinityRadialVacuumExtension::requireSurfaceSize(const mfem::Vector &vector) const {
requireVectorSize(vector, surfaceDisplacementSize(), "Surface displacement");
}
void PreparedFixedInfinityRadialVacuumExtension::requireVacuumSize(const mfem::Vector &vector) const {
requireVectorSize(vector, vacuumDisplacementSize(), "Vacuum displacement");
}
void PreparedFixedInfinityRadialVacuumExtension::applyForward(
const mfem::Vector &surfaceDisplacement,
mfem::Vector &vacuumDisplacement
) const {
gatherSurfaceDisplacement(
surfaceDisplacement, m_globalSurfaceDisplacementWorkspace, m_surfaceDisplacementCounts,
m_surfaceDisplacementOffsets, m_communicator
);
applySparseForward(
m_spatialDimension, m_vacuumSupport, m_radialWeights, m_surfaceInterpolationRowOffsets,
m_surfaceInterpolationGlobalCoordinates, m_surfaceInterpolationWeights,
m_globalSurfaceDisplacementWorkspace, vacuumDisplacement
);
}
void PreparedFixedInfinityRadialVacuumExtension::applyTranspose(
const mfem::Vector &vacuumDisplacementDual,
mfem::Vector &surfaceDisplacementDual
) const {
applySparseTranspose(
m_spatialDimension, m_vacuumSupport, m_radialWeights, m_surfaceInterpolationRowOffsets,
m_surfaceInterpolationGlobalCoordinates, m_surfaceInterpolationWeights, vacuumDisplacementDual,
m_localGlobalSurfaceDualWorkspace
);
reduceSurfaceDual(
m_localGlobalSurfaceDualWorkspace, m_globalSurfaceDualWorkspace, surfaceDisplacementDual,
m_globalSurfaceDisplacementOffset, m_communicator
);
}
void PreparedFixedInfinityRadialVacuumExtension::buildVacuumDisplacement(
const mfem::Vector &surfaceDisplacement,
mfem::Vector &vacuumDisplacement
) const {
requireSurfaceSize(surfaceDisplacement);
requireVacuumSize(vacuumDisplacement);
applyForward(surfaceDisplacement, vacuumDisplacement);
}
void PreparedFixedInfinityRadialVacuumExtension::applyJacobian(
const mfem::Vector &surfaceDisplacement,
const mfem::Vector &surfaceDisplacementDirection,
mfem::Vector &vacuumDisplacementDirection
) const {
requireSurfaceSize(surfaceDisplacement);
requireSurfaceSize(surfaceDisplacementDirection);
requireVacuumSize(vacuumDisplacementDirection);
applyForward(surfaceDisplacementDirection, vacuumDisplacementDirection);
}
void PreparedFixedInfinityRadialVacuumExtension::applyJacobianTranspose(
const mfem::Vector &surfaceDisplacement,
const mfem::Vector &vacuumDisplacementDual,
mfem::Vector &surfaceDisplacementDual
) const {
requireSurfaceSize(surfaceDisplacement);
requireVacuumSize(vacuumDisplacementDual);
requireSurfaceSize(surfaceDisplacementDual);
applyTranspose(vacuumDisplacementDual, surfaceDisplacementDual);
}
void PreparedFixedInfinityRadialVacuumExtension::applyPullbackDerivative(
const mfem::Vector &surfaceDisplacement,
const mfem::Vector &surfaceDisplacementDirection,
const mfem::Vector &vacuumDisplacementDual,
mfem::Vector &surfaceDisplacementDualAction
) const {
requireSurfaceSize(surfaceDisplacement);
requireSurfaceSize(surfaceDisplacementDirection);
requireVacuumSize(vacuumDisplacementDual);
requireSurfaceSize(surfaceDisplacementDualAction);
surfaceDisplacementDualAction = 0.0;
}
PreparedFixedInfinityRadialVacuumExtension compileVacuumDeformationExtension(
const FixedInfinityRadialVacuumExtension &extension,
const RadialDeformationExtensionCompilationContext &context
) {
extension.validate();
return PreparedFixedInfinityRadialVacuumExtension(extension, context);
}
} // namespace mean_field::deformation