feat(surface): major work on implementing surface constraints in a presciption agnostic manner

This commit is contained in:
2026-08-30 16:41:14 -04:00
parent 36adfa1174
commit 0a7f18c5c7
95 changed files with 30144 additions and 25766 deletions

View File

@@ -1,6 +1,7 @@
module;
#include <array>
#include <cmath>
#include <concepts>
#include <cstddef>
#include <memory>
@@ -888,6 +889,282 @@ export namespace mean_field::field {
mfem::Array<int> m_trueToReduced;
};
/*
* Boundary rows expressed in a field's reduced solver ordering.
*
* This object is deliberately independent of any particular physical
* surface condition. Its template constructor below combines a field,
* a semantic boundary, and a domain schema. Consequently the same
* topology machinery can be used by any compiled surface formulation;
* it is not tied to enthalpy or pressure.
*/
class FieldBoundaryDofMap final {
public:
FieldBoundaryDofMap() = default;
FieldBoundaryDofMap(
const int fieldReducedSize,
const mfem::Array<int> &boundaryReducedDofs
)
: m_fieldReducedSize(fieldReducedSize),
m_boundaryReducedDofs(boundaryReducedDofs) {
if (m_fieldReducedSize < 0) {
throw std::invalid_argument("FieldBoundaryDofMap requires a non-negative field size.");
}
m_boundaryReducedDofMarker.SetSize(m_fieldReducedSize);
m_boundaryReducedDofMarker = 0;
int previousReducedDof = -1;
for (const int reducedDof : m_boundaryReducedDofs) {
if (reducedDof < 0 || reducedDof >= m_fieldReducedSize) {
throw std::invalid_argument("FieldBoundaryDofMap contains a DOF outside the reduced field vector.");
}
if (reducedDof <= previousReducedDof) {
throw std::invalid_argument("FieldBoundaryDofMap indices must be strictly increasing and unique.");
}
m_boundaryReducedDofMarker[reducedDof] = 1;
previousReducedDof = reducedDof;
}
}
[[nodiscard]] int field_size() const noexcept {
return m_fieldReducedSize;
}
[[nodiscard]] int size() const noexcept {
return m_boundaryReducedDofs.Size();
}
[[nodiscard]] bool empty() const noexcept {
return size() == 0;
}
[[nodiscard]] const mfem::Array<int> &reduced_dofs() const noexcept {
return m_boundaryReducedDofs;
}
[[nodiscard]] const mfem::Array<int> &reduced_dof_marker() const noexcept {
return m_boundaryReducedDofMarker;
}
[[nodiscard]] bool contains(const int reducedDof) const {
if (reducedDof < 0 || reducedDof >= m_fieldReducedSize) {
throw std::out_of_range("Reduced DOF index is outside FieldBoundaryDofMap.");
}
return m_boundaryReducedDofMarker[reducedDof] != 0;
}
private:
int m_fieldReducedSize{0};
mfem::Array<int> m_boundaryReducedDofs;
mfem::Array<int> m_boundaryReducedDofMarker;
};
/* Point-supported rows in a field's reduced solver ordering. */
class FieldPointDofMap final {
public:
FieldPointDofMap() = default;
FieldPointDofMap(
const int fieldReducedSize,
const mfem::Array<int> &pointReducedDofs
)
: m_selectedDofs(
fieldReducedSize,
pointReducedDofs
) {
}
[[nodiscard]] int field_size() const noexcept {
return m_selectedDofs.field_size();
}
[[nodiscard]] int size() const noexcept {
return m_selectedDofs.size();
}
[[nodiscard]] bool empty() const noexcept {
return m_selectedDofs.empty();
}
[[nodiscard]] const mfem::Array<int> &reduced_dofs() const noexcept {
return m_selectedDofs.reduced_dofs();
}
[[nodiscard]] const mfem::Array<int> &reduced_dof_marker() const noexcept {
return m_selectedDofs.reduced_dof_marker();
}
[[nodiscard]] bool contains(const int reducedDof) const {
return m_selectedDofs.contains(reducedDof);
}
private:
FieldBoundaryDofMap m_selectedDofs;
};
template <
MfemDomainField FieldT,
utils::domain::IsBoundary BoundaryT,
utils::domain::IsSchema SchemaT>
[[nodiscard]] FieldBoundaryDofMap make_field_boundary_dof_map(
const mfem::ParFiniteElementSpace &finiteElementSpace,
const FieldDofMap &fieldDofMap
) {
static_assert(
SchemaT::template contains_boundary<BoundaryT>(),
"The requested boundary is not registered in the supplied DomainSchema."
);
MFEM_VERIFY(
!finiteElementSpace.Nonconforming(),
"Field boundary true-DOF resolution currently requires a conforming mfem::ParFiniteElementSpace."
);
MFEM_VERIFY(
fieldDofMap.full_size() == finiteElementSpace.GetTrueVSize(),
"The field map and finite-element space have incompatible true-DOF sizes."
);
const mfem::Mesh *mesh = finiteElementSpace.GetMesh();
MFEM_VERIFY(mesh != nullptr, "Field 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 boundary vector DOF."
);
boundaryVDofMarker[vdof] = 1;
}
}
finiteElementSpace.Synchronize(boundaryVDofMarker);
mfem::Array<int> boundaryReducedDofMarker(fieldDofMap.reduced_size());
boundaryReducedDofMarker = 0;
for (int vdof = 0; vdof < boundaryVDofMarker.Size(); ++vdof) {
if (boundaryVDofMarker[vdof] == 0) {
continue;
}
const int trueDof = finiteElementSpace.GetLocalTDofNumber(vdof);
if (trueDof < 0) {
continue;
}
const std::optional<int> reducedDof = fieldDofMap.reduced_dof(trueDof);
MFEM_VERIFY(
reducedDof.has_value(),
"A boundary DOF selected for the field is absent from that field's reduced solver map."
);
boundaryReducedDofMarker[*reducedDof] = 1;
}
mfem::Array<int> boundaryReducedDofs;
mfem::FiniteElementSpace::MarkerToList(boundaryReducedDofMarker, boundaryReducedDofs);
return FieldBoundaryDofMap(fieldDofMap.reduced_size(), boundaryReducedDofs);
}
template <MfemDomainField FieldT>
[[nodiscard]] FieldPointDofMap make_field_point_dof_map(
const mfem::ParFiniteElementSpace &finiteElementSpace,
const FieldDofMap &fieldDofMap,
const mfem::Vector &point,
const double tolerance
) {
MFEM_VERIFY(
!finiteElementSpace.Nonconforming(),
"Field point true-DOF resolution currently requires a conforming mfem::ParFiniteElementSpace."
);
MFEM_VERIFY(
fieldDofMap.full_size() == finiteElementSpace.GetTrueVSize(),
"The field map and finite-element space have incompatible true-DOF sizes."
);
MFEM_VERIFY(
std::isfinite(tolerance) && tolerance >= 0.0, "The field point tolerance must be finite and non-negative."
);
const mfem::Mesh *mesh = finiteElementSpace.GetMesh();
MFEM_VERIFY(mesh != nullptr, "Field point DOF resolution requires an MFEM mesh.");
MFEM_VERIFY(
point.Size() == mesh->SpaceDimension(), "The requested field point has the wrong coordinate dimension."
);
mfem::Array<int> pointVDofMarker(finiteElementSpace.GetVSize());
pointVDofMarker = 0;
mfem::Array<int> vertexVDofs;
for (int vertex = 0; vertex < mesh->GetNV(); ++vertex) {
const mfem::real_t *coordinates = mesh->GetVertex(vertex);
double distanceSquared = 0.0;
for (int component = 0; component < point.Size(); ++component) {
const double difference = coordinates[component] - point(component);
distanceSquared += difference * difference;
}
if (std::sqrt(distanceSquared) > tolerance) {
continue;
}
finiteElementSpace.GetVertexVDofs(vertex, vertexVDofs);
for (const int encodedVDof : vertexVDofs) {
const int vdof = mfem::FiniteElementSpace::DecodeDof(encodedVDof);
MFEM_VERIFY(
vdof >= 0 && vdof < finiteElementSpace.GetVSize(), "MFEM returned an invalid point vector DOF."
);
pointVDofMarker[vdof] = 1;
}
}
finiteElementSpace.Synchronize(pointVDofMarker);
mfem::Array<int> pointReducedDofMarker(fieldDofMap.reduced_size());
pointReducedDofMarker = 0;
for (int vdof = 0; vdof < pointVDofMarker.Size(); ++vdof) {
if (pointVDofMarker[vdof] == 0) {
continue;
}
const int trueDof = finiteElementSpace.GetLocalTDofNumber(vdof);
if (trueDof < 0) {
continue;
}
const std::optional<int> reducedDof = fieldDofMap.reduced_dof(trueDof);
MFEM_VERIFY(
reducedDof.has_value(),
"A point DOF selected for the field is absent from that field's reduced solver map."
);
pointReducedDofMarker[*reducedDof] = 1;
}
mfem::Array<int> pointReducedDofs;
mfem::FiniteElementSpace::MarkerToList(pointReducedDofMarker, pointReducedDofs);
const long long localPointDofCount = pointReducedDofs.Size();
long long globalPointDofCount = 0;
MPI_Allreduce(
&localPointDofCount, &globalPointDofCount, 1, MPI_LONG_LONG, MPI_SUM, finiteElementSpace.GetComm()
);
MFEM_VERIFY(
globalPointDofCount == finiteElementSpace.GetVDim(),
"The requested geometric point must identify exactly one field vertex globally."
);
return FieldPointDofMap(fieldDofMap.reduced_size(), pointReducedDofs);
}
/*
* Canonical adapter between an MFEM GridFunction and a reduced field
* vector.
@@ -1015,9 +1292,6 @@ export namespace mean_field::field {
[[nodiscard]]
FieldDofGridFunctionAdapter
make_field_dof_grid_function_adapter(const mfem::ParFiniteElementSpace &finiteElementSpace) {
return FieldDofGridFunctionAdapter(
make_field_dof_map<FieldT, SchemaT>(finiteElementSpace),
finiteElementSpace
);
return FieldDofGridFunctionAdapter(make_field_dof_map<FieldT, SchemaT>(finiteElementSpace), finiteElementSpace);
}
} // namespace mean_field::field