module; #include #include #include #include #include #include #include #include #include #include #include #include #include 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::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 concept PreparedDomainDeformationOperator = requires( const std::remove_cvref_t &preparedDeformation, const mfem::Vector ¶meters, const mfem::Vector ¶meterDirection, const mfem::Vector &volumeDisplacementDual, mfem::Vector &volumeDisplacement, mfem::Vector ¶meterDual ) { { preparedDeformation.descriptor() } noexcept -> std::same_as; { preparedDeformation.parameterCount() } noexcept -> std::same_as; { preparedDeformation.surfaceDisplacementSize() } noexcept -> std::same_as; { preparedDeformation.volumeDisplacementSize() } noexcept -> std::same_as; { preparedDeformation.buildVolumeDisplacement(parameters, volumeDisplacement) } -> std::same_as; { preparedDeformation.applyJacobian(parameters, parameterDirection, volumeDisplacement) } -> std::same_as; { preparedDeformation.applyJacobianTranspose(parameters, volumeDisplacementDual, parameterDual) } -> std::same_as; { preparedDeformation.applyPullbackDerivative( parameters, parameterDirection, volumeDisplacementDual, parameterDual ) } -> std::same_as; }; 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(&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(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 ¶meters, 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 ¶meters, const mfem::Vector ¶meterDirection, 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 ¶meters, const mfem::Vector &volumeDisplacementDual, mfem::Vector ¶meterDual ) 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 ¶meters, const mfem::Vector ¶meterDirection, const mfem::Vector &volumeDisplacementDual, mfem::Vector ¶meterDualAction ) 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::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::quiet_NaN(); } ++m_actionStatistics.geometryInspections; return {.minimumJacobianDeterminant = globalMinimumDeterminant}; } [[nodiscard]] DomainDeformationGeometryReport buildValidatedVolumeDisplacement( const mfem::Vector ¶meters, 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(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(scalarTrueDof)] = VolumeDeformationOwner::StellarInterior; ++m_compositionReport.stellarInteriorOwnedScalarDofCount; if (hasVacuumSupport) { ++m_compositionReport.sharedSurfaceScalarDofCount; } } else { m_volumeOwners[static_cast(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 ¶meters) 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 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 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{ std::move(preparedSurface), std::move(preparedInterior), std::move(preparedVacuum), surfaceScalarSpace, volumeDisplacementSpace, logicalReferenceMesh }; } class PreparedDomainDeformationRuntime final { public: template requires(!std::same_as< std::remove_cvref_t, PreparedDomainDeformationRuntime>) explicit PreparedDomainDeformationRuntime(PreparedDeformation &&preparedDeformation) : m_implementation( std::make_unique>>( std::forward(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 ¶meters, mfem::Vector &volumeDisplacement ) const { m_implementation->buildVolumeDisplacement(parameters, volumeDisplacement); } void applyJacobian( const mfem::Vector ¶meters, const mfem::Vector ¶meterDirection, mfem::Vector &volumeDisplacementDirection ) const { m_implementation->applyJacobian(parameters, parameterDirection, volumeDisplacementDirection); } void applyJacobianTranspose( const mfem::Vector ¶meters, const mfem::Vector &volumeDisplacementDual, mfem::Vector ¶meterDual ) const { m_implementation->applyJacobianTranspose(parameters, volumeDisplacementDual, parameterDual); } void applyPullbackDerivative( const mfem::Vector ¶meters, const mfem::Vector ¶meterDirection, const mfem::Vector &volumeDisplacementDual, mfem::Vector ¶meterDualAction ) 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 ¶meters, 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 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 ¶meters, mfem::Vector &volumeDisplacement ) const override { m_preparedDeformation.buildVolumeDisplacement(parameters, volumeDisplacement); } void applyJacobian( const mfem::Vector ¶meters, const mfem::Vector ¶meterDirection, mfem::Vector &volumeDisplacementDirection ) const override { m_preparedDeformation.applyJacobian(parameters, parameterDirection, volumeDisplacementDirection); } void applyJacobianTranspose( const mfem::Vector ¶meters, const mfem::Vector &volumeDisplacementDual, mfem::Vector ¶meterDual ) const override { m_preparedDeformation.applyJacobianTranspose(parameters, volumeDisplacementDual, parameterDual); } void applyPullbackDerivative( const mfem::Vector ¶meters, const mfem::Vector ¶meterDirection, const mfem::Vector &volumeDisplacementDual, mfem::Vector ¶meterDualAction ) 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 ¶meters, mfem::Vector &volumeDisplacement, const double determinantFloor ) const override { return m_preparedDeformation.buildValidatedVolumeDisplacement( parameters, volumeDisplacement, determinantFloor ); } private: PreparedDeformation m_preparedDeformation; }; std::unique_ptr 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(*finiteElementModel.surfaceDeformationFes); const SurfaceDeformationCompilationContext surfaceContext{ *finiteElementModel.surfaceDeformationFes, surfaceDofMap }; auto preparedSurface = compileSurfaceDeformationPrescription(surfacePrescription, surfaceContext); const RadialDeformationExtensionCompilationContext extensionContext = makeRadialDeformationExtensionCompilationContext( *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