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.
This commit is contained in:
2026-09-01 11:50:13 -04:00
parent 0a7f18c5c7
commit 85500fef3b
40 changed files with 8924 additions and 1164 deletions

View File

@@ -5,6 +5,7 @@ module;
#include <concepts>
#include <cstddef>
#include <memory>
#include <optional>
#include <stdexcept>
#include <utility>
@@ -889,6 +890,198 @@ export namespace mean_field::field {
mfem::Array<int> m_trueToReduced;
};
/*
* Canonical coordinates on a scalar finite-element boundary.
*
* Unlike FieldBoundaryDofMap, this is not a row selection inside an
* existing physical field. It defines an independent dense coordinate
* vector whose entries are the locally owned scalar true DOFs on a
* semantic boundary.
*
* Local coordinates follow increasing MFEM true-DOF order. Global
* coordinates use the distributed-vector convention: ranks are ordered by
* communicator rank and each rank contributes its locally sorted block.
*/
class ScalarBoundaryDofMap final {
public:
ScalarBoundaryDofMap() = default;
ScalarBoundaryDofMap(
const int volumeTrueDofSize,
const mfem::Array<int> &boundaryTrueDofs,
const long long globalOffset,
const long long globalSize
)
: m_boundaryDofs(
volumeTrueDofSize,
boundaryTrueDofs
),
m_globalOffset(globalOffset),
m_globalSize(globalSize) {
if (m_globalOffset < 0) {
throw std::invalid_argument("ScalarBoundaryDofMap requires a non-negative global offset.");
}
if (m_globalSize < 0) {
throw std::invalid_argument("ScalarBoundaryDofMap requires a non-negative global size.");
}
if (m_globalOffset + local_size() > m_globalSize) {
throw std::invalid_argument(
"ScalarBoundaryDofMap local coordinates lie outside the global boundary coordinate vector."
);
}
}
[[nodiscard]] int volume_true_dof_size() const noexcept {
return m_boundaryDofs.full_size();
}
[[nodiscard]] int local_size() const noexcept {
return m_boundaryDofs.reduced_size();
}
[[nodiscard]] long long global_size() const noexcept {
return m_globalSize;
}
[[nodiscard]] long long global_offset() const noexcept {
return m_globalOffset;
}
[[nodiscard]] bool empty() const noexcept {
return local_size() == 0;
}
[[nodiscard]] const mfem::Array<int> &boundary_true_dofs() const noexcept {
return m_boundaryDofs.reduced_to_true();
}
[[nodiscard]] int volume_true_dof(const int localBoundaryDof) const {
return m_boundaryDofs.true_dof(localBoundaryDof);
}
[[nodiscard]] std::optional<int> local_boundary_dof(const int volumeTrueDof) const {
return m_boundaryDofs.reduced_dof(volumeTrueDof);
}
[[nodiscard]] bool contains_volume_true_dof(const int volumeTrueDof) const {
return m_boundaryDofs.contains_true_dof(volumeTrueDof);
}
[[nodiscard]] long long global_boundary_dof(const int localBoundaryDof) const {
if (localBoundaryDof < 0 || localBoundaryDof >= local_size()) {
throw std::out_of_range("Local boundary DOF is outside ScalarBoundaryDofMap.");
}
return m_globalOffset + localBoundaryDof;
}
void gather(
const mfem::Vector &volumeTrueValues,
mfem::Vector &boundaryValues
) const {
m_boundaryDofs.gather(volumeTrueValues, boundaryValues);
}
[[nodiscard]] mfem::Vector gather(const mfem::Vector &volumeTrueValues) const {
return m_boundaryDofs.gather(volumeTrueValues);
}
void scatter(
const mfem::Vector &boundaryValues,
mfem::Vector &volumeTrueValues
) const {
m_boundaryDofs.scatter(boundaryValues, volumeTrueValues);
}
[[nodiscard]] mfem::Vector scatter(const mfem::Vector &boundaryValues) const {
return m_boundaryDofs.scatter(boundaryValues);
}
private:
FieldDofMap m_boundaryDofs;
long long m_globalOffset{0};
long long m_globalSize{0};
};
template <
utils::domain::IsBoundary BoundaryT,
utils::domain::IsSchema SchemaT>
requires(SchemaT::template contains_boundary<BoundaryT>())
[[nodiscard]] ScalarBoundaryDofMap
make_scalar_boundary_dof_map(const mfem::ParFiniteElementSpace &finiteElementSpace) {
MFEM_VERIFY(
!finiteElementSpace.Nonconforming(),
"Scalar boundary true-DOF resolution currently requires a conforming mfem::ParFiniteElementSpace."
);
MFEM_VERIFY(finiteElementSpace.GetVDim() == 1, "ScalarBoundaryDofMap requires a scalar finite-element space.");
const mfem::Mesh *mesh = finiteElementSpace.GetMesh();
MFEM_VERIFY(mesh != nullptr, "Scalar boundary DOF resolution requires an MFEM mesh.");
mfem::Array<int> boundaryVDofMarker(finiteElementSpace.GetVSize());
boundaryVDofMarker = 0;
mfem::Array<int> boundaryElementVDofs;
for (int boundaryElement = 0; boundaryElement < mesh->GetNBE(); ++boundaryElement) {
if (!SchemaT::template boundary_attribute_matches<BoundaryT>(mesh->GetBdrAttribute(boundaryElement))) {
continue;
}
finiteElementSpace.GetBdrElementVDofs(boundaryElement, boundaryElementVDofs);
for (const int encodedVDof : boundaryElementVDofs) {
const int vdof = mfem::FiniteElementSpace::DecodeDof(encodedVDof);
MFEM_VERIFY(
vdof >= 0 && vdof < finiteElementSpace.GetVSize(),
"MFEM returned an invalid scalar boundary vector DOF."
);
boundaryVDofMarker[vdof] = 1;
}
}
finiteElementSpace.Synchronize(boundaryVDofMarker);
mfem::Array<int> boundaryTrueDofMarker(finiteElementSpace.GetTrueVSize());
boundaryTrueDofMarker = 0;
for (int vdof = 0; vdof < boundaryVDofMarker.Size(); ++vdof) {
if (boundaryVDofMarker[vdof] == 0) {
continue;
}
const int trueDof = finiteElementSpace.GetLocalTDofNumber(vdof);
if (trueDof < 0) {
continue;
}
MFEM_VERIFY(trueDof < boundaryTrueDofMarker.Size(), "MFEM returned an invalid scalar boundary true DOF.");
boundaryTrueDofMarker[trueDof] = 1;
}
mfem::Array<int> boundaryTrueDofs;
mfem::FiniteElementSpace::MarkerToList(boundaryTrueDofMarker, boundaryTrueDofs);
const long long localSize = boundaryTrueDofs.Size();
long long globalSize = 0;
long long globalOffset = 0;
MPI_Allreduce(&localSize, &globalSize, 1, MPI_LONG_LONG, MPI_SUM, finiteElementSpace.GetComm());
MPI_Exscan(&localSize, &globalOffset, 1, MPI_LONG_LONG, MPI_SUM, finiteElementSpace.GetComm());
if (finiteElementSpace.GetMyRank() == 0) {
globalOffset = 0;
}
MFEM_VERIFY(globalSize > 0, "The requested semantic boundary has no scalar true DOFs.");
return ScalarBoundaryDofMap(finiteElementSpace.GetTrueVSize(), boundaryTrueDofs, globalOffset, globalSize);
}
template <utils::domain::IsSchema SchemaT = utils::domain::CoreEnvelopeVacuumDomainSchema>
requires(SchemaT::template contains_boundary<utils::domain::StellarSurface>())
[[nodiscard]] ScalarBoundaryDofMap
make_stellar_surface_scalar_dof_map(const mfem::ParFiniteElementSpace &finiteElementSpace) {
return make_scalar_boundary_dof_map<utils::domain::StellarSurface, SchemaT>(finiteElementSpace);
}
/*
* Boundary rows expressed in a field's reduced solver ordering.
*

View File

@@ -3,6 +3,10 @@ module;
#include <concepts>
#include <string_view>
#ifndef MEAN_FIELD_UNIFORM_POLYNOMIAL_ORDER_INCREMENT
#define MEAN_FIELD_UNIFORM_POLYNOMIAL_ORDER_INCREMENT 0
#endif
export module mean_field:field.registry;
export import :field.base;
@@ -10,13 +14,16 @@ export import :quadrature.policy;
export import :utils.domain;
export namespace mean_field::field {
inline constexpr int uniformPolynomialOrderIncrement = MEAN_FIELD_UNIFORM_POLYNOMIAL_ORDER_INCREMENT;
static_assert(uniformPolynomialOrderIncrement >= 0);
// =========================================================================
// Density
// =========================================================================
struct Density {
static constexpr std::string_view name = "density";
static constexpr int scalarOrder = 2;
static constexpr int scalarOrder = 2 + uniformPolynomialOrderIncrement;
using Support = DomainSupport<utils::domain::Stellar>;
@@ -79,8 +86,8 @@ export namespace mean_field::field {
struct Gravity {
static constexpr std::string_view name = "gravity";
static constexpr int potentialOrder = 2;
static constexpr int fluxOrder = 2;
static constexpr int potentialOrder = 2 + uniformPolynomialOrderIncrement;
static constexpr int fluxOrder = 2 + uniformPolynomialOrderIncrement;
using Support = DomainSupport<utils::domain::All>;
@@ -147,7 +154,7 @@ export namespace mean_field::field {
struct Displacement {
static constexpr std::string_view name = "displacement";
static constexpr int vectorOrder = 3;
static constexpr int vectorOrder = 3 + uniformPolynomialOrderIncrement;
using Support = DomainSupport<utils::domain::All>;
@@ -229,7 +236,7 @@ export namespace mean_field::field {
struct Enthalpy {
static constexpr std::string_view name = "specific_enthalpy";
static constexpr int scalarOrder = 3;
static constexpr int scalarOrder = 3 + uniformPolynomialOrderIncrement;
using Support = DomainSupport<utils::domain::Stellar>;