module; #include #include #include #include #include #include #include #include #include #include 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::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 buildTrueDofSupport( mfem::ParFiniteElementSpace &scalarFiniteElementSpace, const mfem::Array &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 localDofMarker(scalarFiniteElementSpace.GetVSize()); localDofMarker = 0; mfem::Array 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 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 processed(trueDofCount); processed = 0; mfem::Array 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::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::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 &globalCoordinates, std::vector &weights ) { mfem::Array 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::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 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::max()) { throw std::overflow_error("A global surface coordinate exceeds supported integer indexing."); } globalCoordinates.push_back(static_cast(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 gatherCounts( const int localCount, MPI_Comm communicator ) { int communicatorSize = 0; MPI_Comm_size(communicator, &communicatorSize); std::vector counts(static_cast(communicatorSize)); MPI_Allgather(&localCount, 1, MPI_INT, counts.data(), 1, MPI_INT, communicator); return counts; } [[nodiscard]] std::vector prefixOffsets(const std::vector &counts) { std::vector 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 &counts, const std::vector &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 &rowOffsets, const int scalarTrueDof, const int interpolationEntry ) { if (scalarTrueDof < 0 || scalarTrueDof + 1 >= static_cast(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 &support, const mfem::Vector &radialWeights, const std::vector &rowOffsets, const std::vector &surfaceCoordinates, const std::vector &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 &support, const mfem::Vector &radialWeights, const std::vector &rowOffsets, const std::vector &surfaceCoordinates, const std::vector &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 stellarMaterialMarker, mfem::Array 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::max() || globalOffset > std::numeric_limits::max()) { throw std::overflow_error("The logical radial extension surface vector exceeds MPI integer indexing."); } m_globalSurfaceDisplacementSize = static_cast(globalSurfaceSize); m_globalSurfaceDisplacementOffset = static_cast(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(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::epsilon() * m_infinitySurfaceLogicalRadius; const double radialTolerance = 128.0 * std::numeric_limits::epsilon() * m_infinitySurfaceLogicalRadius; for (int scalarTrueDof = 0; scalarTrueDof < m_scalarTrueDofCount; ++scalarTrueDof) { m_surfaceInterpolationRowOffsets[scalarTrueDof] = static_cast(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(locateLogicalBoundaryPoint( logicalReferenceMesh, infinitySurfaceBoundaryAttribute, infinitySurfaceTarget )); } } m_surfaceInterpolationRowOffsets[m_scalarTrueDofCount] = static_cast(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(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