Files
MeanField/libmeanfield/interface/deformation/domain_deformation.cppm
Emily Boudreaux 85500fef3b feat(surface): surface deformation prescriptions
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.
2026-09-01 11:50:13 -04:00

871 lines
42 KiB
C++

module;
#include <algorithm>
#include <cmath>
#include <compare>
#include <concepts>
#include <cstdint>
#include <limits>
#include <memory>
#include <stdexcept>
#include <type_traits>
#include <utility>
#include <vector>
#include <mfem.hpp>
#include <mpi.h>
export module mean_field:deformation.domain_deformation;
export import :deformation.interior_extension;
export import :deformation.nodal_radial_surface;
export import :deformation.radial_extensions;
export import :deformation.surface_prescription;
export import :deformation.vacuum_extension;
export import :fem;
export import :field.mfem;
export import :utils.domain;
export namespace mean_field::deformation {
enum class VolumeDeformationOwner : std::uint8_t { StellarInterior, Vacuum };
struct DomainDeformationDiscretizationDependencies final {
const mfem::Mesh *physicalMeshIdentity{nullptr};
const mfem::ParMesh *logicalReferenceMeshIdentity{nullptr};
const mfem::ParFiniteElementSpace *surfaceScalarSpaceIdentity{nullptr};
const mfem::ParFiniteElementSpace *volumeDisplacementSpaceIdentity{nullptr};
long physicalMeshSequence{-1};
long logicalReferenceMeshSequence{-1};
long surfaceScalarSpaceSequence{-1};
long volumeDisplacementSpaceSequence{-1};
[[nodiscard]] bool isCurrent() const noexcept {
return physicalMeshIdentity != nullptr && logicalReferenceMeshIdentity != nullptr &&
surfaceScalarSpaceIdentity != nullptr && volumeDisplacementSpaceIdentity != nullptr &&
physicalMeshIdentity->GetSequence() == physicalMeshSequence &&
logicalReferenceMeshIdentity->GetSequence() == logicalReferenceMeshSequence &&
surfaceScalarSpaceIdentity->GetSequence() == surfaceScalarSpaceSequence &&
volumeDisplacementSpaceIdentity->GetSequence() == volumeDisplacementSpaceSequence;
}
};
struct DomainDeformationCompositionReport final {
int scalarTrueDofCount{0};
int stellarInteriorOwnedScalarDofCount{0};
int vacuumOwnedScalarDofCount{0};
int sharedSurfaceScalarDofCount{0};
[[nodiscard]] constexpr int assignedScalarDofCount() const noexcept {
return stellarInteriorOwnedScalarDofCount + vacuumOwnedScalarDofCount;
}
constexpr auto operator<=>(const DomainDeformationCompositionReport &) const = default;
};
struct DomainDeformationGeometryReport final {
double minimumJacobianDeterminant{std::numeric_limits<double>::infinity()};
[[nodiscard]] bool isOrientationPreserving(const double determinantFloor = 0.0) const noexcept {
return std::isfinite(minimumJacobianDeterminant) && std::isfinite(determinantFloor) &&
determinantFloor >= 0.0 && minimumJacobianDeterminant > determinantFloor;
}
};
struct PreparedDomainDeformationActionStatistics final {
std::uint64_t volumeBuildApplications{0};
std::uint64_t jacobianApplications{0};
std::uint64_t jacobianTransposeApplications{0};
std::uint64_t pullbackDerivativeApplications{0};
std::uint64_t geometryInspections{0};
constexpr auto operator<=>(const PreparedDomainDeformationActionStatistics &) const = default;
};
template <typename Candidate>
concept PreparedDomainDeformationOperator = requires(
const std::remove_cvref_t<Candidate> &preparedDeformation,
const mfem::Vector &parameters,
const mfem::Vector &parameterDirection,
const mfem::Vector &volumeDisplacementDual,
mfem::Vector &volumeDisplacement,
mfem::Vector &parameterDual
) {
{ preparedDeformation.descriptor() } noexcept -> std::same_as<DomainDeformationDescriptor>;
{ preparedDeformation.parameterCount() } noexcept -> std::same_as<int>;
{ preparedDeformation.surfaceDisplacementSize() } noexcept -> std::same_as<int>;
{ preparedDeformation.volumeDisplacementSize() } noexcept -> std::same_as<int>;
{ preparedDeformation.buildVolumeDisplacement(parameters, volumeDisplacement) } -> std::same_as<void>;
{ preparedDeformation.applyJacobian(parameters, parameterDirection, volumeDisplacement) } -> std::same_as<void>;
{
preparedDeformation.applyJacobianTranspose(parameters, volumeDisplacementDual, parameterDual)
} -> std::same_as<void>;
{
preparedDeformation.applyPullbackDerivative(
parameters, parameterDirection, volumeDisplacementDual, parameterDual
)
} -> std::same_as<void>;
};
template <
PreparedSurfaceDeformationPrescription PreparedSurface,
PreparedInteriorDeformationExtension PreparedInterior,
PreparedVacuumDeformationExtension PreparedVacuum>
class PreparedDomainDeformation final {
public:
PreparedDomainDeformation(
PreparedSurface preparedSurface,
PreparedInterior preparedInterior,
PreparedVacuum preparedVacuum,
mfem::ParFiniteElementSpace &surfaceScalarSpace,
mfem::ParFiniteElementSpace &volumeDisplacementSpace,
mfem::ParMesh &logicalReferenceMesh
)
: m_surface(std::move(preparedSurface)),
m_interior(std::move(preparedInterior)),
m_vacuum(std::move(preparedVacuum)),
m_volumeDisplacementSpace(&volumeDisplacementSpace),
m_descriptor(makeDescriptor(
m_surface,
m_interior,
m_vacuum
)),
m_surfaceDisplacementWorkspace(surfaceDisplacementSize()),
m_surfaceDirectionWorkspace(surfaceDisplacementSize()),
m_interiorVolumeWorkspace(volumeDisplacementSize()),
m_vacuumVolumeWorkspace(volumeDisplacementSize()),
m_interiorVolumeDualWorkspace(volumeDisplacementSize()),
m_vacuumVolumeDualWorkspace(volumeDisplacementSize()),
m_interiorSurfaceDualWorkspace(surfaceDisplacementSize()),
m_vacuumSurfaceDualWorkspace(surfaceDisplacementSize()),
m_surfaceDualWorkspace(surfaceDisplacementSize()),
m_interiorSurfacePullbackWorkspace(surfaceDisplacementSize()),
m_vacuumSurfacePullbackWorkspace(surfaceDisplacementSize()),
m_surfacePullbackWorkspace(surfaceDisplacementSize()),
m_parameterPullbackWorkspace(parameterCount()),
m_volumeGridFunctionWorkspace(std::make_unique<mfem::ParGridFunction>(&volumeDisplacementSpace)) {
validateCompatibility(surfaceScalarSpace, volumeDisplacementSpace, logicalReferenceMesh);
compileOwnership();
const mfem::Mesh *physicalMesh = volumeDisplacementSpace.GetMesh();
m_discretizationDependencies = {
.physicalMeshIdentity = physicalMesh,
.logicalReferenceMeshIdentity = &logicalReferenceMesh,
.surfaceScalarSpaceIdentity = &surfaceScalarSpace,
.volumeDisplacementSpaceIdentity = &volumeDisplacementSpace,
.physicalMeshSequence = physicalMesh->GetSequence(),
.logicalReferenceMeshSequence = logicalReferenceMesh.GetSequence(),
.surfaceScalarSpaceSequence = surfaceScalarSpace.GetSequence(),
.volumeDisplacementSpaceSequence = volumeDisplacementSpace.GetSequence()
};
}
PreparedDomainDeformation(const PreparedDomainDeformation &) = delete;
PreparedDomainDeformation &operator=(const PreparedDomainDeformation &) = delete;
PreparedDomainDeformation(PreparedDomainDeformation &&) noexcept = default;
PreparedDomainDeformation &operator=(PreparedDomainDeformation &&) noexcept = default;
[[nodiscard]] DomainDeformationDescriptor descriptor() const noexcept {
return m_descriptor;
}
[[nodiscard]] int parameterCount() const noexcept {
return m_surface.parameterCount();
}
[[nodiscard]] int surfaceDisplacementSize() const noexcept {
return m_surface.surfaceDisplacementSize();
}
[[nodiscard]] int volumeDisplacementSize() const noexcept {
return m_interior.interiorDisplacementSize();
}
[[nodiscard]] int scalarTrueDofCount() const noexcept {
return m_interior.scalarTrueDofCount();
}
[[nodiscard]] int spatialDimension() const noexcept {
return m_descriptor.surfaceDeformation.spatialDimension;
}
[[nodiscard]] VolumeDeformationOwner volumeOwner(const int scalarTrueDof) const {
requireScalarTrueDof(scalarTrueDof);
return m_volumeOwners[static_cast<std::size_t>(scalarTrueDof)];
}
[[nodiscard]] bool isSharedSurfaceDof(const int scalarTrueDof) const {
requireScalarTrueDof(scalarTrueDof);
return m_interior.hasStellarSupport(scalarTrueDof) && m_vacuum.hasVacuumSupport(scalarTrueDof);
}
[[nodiscard]] const DomainDeformationCompositionReport &compositionReport() const noexcept {
return m_compositionReport;
}
[[nodiscard]] const DomainDeformationDiscretizationDependencies &discretizationDependencies() const noexcept {
return m_discretizationDependencies;
}
[[nodiscard]] bool matchesCurrentDiscretization() const noexcept {
return m_discretizationDependencies.isCurrent();
}
[[nodiscard]] const PreparedDomainDeformationActionStatistics &actionStatistics() const noexcept {
return m_actionStatistics;
}
[[nodiscard]] const PreparedSurface &surfaceDeformationPrescription() const noexcept {
return m_surface;
}
[[nodiscard]] const PreparedInterior &stellarInteriorExtension() const noexcept {
return m_interior;
}
[[nodiscard]] const PreparedVacuum &vacuumExtension() const noexcept {
return m_vacuum;
}
void buildVolumeDisplacement(
const mfem::Vector &parameters,
mfem::Vector &volumeDisplacement
) const {
requireCurrentDiscretization();
requireParameterSize(parameters);
requireVolumeSize(volumeDisplacement);
m_surface.buildSurfaceDisplacement(parameters, m_surfaceDisplacementWorkspace);
m_interior.buildInteriorDisplacement(m_surfaceDisplacementWorkspace, m_interiorVolumeWorkspace);
m_vacuum.buildVacuumDisplacement(m_surfaceDisplacementWorkspace, m_vacuumVolumeWorkspace);
mergeVolumeFields(m_interiorVolumeWorkspace, m_vacuumVolumeWorkspace, volumeDisplacement);
++m_actionStatistics.volumeBuildApplications;
}
void applyJacobian(
const mfem::Vector &parameters,
const mfem::Vector &parameterDirection,
mfem::Vector &volumeDisplacementDirection
) const {
requireCurrentDiscretization();
requireParameterSize(parameters);
requireParameterSize(parameterDirection);
requireVolumeSize(volumeDisplacementDirection);
m_surface.buildSurfaceDisplacement(parameters, m_surfaceDisplacementWorkspace);
m_surface.applyJacobian(parameters, parameterDirection, m_surfaceDirectionWorkspace);
m_interior.applyJacobian(
m_surfaceDisplacementWorkspace, m_surfaceDirectionWorkspace, m_interiorVolumeWorkspace
);
m_vacuum.applyJacobian(
m_surfaceDisplacementWorkspace, m_surfaceDirectionWorkspace, m_vacuumVolumeWorkspace
);
mergeVolumeFields(m_interiorVolumeWorkspace, m_vacuumVolumeWorkspace, volumeDisplacementDirection);
++m_actionStatistics.jacobianApplications;
}
void applyJacobianTranspose(
const mfem::Vector &parameters,
const mfem::Vector &volumeDisplacementDual,
mfem::Vector &parameterDual
) const {
requireCurrentDiscretization();
requireParameterSize(parameters);
requireVolumeSize(volumeDisplacementDual);
requireParameterSize(parameterDual);
m_surface.buildSurfaceDisplacement(parameters, m_surfaceDisplacementWorkspace);
splitVolumeDual(volumeDisplacementDual);
applyExtensionTransposes();
m_surface.applyJacobianTranspose(parameters, m_surfaceDualWorkspace, parameterDual);
++m_actionStatistics.jacobianTransposeApplications;
}
void applyPullbackDerivative(
const mfem::Vector &parameters,
const mfem::Vector &parameterDirection,
const mfem::Vector &volumeDisplacementDual,
mfem::Vector &parameterDualAction
) const {
requireCurrentDiscretization();
requireParameterSize(parameters);
requireParameterSize(parameterDirection);
requireVolumeSize(volumeDisplacementDual);
requireParameterSize(parameterDualAction);
m_surface.buildSurfaceDisplacement(parameters, m_surfaceDisplacementWorkspace);
m_surface.applyJacobian(parameters, parameterDirection, m_surfaceDirectionWorkspace);
splitVolumeDual(volumeDisplacementDual);
applyExtensionTransposes();
m_interior.applyPullbackDerivative(
m_surfaceDisplacementWorkspace, m_surfaceDirectionWorkspace, m_interiorVolumeDualWorkspace,
m_interiorSurfacePullbackWorkspace
);
m_vacuum.applyPullbackDerivative(
m_surfaceDisplacementWorkspace, m_surfaceDirectionWorkspace, m_vacuumVolumeDualWorkspace,
m_vacuumSurfacePullbackWorkspace
);
addSurfaceFields(
m_interiorSurfacePullbackWorkspace, m_vacuumSurfacePullbackWorkspace, m_surfacePullbackWorkspace
);
m_surface.applyJacobianTranspose(parameters, m_surfacePullbackWorkspace, parameterDualAction);
m_surface.applyPullbackDerivative(
parameters, parameterDirection, m_surfaceDualWorkspace, m_parameterPullbackWorkspace
);
parameterDualAction += m_parameterPullbackWorkspace;
++m_actionStatistics.pullbackDerivativeApplications;
}
[[nodiscard]] DomainDeformationGeometryReport
inspectMappedGeometry(const mfem::Vector &volumeDisplacement) const {
requireCurrentDiscretization();
requireVolumeSize(volumeDisplacement);
m_volumeGridFunctionWorkspace->SetFromTrueDofs(volumeDisplacement);
mfem::Mesh *mesh = m_volumeDisplacementSpace->GetMesh();
double localMinimumDeterminant = std::numeric_limits<double>::infinity();
int localGeometryIsFinite = 1;
for (int element = 0; element < mesh->GetNE(); ++element) {
mfem::ElementTransformation *transformation = mesh->GetElementTransformation(element);
const mfem::FiniteElement *finiteElement = m_volumeDisplacementSpace->GetFE(element);
// Positivity is a pointwise geometry requirement, not an
// integration-accuracy requirement. A rule only slightly
// above the displacement order can miss a narrow negative
// region of the determinant even when a downstream physics
// rule samples it. The determinant of a d-dimensional
// degree-p deformation gradient can vary at substantially
// higher order, so inspect at a conservative d*p scale.
const int geometryInspectionOrder =
std::max(finiteElement->GetOrder() + 2, 2 * spatialDimension() * finiteElement->GetOrder());
const mfem::IntegrationRule &rule =
mfem::IntRules.Get(transformation->GetGeometryType(), geometryInspectionOrder);
for (int point = 0; point < rule.GetNPoints(); ++point) {
transformation->SetIntPoint(&rule.IntPoint(point));
mfem::DenseMatrix deformationGradient;
m_volumeGridFunctionWorkspace->GetVectorGradient(*transformation, deformationGradient);
for (int component = 0; component < spatialDimension(); ++component) {
deformationGradient(component, component) += 1.0;
}
const double determinant = deformationGradient.Det();
if (!std::isfinite(determinant)) {
localGeometryIsFinite = 0;
} else {
localMinimumDeterminant = std::min(localMinimumDeterminant, determinant);
}
}
}
double globalMinimumDeterminant = 0.0;
int globalGeometryIsFinite = 0;
MPI_Allreduce(
&localMinimumDeterminant, &globalMinimumDeterminant, 1, MPI_DOUBLE, MPI_MIN,
m_volumeDisplacementSpace->GetComm()
);
MPI_Allreduce(
&localGeometryIsFinite, &globalGeometryIsFinite, 1, MPI_INT, MPI_MIN,
m_volumeDisplacementSpace->GetComm()
);
if (globalGeometryIsFinite == 0) {
globalMinimumDeterminant = std::numeric_limits<double>::quiet_NaN();
}
++m_actionStatistics.geometryInspections;
return {.minimumJacobianDeterminant = globalMinimumDeterminant};
}
[[nodiscard]] DomainDeformationGeometryReport buildValidatedVolumeDisplacement(
const mfem::Vector &parameters,
mfem::Vector &volumeDisplacement,
const double determinantFloor = 0.0
) const {
if (!std::isfinite(determinantFloor) || determinantFloor < 0.0) {
throw std::invalid_argument("The mapped-geometry determinant floor must be finite and non-negative.");
}
buildVolumeDisplacement(parameters, volumeDisplacement);
const DomainDeformationGeometryReport report = inspectMappedGeometry(volumeDisplacement);
if (!report.isOrientationPreserving(determinantFloor)) {
throw std::domain_error("The prepared domain deformation inverts at least one volume element.");
}
return report;
}
private:
[[nodiscard]] static DomainDeformationDescriptor makeDescriptor(
const PreparedSurface &surface,
const PreparedInterior &interior,
const PreparedVacuum &vacuum
) noexcept {
const SurfaceDeformationDescriptor surfaceDescriptor = surface.descriptor();
const InteriorDeformationExtensionDescriptor interiorDescriptor = interior.descriptor();
const VacuumDeformationExtensionDescriptor vacuumDescriptor = vacuum.descriptor();
return {
.surfaceDeformation = surfaceDescriptor,
.stellarInteriorExtension = interiorDescriptor,
.vacuumExtension = vacuumDescriptor,
.linearOnReferenceGeometry = surfaceDescriptor.linearOnReferenceGeometry &&
interiorDescriptor.linearOnReferenceGeometry &&
vacuumDescriptor.linearOnReferenceGeometry,
.requiresAuxiliarySolve =
interiorDescriptor.requiresAuxiliarySolve || vacuumDescriptor.requiresAuxiliarySolve,
.hasExactDerivativeTranspose = surfaceDescriptor.hasExactDerivativeTranspose &&
interiorDescriptor.hasExactDerivativeTranspose &&
vacuumDescriptor.hasExactDerivativeTranspose,
.hasExactPullbackDerivative = surfaceDescriptor.hasExactPullbackDerivative &&
interiorDescriptor.hasExactPullbackDerivative &&
vacuumDescriptor.hasExactPullbackDerivative
};
}
void validateCompatibility(
mfem::ParFiniteElementSpace &surfaceScalarSpace,
mfem::ParFiniteElementSpace &volumeDisplacementSpace,
mfem::ParMesh &logicalReferenceMesh
) const {
const mfem::Mesh *physicalMesh = volumeDisplacementSpace.GetMesh();
if (!m_descriptor.isValid()) {
throw std::invalid_argument("Prepared domain deformation descriptors are incompatible.");
}
if (!m_descriptor.supportsExactNewtonLinearization()) {
throw std::invalid_argument("Prepared domain deformation requires exact transpose and pullback paths.");
}
if (physicalMesh == nullptr || surfaceScalarSpace.GetMesh() != physicalMesh) {
throw std::invalid_argument("Prepared domain deformation spaces must share one physical mesh.");
}
if (logicalReferenceMesh.GetNE() != physicalMesh->GetNE() ||
logicalReferenceMesh.GetNBE() != physicalMesh->GetNBE()) {
throw std::invalid_argument("Prepared domain deformation requires the paired logical reference mesh.");
}
if (m_surface.surfaceDisplacementSize() != m_interior.surfaceDisplacementSize() ||
m_surface.surfaceDisplacementSize() != m_vacuum.surfaceDisplacementSize()) {
throw std::invalid_argument("Prepared deformation factors have incompatible surface trace sizes.");
}
if (m_interior.interiorDisplacementSize() != m_vacuum.vacuumDisplacementSize() ||
m_interior.interiorDisplacementSize() != volumeDisplacementSpace.GetTrueVSize()) {
throw std::invalid_argument("Prepared deformation factors have incompatible volume vector sizes.");
}
if (m_interior.scalarTrueDofCount() != m_vacuum.scalarTrueDofCount() ||
volumeDisplacementSpace.GetTrueVSize() != spatialDimension() * m_interior.scalarTrueDofCount()) {
throw std::invalid_argument("Prepared deformation factors have incompatible scalar volume topology.");
}
if (volumeDisplacementSpace.GetOrdering() != mfem::Ordering::byNODES) {
throw std::invalid_argument("Prepared domain deformation requires MFEM byNODES volume ordering.");
}
}
void compileOwnership() {
m_volumeOwners.resize(static_cast<std::size_t>(scalarTrueDofCount()));
m_compositionReport.scalarTrueDofCount = scalarTrueDofCount();
for (int scalarTrueDof = 0; scalarTrueDof < scalarTrueDofCount(); ++scalarTrueDof) {
const bool hasStellarSupport = m_interior.hasStellarSupport(scalarTrueDof);
const bool hasVacuumSupport = m_vacuum.hasVacuumSupport(scalarTrueDof);
if (!hasStellarSupport && !hasVacuumSupport) {
throw std::invalid_argument("A volume displacement DOF has no deformation-extension owner.");
}
if (hasStellarSupport) {
m_volumeOwners[static_cast<std::size_t>(scalarTrueDof)] = VolumeDeformationOwner::StellarInterior;
++m_compositionReport.stellarInteriorOwnedScalarDofCount;
if (hasVacuumSupport) {
++m_compositionReport.sharedSurfaceScalarDofCount;
}
} else {
m_volumeOwners[static_cast<std::size_t>(scalarTrueDof)] = VolumeDeformationOwner::Vacuum;
++m_compositionReport.vacuumOwnedScalarDofCount;
}
}
}
[[nodiscard]] int volumeVectorDof(
const int scalarTrueDof,
const int component
) const noexcept {
return scalarTrueDof + component * scalarTrueDofCount();
}
void mergeVolumeFields(
const mfem::Vector &interiorVolume,
const mfem::Vector &vacuumVolume,
mfem::Vector &volume
) const noexcept {
for (int scalarTrueDof = 0; scalarTrueDof < scalarTrueDofCount(); ++scalarTrueDof) {
const mfem::Vector &source = volumeOwner(scalarTrueDof) == VolumeDeformationOwner::StellarInterior
? interiorVolume
: vacuumVolume;
for (int component = 0; component < spatialDimension(); ++component) {
const int vectorDof = volumeVectorDof(scalarTrueDof, component);
volume(vectorDof) = source(vectorDof);
}
}
}
void splitVolumeDual(const mfem::Vector &volumeDual) const noexcept {
m_interiorVolumeDualWorkspace = 0.0;
m_vacuumVolumeDualWorkspace = 0.0;
for (int scalarTrueDof = 0; scalarTrueDof < scalarTrueDofCount(); ++scalarTrueDof) {
mfem::Vector &destination = volumeOwner(scalarTrueDof) == VolumeDeformationOwner::StellarInterior
? m_interiorVolumeDualWorkspace
: m_vacuumVolumeDualWorkspace;
for (int component = 0; component < spatialDimension(); ++component) {
const int vectorDof = volumeVectorDof(scalarTrueDof, component);
destination(vectorDof) = volumeDual(vectorDof);
}
}
}
void applyExtensionTransposes() const {
m_interior.applyJacobianTranspose(
m_surfaceDisplacementWorkspace, m_interiorVolumeDualWorkspace, m_interiorSurfaceDualWorkspace
);
m_vacuum.applyJacobianTranspose(
m_surfaceDisplacementWorkspace, m_vacuumVolumeDualWorkspace, m_vacuumSurfaceDualWorkspace
);
addSurfaceFields(m_interiorSurfaceDualWorkspace, m_vacuumSurfaceDualWorkspace, m_surfaceDualWorkspace);
}
static void addSurfaceFields(
const mfem::Vector &interior,
const mfem::Vector &vacuum,
mfem::Vector &sum
) {
sum = interior;
sum += vacuum;
}
void requireCurrentDiscretization() const {
if (!matchesCurrentDiscretization()) {
throw std::logic_error("Prepared domain deformation discretization dependencies are stale.");
}
}
void requireParameterSize(const mfem::Vector &parameters) const {
if (parameters.Size() != parameterCount()) {
throw std::invalid_argument("Prepared domain deformation received an incompatible parameter vector.");
}
}
void requireVolumeSize(const mfem::Vector &volume) const {
if (volume.Size() != volumeDisplacementSize()) {
throw std::invalid_argument("Prepared domain deformation received an incompatible volume vector.");
}
}
void requireScalarTrueDof(const int scalarTrueDof) const {
if (scalarTrueDof < 0 || scalarTrueDof >= scalarTrueDofCount()) {
throw std::out_of_range("Scalar true DOF is outside the prepared domain deformation.");
}
}
PreparedSurface m_surface;
PreparedInterior m_interior;
PreparedVacuum m_vacuum;
mfem::ParFiniteElementSpace *m_volumeDisplacementSpace;
DomainDeformationDescriptor m_descriptor;
DomainDeformationCompositionReport m_compositionReport;
DomainDeformationDiscretizationDependencies m_discretizationDependencies;
std::vector<VolumeDeformationOwner> m_volumeOwners;
mutable PreparedDomainDeformationActionStatistics m_actionStatistics;
mutable mfem::Vector m_surfaceDisplacementWorkspace;
mutable mfem::Vector m_surfaceDirectionWorkspace;
mutable mfem::Vector m_interiorVolumeWorkspace;
mutable mfem::Vector m_vacuumVolumeWorkspace;
mutable mfem::Vector m_interiorVolumeDualWorkspace;
mutable mfem::Vector m_vacuumVolumeDualWorkspace;
mutable mfem::Vector m_interiorSurfaceDualWorkspace;
mutable mfem::Vector m_vacuumSurfaceDualWorkspace;
mutable mfem::Vector m_surfaceDualWorkspace;
mutable mfem::Vector m_interiorSurfacePullbackWorkspace;
mutable mfem::Vector m_vacuumSurfacePullbackWorkspace;
mutable mfem::Vector m_surfacePullbackWorkspace;
mutable mfem::Vector m_parameterPullbackWorkspace;
mutable std::unique_ptr<mfem::ParGridFunction> m_volumeGridFunctionWorkspace;
};
template <
PreparedSurfaceDeformationPrescription PreparedSurface,
PreparedInteriorDeformationExtension PreparedInterior,
PreparedVacuumDeformationExtension PreparedVacuum>
[[nodiscard]] auto composePreparedDomainDeformation(
PreparedSurface preparedSurface,
PreparedInterior preparedInterior,
PreparedVacuum preparedVacuum,
mfem::ParFiniteElementSpace &surfaceScalarSpace,
mfem::ParFiniteElementSpace &volumeDisplacementSpace,
mfem::ParMesh &logicalReferenceMesh
) {
return PreparedDomainDeformation<PreparedSurface, PreparedInterior, PreparedVacuum>{
std::move(preparedSurface), std::move(preparedInterior), std::move(preparedVacuum),
surfaceScalarSpace, volumeDisplacementSpace, logicalReferenceMesh
};
}
class PreparedDomainDeformationRuntime final {
public:
template <PreparedDomainDeformationOperator PreparedDeformation>
requires(!std::same_as<
std::remove_cvref_t<PreparedDeformation>,
PreparedDomainDeformationRuntime>)
explicit PreparedDomainDeformationRuntime(PreparedDeformation &&preparedDeformation)
: m_implementation(
std::make_unique<Implementation<std::remove_cvref_t<PreparedDeformation>>>(
std::forward<PreparedDeformation>(preparedDeformation)
)
) {
}
PreparedDomainDeformationRuntime(const PreparedDomainDeformationRuntime &) = delete;
PreparedDomainDeformationRuntime &operator=(const PreparedDomainDeformationRuntime &) = delete;
PreparedDomainDeformationRuntime(PreparedDomainDeformationRuntime &&) noexcept = default;
PreparedDomainDeformationRuntime &operator=(PreparedDomainDeformationRuntime &&) noexcept = default;
[[nodiscard]] DomainDeformationDescriptor descriptor() const noexcept {
return m_implementation->descriptor();
}
[[nodiscard]] int parameterCount() const noexcept {
return m_implementation->parameterCount();
}
[[nodiscard]] int surfaceDisplacementSize() const noexcept {
return m_implementation->surfaceDisplacementSize();
}
[[nodiscard]] int volumeDisplacementSize() const noexcept {
return m_implementation->volumeDisplacementSize();
}
[[nodiscard]] bool matchesCurrentDiscretization() const noexcept {
return m_implementation->matchesCurrentDiscretization();
}
[[nodiscard]] DomainDeformationCompositionReport compositionReport() const noexcept {
return m_implementation->compositionReport();
}
[[nodiscard]] DomainDeformationDiscretizationDependencies discretizationDependencies() const noexcept {
return m_implementation->discretizationDependencies();
}
[[nodiscard]] PreparedDomainDeformationActionStatistics actionStatistics() const noexcept {
return m_implementation->actionStatistics();
}
void buildVolumeDisplacement(
const mfem::Vector &parameters,
mfem::Vector &volumeDisplacement
) const {
m_implementation->buildVolumeDisplacement(parameters, volumeDisplacement);
}
void applyJacobian(
const mfem::Vector &parameters,
const mfem::Vector &parameterDirection,
mfem::Vector &volumeDisplacementDirection
) const {
m_implementation->applyJacobian(parameters, parameterDirection, volumeDisplacementDirection);
}
void applyJacobianTranspose(
const mfem::Vector &parameters,
const mfem::Vector &volumeDisplacementDual,
mfem::Vector &parameterDual
) const {
m_implementation->applyJacobianTranspose(parameters, volumeDisplacementDual, parameterDual);
}
void applyPullbackDerivative(
const mfem::Vector &parameters,
const mfem::Vector &parameterDirection,
const mfem::Vector &volumeDisplacementDual,
mfem::Vector &parameterDualAction
) const {
m_implementation->applyPullbackDerivative(
parameters, parameterDirection, volumeDisplacementDual, parameterDualAction
);
}
[[nodiscard]] DomainDeformationGeometryReport
inspectMappedGeometry(const mfem::Vector &volumeDisplacement) const {
return m_implementation->inspectMappedGeometry(volumeDisplacement);
}
[[nodiscard]] DomainDeformationGeometryReport buildValidatedVolumeDisplacement(
const mfem::Vector &parameters,
mfem::Vector &volumeDisplacement,
const double determinantFloor = 0.0
) const {
return m_implementation->buildValidatedVolumeDisplacement(parameters, volumeDisplacement, determinantFloor);
}
private:
class Interface {
public:
virtual ~Interface() = default;
[[nodiscard]] virtual DomainDeformationDescriptor descriptor() const noexcept = 0;
[[nodiscard]] virtual int parameterCount() const noexcept = 0;
[[nodiscard]] virtual int surfaceDisplacementSize() const noexcept = 0;
[[nodiscard]] virtual int volumeDisplacementSize() const noexcept = 0;
[[nodiscard]] virtual bool matchesCurrentDiscretization() const noexcept = 0;
[[nodiscard]] virtual DomainDeformationCompositionReport compositionReport() const noexcept = 0;
[[nodiscard]] virtual DomainDeformationDiscretizationDependencies
discretizationDependencies() const noexcept = 0;
[[nodiscard]] virtual PreparedDomainDeformationActionStatistics actionStatistics() const noexcept = 0;
virtual void buildVolumeDisplacement(
const mfem::Vector &,
mfem::Vector &
) const = 0;
virtual void applyJacobian(
const mfem::Vector &,
const mfem::Vector &,
mfem::Vector &
) const = 0;
virtual void applyJacobianTranspose(
const mfem::Vector &,
const mfem::Vector &,
mfem::Vector &
) const = 0;
virtual void applyPullbackDerivative(
const mfem::Vector &,
const mfem::Vector &,
const mfem::Vector &,
mfem::Vector &
) const = 0;
[[nodiscard]] virtual DomainDeformationGeometryReport inspectMappedGeometry(const mfem::Vector &) const = 0;
[[nodiscard]] virtual DomainDeformationGeometryReport buildValidatedVolumeDisplacement(
const mfem::Vector &,
mfem::Vector &,
double
) const = 0;
};
template <PreparedDomainDeformationOperator PreparedDeformation> class Implementation final : public Interface {
public:
explicit Implementation(PreparedDeformation preparedDeformation)
: m_preparedDeformation(std::move(preparedDeformation)) {
}
[[nodiscard]] DomainDeformationDescriptor descriptor() const noexcept override {
return m_preparedDeformation.descriptor();
}
[[nodiscard]] int parameterCount() const noexcept override {
return m_preparedDeformation.parameterCount();
}
[[nodiscard]] int surfaceDisplacementSize() const noexcept override {
return m_preparedDeformation.surfaceDisplacementSize();
}
[[nodiscard]] int volumeDisplacementSize() const noexcept override {
return m_preparedDeformation.volumeDisplacementSize();
}
[[nodiscard]] bool matchesCurrentDiscretization() const noexcept override {
return m_preparedDeformation.matchesCurrentDiscretization();
}
[[nodiscard]] DomainDeformationCompositionReport compositionReport() const noexcept override {
return m_preparedDeformation.compositionReport();
}
[[nodiscard]] DomainDeformationDiscretizationDependencies
discretizationDependencies() const noexcept override {
return m_preparedDeformation.discretizationDependencies();
}
[[nodiscard]] PreparedDomainDeformationActionStatistics actionStatistics() const noexcept override {
return m_preparedDeformation.actionStatistics();
}
void buildVolumeDisplacement(
const mfem::Vector &parameters,
mfem::Vector &volumeDisplacement
) const override {
m_preparedDeformation.buildVolumeDisplacement(parameters, volumeDisplacement);
}
void applyJacobian(
const mfem::Vector &parameters,
const mfem::Vector &parameterDirection,
mfem::Vector &volumeDisplacementDirection
) const override {
m_preparedDeformation.applyJacobian(parameters, parameterDirection, volumeDisplacementDirection);
}
void applyJacobianTranspose(
const mfem::Vector &parameters,
const mfem::Vector &volumeDisplacementDual,
mfem::Vector &parameterDual
) const override {
m_preparedDeformation.applyJacobianTranspose(parameters, volumeDisplacementDual, parameterDual);
}
void applyPullbackDerivative(
const mfem::Vector &parameters,
const mfem::Vector &parameterDirection,
const mfem::Vector &volumeDisplacementDual,
mfem::Vector &parameterDualAction
) const override {
m_preparedDeformation.applyPullbackDerivative(
parameters, parameterDirection, volumeDisplacementDual, parameterDualAction
);
}
[[nodiscard]] DomainDeformationGeometryReport
inspectMappedGeometry(const mfem::Vector &volumeDisplacement) const override {
return m_preparedDeformation.inspectMappedGeometry(volumeDisplacement);
}
[[nodiscard]] DomainDeformationGeometryReport buildValidatedVolumeDisplacement(
const mfem::Vector &parameters,
mfem::Vector &volumeDisplacement,
const double determinantFloor
) const override {
return m_preparedDeformation.buildValidatedVolumeDisplacement(
parameters, volumeDisplacement, determinantFloor
);
}
private:
PreparedDeformation m_preparedDeformation;
};
std::unique_ptr<Interface> m_implementation;
};
template <
utils::domain::IsSchema SchemaT = utils::domain::CoreEnvelopeVacuumDomainSchema,
SurfaceDeformationPrescription SurfacePrescription,
InteriorDeformationExtension InteriorExtension,
VacuumDeformationExtension VacuumExtension>
requires SurfaceDeformationCompilable<
SurfacePrescription,
SurfaceDeformationCompilationContext> &&
InteriorDeformationExtensionCompilable<
InteriorExtension,
RadialDeformationExtensionCompilationContext> &&
VacuumDeformationExtensionCompilable<
VacuumExtension,
RadialDeformationExtensionCompilationContext>
[[nodiscard]] auto compileDomainDeformation(
const SurfacePrescription &surfacePrescription,
const InteriorExtension &interiorExtension,
const VacuumExtension &vacuumExtension,
fem::FEM &finiteElementModel
) {
if (!finiteElementModel.okay()) {
throw std::invalid_argument("Domain deformation compilation requires a complete finite-element model.");
}
const field::ScalarBoundaryDofMap surfaceDofMap =
field::make_stellar_surface_scalar_dof_map<SchemaT>(*finiteElementModel.surfaceDeformationFes);
const SurfaceDeformationCompilationContext surfaceContext{
*finiteElementModel.surfaceDeformationFes, surfaceDofMap
};
auto preparedSurface = compileSurfaceDeformationPrescription(surfacePrescription, surfaceContext);
const RadialDeformationExtensionCompilationContext extensionContext =
makeRadialDeformationExtensionCompilationContext<SchemaT>(
*finiteElementModel.surfaceDeformationFes, *finiteElementModel.displacementFes,
*finiteElementModel.logicalReferenceMesh
);
auto preparedInterior = compileInteriorDeformationExtension(interiorExtension, extensionContext);
auto preparedVacuum = compileVacuumDeformationExtension(vacuumExtension, extensionContext);
return composePreparedDomainDeformation(
std::move(preparedSurface), std::move(preparedInterior), std::move(preparedVacuum),
*finiteElementModel.surfaceDeformationFes, *finiteElementModel.displacementFes,
*finiteElementModel.logicalReferenceMesh
);
}
} // namespace mean_field::deformation