Files
MeanField/libmeanfield/interface/field/field_mfem.cppm
Emily Boudreaux 0f3ca8050b feat(field-support): added field support system, mid migration
currently the barotope and the pressure force operator are migrated to the new support system
2026-08-23 10:13:53 -04:00

907 lines
32 KiB
C++

module;
#include <array>
#include <concepts>
#include <cstddef>
#include <memory>
#include <stdexcept>
#include <mfem.hpp>
export module mean_field:field.mfem;
export import :field.registry;
namespace mean_field::field::detail {
template <typename T> inline constexpr bool alwaysFalse = false;
// -------------------------------------------------------------------------
// MFEM finite-element collection construction
// -------------------------------------------------------------------------
template <typename SpaceT> struct FecFor;
template <> struct FecFor<L2> {
static std::unique_ptr<mfem::FiniteElementCollection> make(
int familyOrder,
int dimension
) {
return std::make_unique<mfem::L2_FECollection>(familyOrder, dimension);
}
};
template <> struct FecFor<H1> {
static std::unique_ptr<mfem::FiniteElementCollection> make(
int familyOrder,
int dimension
) {
return std::make_unique<mfem::H1_FECollection>(familyOrder, dimension);
}
};
template <> struct FecFor<RT> {
static std::unique_ptr<mfem::FiniteElementCollection> make(
int familyOrder,
int dimension
) {
return std::make_unique<mfem::RT_FECollection>(familyOrder, dimension);
}
};
template <> struct FecFor<ND> {
static std::unique_ptr<mfem::FiniteElementCollection> make(
int familyOrder,
int dimension
) {
return std::make_unique<mfem::ND_FECollection>(familyOrder, dimension);
}
};
// -------------------------------------------------------------------------
// MFEM polynomial-order interpretation
//
// familyOrder is the collection constructor argument.
//
// For RT_p:
// value order = p + 1
// divergence order = p
// normal-trace order = p
//
// This distinction is what allows Disc<RT, p> and Disc<L2, p> to form a
// compatible pair while still giving different value-shape orders.
// -------------------------------------------------------------------------
template <typename OperandT> struct MfemOperandOrder;
template <RegisteredQuantity QuantityT, FieldOperationTag OperationT>
struct MfemOperandOrder<Operand<QuantityT, OperationT>> {
static constexpr int orderValue = []() consteval {
if constexpr (GlobalScalarQuantity<QuantityT>) {
static_assert(
std::same_as<OperationT, FieldOperation::Value>, "Global scalars support only the value operation."
);
return 0;
} else {
using Space = typename QuantityT::Space;
constexpr int familyOrder = QuantityT::familyOrder;
if constexpr (std::same_as<OperationT, FieldOperation::Value>) {
if constexpr (std::same_as<Space, RT>) {
return familyOrder + 1;
} else {
return familyOrder;
}
} else if constexpr (std::same_as<OperationT, FieldOperation::Divergence>) {
static_assert(
std::same_as<Space, RT>, "Only RT quantities currently support the divergence "
"polynomial-order rule."
);
return familyOrder;
} else if constexpr (std::same_as<OperationT, FieldOperation::Gradient>) {
static_assert(
std::same_as<Space, H1>, "Only H1 quantities currently support the gradient "
"polynomial-order rule."
);
return familyOrder > 0 ? familyOrder - 1 : 0;
} else if constexpr (std::same_as<OperationT, FieldOperation::Curl>) {
static_assert(
std::same_as<Space, ND>, "Only ND quantities currently support the curl "
"polynomial-order rule."
);
return familyOrder > 0 ? familyOrder - 1 : 0;
} else if constexpr (std::same_as<OperationT, FieldOperation::NormalTrace>) {
static_assert(
std::same_as<Space, RT>, "Only RT quantities currently support the normal-trace "
"polynomial-order rule."
);
return familyOrder;
} else {
static_assert(alwaysFalse<OperationT>, "Unsupported MFEM field operation.");
}
}
}();
};
// -------------------------------------------------------------------------
// Static polynomial-order contribution of an entire form
// -------------------------------------------------------------------------
template <typename FormT> struct MfemFormOrder;
template <auto PolicyKeyV, std::size_t DynamicOrderCountV, FieldOperand... OperandTs>
struct MfemFormOrder<FormSpec<PolicyKeyV, DynamicOrderCountV, OperandTs...>> {
static constexpr int staticOrder = (MfemOperandOrder<OperandTs>::orderValue + ... + 0);
};
// -------------------------------------------------------------------------
// MFEM vector-dimension and ordering rules
//
// Vector H1/L2 fields are represented using multiple copies of a scalar
// finite-element space. RT and ND elements are intrinsically vector-valued
// and therefore use vdim = 1.
// -------------------------------------------------------------------------
template <FieldQuantity QuantityT> int get_vdim(int spaceDimension) {
if (spaceDimension <= 0) {
throw std::invalid_argument("Space dimension must be positive.");
}
if constexpr (QuantityT::rankValue == 0) {
return 1;
} else if constexpr (
std::same_as<typename QuantityT::Space, H1> || std::same_as<typename QuantityT::Space, L2>
) {
return spaceDimension;
} else {
return 1;
}
}
template <FieldQuantity QuantityT> constexpr mfem::Ordering::Type get_ordering() {
if constexpr (
QuantityT::rankValue == 1 &&
(std::same_as<typename QuantityT::Space, H1> || std::same_as<typename QuantityT::Space, L2>)
) {
return mfem::Ordering::byVDIM;
} else {
return mfem::Ordering::byNODES;
}
}
// -------------------------------------------------------------------------
// Quantity-specific MFEM realization
//
// Backend choices that are part of a field definition live here rather
// than leaking into FEM setup or call sites.
// -------------------------------------------------------------------------
template <FieldQuantity QuantityT> struct MfemQuantityTraits {
static std::unique_ptr<mfem::FiniteElementCollection> make_fec(int dimension) {
return FecFor<typename QuantityT::Space>::make(QuantityT::familyOrder, dimension);
}
static constexpr mfem::Ordering::Type ordering = get_ordering<QuantityT>();
};
template <> struct MfemQuantityTraits<Gravity::Flux> {
static std::unique_ptr<mfem::FiniteElementCollection> make_fec(int dimension) {
return std::make_unique<mfem::RT_FECollection>(
Gravity::Flux::familyOrder, dimension, mfem::BasisType::GaussLobatto, mfem::BasisType::IntegratedGLL
);
}
static constexpr mfem::Ordering::Type ordering = mfem::Ordering::byNODES;
};
template <> struct MfemQuantityTraits<Displacement::Vector> {
static std::unique_ptr<mfem::FiniteElementCollection> make_fec(int dimension) {
return FecFor<H1>::make(Displacement::Vector::familyOrder, dimension);
}
static constexpr mfem::Ordering::Type ordering = mfem::Ordering::byNODES;
};
} // namespace mean_field::field::detail
export namespace mean_field::field {
// -------------------------------------------------------------------------
// User-facing field type
//
// The object itself is currently a zero-cost compile-time descriptor:
//
// Field<Gravity> gravityField;
//
// MFEM construction and typed quadrature-query generation are provided as
// static operations. Runtime ownership can later be added without changing
// Gravity, Displacement, or their form definitions.
// -------------------------------------------------------------------------
template <FieldTag TagT> class Field {
public:
using Tag = TagT;
// ---------------------------------------------------------------------
// MFEM finite-element collection construction
// ---------------------------------------------------------------------
template <FieldQuantity QuantityT>
requires typeListContains<
QuantityT,
typename TagT::Quantities>
static std::unique_ptr<mfem::FiniteElementCollection> make_fec(int dimension) {
if (dimension <= 0) {
throw std::invalid_argument("Mesh dimension must be positive.");
}
return detail::MfemQuantityTraits<QuantityT>::make_fec(dimension);
}
// ---------------------------------------------------------------------
// MFEM parallel finite-element space construction
//
// The finite-element collection must outlive the returned space.
// ---------------------------------------------------------------------
template <FieldQuantity QuantityT>
requires typeListContains<
QuantityT,
typename TagT::Quantities>
static std::unique_ptr<mfem::ParFiniteElementSpace> make_fespace(
mfem::ParMesh &mesh,
mfem::FiniteElementCollection &finiteElementCollection
) {
return std::make_unique<mfem::ParFiniteElementSpace>(
&mesh, &finiteElementCollection, detail::get_vdim<QuantityT>(mesh.SpaceDimension()),
detail::MfemQuantityTraits<QuantityT>::ordering
);
}
// ---------------------------------------------------------------------
// Typed quadrature-query construction
//
// geometryWeightOrder is supplied at runtime because it depends on the
// actual element transformation.
//
// dynamicOrders contains the form-specific polynomial orders that are
// not represented by registered compile-time quantities.
//
// Examples:
//
// Density::Form::CenterOfMass:
// { positionOrder }
//
// Gravity source forms need no dynamic orders because density and
// potential are both registered quantities.
//
// The completed base order is stored in Query::base_order, so Policy
// does not need to understand divergence, RT conventions, or individual
// field layouts.
// ---------------------------------------------------------------------
template <FieldForm FormT>
requires typeListContains<
FormT,
typename TagT::FormList>
static constexpr quadrature::Query make_query(
quadrature::QuadratureRole role,
int geometryWeightOrder,
std::array<
int,
FormT::dynamicOrderCount> dynamicOrders = {},
utils::DOMAINS domain = utils::DOMAINS::ALL,
quadrature::MappingKind mapping = quadrature::MappingKind::none
) {
if (geometryWeightOrder < 0) {
throw std::invalid_argument("Geometry weight order cannot be negative.");
}
int baseOrder = detail::MfemFormOrder<FormT>::staticOrder + geometryWeightOrder;
for (const int dynamicOrder : dynamicOrders) {
if (dynamicOrder < 0) {
throw std::invalid_argument("Dynamic polynomial orders cannot be negative.");
}
baseOrder += dynamicOrder;
}
return {
.term = FormT::policyKey,
.role = role,
.domain = domain,
.mapping = mapping,
.trial_order = 0,
.test_order = 0,
.coefficient_order = 0,
.geometry_weight_order = geometryWeightOrder,
.base_order = baseOrder
};
}
};
static_assert(FieldTag<Gravity>);
static_assert(FieldTag<Displacement>);
static_assert(FieldTag<Density>);
static_assert(FieldTag<BarotropicConstant>);
/*
* Field-support realization onto MFEM element and DOF indices.
*
* A field's compile-time Support is declared in field.registry.
* These utilities resolve that semantic support through a DomainSchema
* onto a concrete MFEM finite-element space.
*
* Important:
*
* active DOFs = union of DOFs touched by supported elements
*
* This is deliberately NOT implemented as "remove every DOF touched by
* an unsupported element". For continuous spaces such as H1, a DOF on
* the Stellar/Vacuum interface is shared by elements on both sides and
* remains an active stellar-field DOF.
*/
struct FieldLocalDofSupport {
/*
* Marker in local/vector-DOF numbering.
*
* Size == finiteElementSpace.GetVSize().
* Entries are 1 for active DOFs and 0 otherwise.
*/
mfem::Array<int> activeVDofMarker;
/*
* Sorted MFEM local/vector DOF indices.
*/
mfem::Array<int> activeVDofs;
mfem::Array<int> inactiveVDofs;
};
struct FieldDofSupport {
/*
* Local/vector-DOF information.
*
* For a ParFiniteElementSpace the marker is synchronized across
* neighboring ranks before these lists are constructed, so a shared
* DOF is active on every rank carrying it if any rank has a supported
* element touching it.
*/
mfem::Array<int> activeVDofMarker;
mfem::Array<int> activeVDofs;
mfem::Array<int> inactiveVDofs;
/*
* True-DOF information owned by this MPI rank.
*
* Size of activeTrueDofMarker == GetTrueVSize().
*/
mfem::Array<int> activeTrueDofMarker;
mfem::Array<int> activeTrueDofs;
mfem::Array<int> inactiveTrueDofs;
};
template <typename FieldT>
concept MfemDomainField = FieldTag<FieldT> && DomainSupportedField<FieldT>;
template <
MfemDomainField FieldT,
utils::domain::IsSchema SchemaT>
[[nodiscard]]
bool element_is_in_field_support(
const mfem::Mesh &mesh,
const int elementId
) {
using DomainT = FieldDomainT<FieldT>;
static_assert(
SchemaT::template contains_domain<DomainT>(), "The field support is not completely registered in the "
"supplied DomainSchema."
);
MFEM_VERIFY(
elementId >= 0 && elementId < mesh.GetNE(), "The requested field-support element ID is outside the mesh."
);
return SchemaT::template attribute_belongs_to<DomainT>(mesh.GetAttribute(elementId));
}
namespace detail {
inline void build_marker_lists(
const mfem::Array<int> &activeMarker,
mfem::Array<int> &activeDofs,
mfem::Array<int> &inactiveDofs
) {
mfem::FiniteElementSpace::MarkerToList(activeMarker, activeDofs);
mfem::Array<int> inactiveMarker(activeMarker.Size());
for (int dofId = 0; dofId < activeMarker.Size(); ++dofId) {
inactiveMarker[dofId] = activeMarker[dofId] == 0 ? 1 : 0;
}
mfem::FiniteElementSpace::MarkerToList(inactiveMarker, inactiveDofs);
}
template <
MfemDomainField FieldT,
utils::domain::IsSchema SchemaT>
[[nodiscard]]
mfem::Array<int> build_local_active_vdof_marker(const mfem::FiniteElementSpace &finiteElementSpace) {
using DomainT = FieldDomainT<FieldT>;
static_assert(
SchemaT::template contains_domain<DomainT>(), "The field support is not completely registered in the "
"supplied DomainSchema."
);
const mfem::Mesh *mesh = finiteElementSpace.GetMesh();
MFEM_VERIFY(mesh != nullptr, "Field-support DOF resolution requires an MFEM mesh.");
MFEM_VERIFY(
finiteElementSpace.GetNE() == mesh->GetNE(), "The finite-element space and mesh have incompatible "
"element counts."
);
mfem::Array<int> activeMarker(finiteElementSpace.GetVSize());
activeMarker = 0;
mfem::Array<int> elementVDofs;
for (int elementId = 0; elementId < mesh->GetNE(); ++elementId) {
const int materialId = mesh->GetAttribute(elementId);
if (!SchemaT::template attribute_belongs_to<DomainT>(materialId)) {
continue;
}
finiteElementSpace.GetElementVDofs(elementId, elementVDofs);
for (int localIndex = 0; localIndex < elementVDofs.Size(); ++localIndex) {
/*
* MFEM can encode orientation in a DOF index by using a
* negative value. DecodeDof removes that orientation sign
* and returns the actual local/vector DOF index.
*/
const int vdof = mfem::FiniteElementSpace::DecodeDof(elementVDofs[localIndex]);
MFEM_VERIFY(
vdof >= 0 && vdof < finiteElementSpace.GetVSize(),
"MFEM returned an invalid element vector DOF."
);
activeMarker[vdof] = 1;
}
}
return activeMarker;
}
} // namespace detail
/*
* Serial/local support resolution.
*
* This works with any mfem::FiniteElementSpace and is particularly
* useful for topology/unit tests.
*
* The returned indices use MFEM local/vector-DOF numbering, not
* true-DOF numbering.
*/
template <
MfemDomainField FieldT,
utils::domain::IsSchema SchemaT>
[[nodiscard]]
FieldLocalDofSupport resolve_field_local_dof_support(const mfem::FiniteElementSpace &finiteElementSpace) {
FieldLocalDofSupport result;
result.activeVDofMarker = detail::build_local_active_vdof_marker<FieldT, SchemaT>(finiteElementSpace);
detail::build_marker_lists(result.activeVDofMarker, result.activeVDofs, result.inactiveVDofs);
return result;
}
/*
* Parallel production support resolution.
*
* This additionally converts the field support to the locally-owned
* true-DOF numbering used by nonlinear vectors and operators.
*
* For now this intentionally requires a conforming ParFiniteElementSpace.
* MFEM's nonconforming spaces require an additional constraint/conforming-
* DOF projection step; silently treating their local DOFs as ordinary
* true DOFs would be incorrect.
*/
template <
MfemDomainField FieldT,
utils::domain::IsSchema SchemaT>
[[nodiscard]]
FieldDofSupport resolve_field_dof_support(const mfem::ParFiniteElementSpace &finiteElementSpace) {
FieldDofSupport result;
MFEM_VERIFY(
!finiteElementSpace.Nonconforming(), "Field-support true-DOF resolution currently requires a "
"conforming mfem::ParFiniteElementSpace."
);
result.activeVDofMarker = detail::build_local_active_vdof_marker<FieldT, SchemaT>(finiteElementSpace);
/*
* Shared H1/RT DOFs can lie on an MPI partition boundary.
*
* If a supported element exists on one rank and the shared DOF also
* exists on a neighboring rank whose local elements are unsupported,
* that DOF must nevertheless be active globally.
*
* MFEM Synchronize performs the required OR-like synchronization of
* the marker across shared local DOFs.
*/
finiteElementSpace.Synchronize(result.activeVDofMarker);
detail::build_marker_lists(result.activeVDofMarker, result.activeVDofs, result.inactiveVDofs);
result.activeTrueDofMarker.SetSize(finiteElementSpace.GetTrueVSize());
result.activeTrueDofMarker = 0;
for (int vdof = 0; vdof < result.activeVDofMarker.Size(); ++vdof) {
if (result.activeVDofMarker[vdof] == 0) {
continue;
}
/*
* GetLocalTDofNumber returns the locally-owned true-DOF index
* for this local/vector DOF, or -1 when this rank does not own
* the shared true DOF.
*
* Because activeVDofMarker was synchronized first, the owning
* rank will also see the active marker.
*/
const int trueDof = finiteElementSpace.GetLocalTDofNumber(vdof);
if (trueDof < 0) {
continue;
}
MFEM_VERIFY(trueDof < result.activeTrueDofMarker.Size(), "MFEM returned an invalid local true DOF.");
result.activeTrueDofMarker[trueDof] = 1;
}
detail::build_marker_lists(result.activeTrueDofMarker, result.activeTrueDofs, result.inactiveTrueDofs);
return result;
}
/*
* Canonical correspondence between a dense reduced field vector and
* the selected MFEM true DOFs representing that field.
*
* The map contains no field, domain, mesh, or solver policy. It is an
* immutable indexing object once constructed:
*
* reduced index i
* |
* v
* reducedToTrue[i]
* |
* v
* MFEM true DOF
*
* trueToReduced supplies the inverse map. Unsupported true DOFs carry
* the sentinel -1.
*
* The reduced-to-true list is required to be strictly increasing.
* This makes reduced ordering deterministic and agrees with the
* canonical ordering produced by MFEM MarkerToList().
*/
class FieldDofMap {
public:
FieldDofMap() = default;
FieldDofMap(
const int fullTrueDofSize,
const mfem::Array<int> &reducedToTrue
) {
if (fullTrueDofSize < 0) {
throw std::invalid_argument("FieldDofMap requires a non-negative full true-DOF size.");
}
m_fullTrueDofSize = fullTrueDofSize;
m_reducedToTrue.SetSize(reducedToTrue.Size());
m_trueToReduced.SetSize(m_fullTrueDofSize);
m_trueToReduced = -1;
int previousTrueDof = -1;
for (int reducedDof = 0; reducedDof < reducedToTrue.Size(); ++reducedDof) {
const int trueDof = reducedToTrue[reducedDof];
if (trueDof < 0 || trueDof >= m_fullTrueDofSize) {
throw std::invalid_argument(
"FieldDofMap contains a true DOF outside the full "
"true-DOF space."
);
}
if (reducedDof > 0 && trueDof <= previousTrueDof) {
throw std::invalid_argument(
"FieldDofMap reduced-to-true indices must be "
"strictly increasing and unique."
);
}
m_reducedToTrue[reducedDof] = trueDof;
m_trueToReduced[trueDof] = reducedDof;
previousTrueDof = trueDof;
}
}
/*
* Construct directly from the support result produced by
* resolve_field_dof_support().
*
* The marker is checked against the active true-DOF list so that
* an internally inconsistent FieldDofSupport cannot silently
* produce a solver map.
*/
explicit FieldDofMap(const FieldDofSupport &support)
: FieldDofMap(
support.activeTrueDofMarker.Size(),
support.activeTrueDofs
) {
for (int trueDof = 0; trueDof < m_fullTrueDofSize; ++trueDof) {
const bool markerSaysActive = support.activeTrueDofMarker[trueDof] != 0;
const bool mapSaysActive = m_trueToReduced[trueDof] >= 0;
if (markerSaysActive != mapSaysActive) {
throw std::invalid_argument(
"FieldDofSupport active marker and active true-DOF "
"list are inconsistent."
);
}
}
}
[[nodiscard]]
int full_size() const noexcept {
return m_fullTrueDofSize;
}
[[nodiscard]]
int reduced_size() const noexcept {
return m_reducedToTrue.Size();
}
[[nodiscard]]
int inactive_size() const noexcept {
return full_size() - reduced_size();
}
/*
* Because reducedToTrue is strictly increasing, a map containing
* every true DOF necessarily has
*
* reducedToTrue[i] == i.
*/
[[nodiscard]]
bool is_identity() const noexcept {
return reduced_size() == full_size();
}
[[nodiscard]]
const mfem::Array<int> &reduced_to_true() const noexcept {
return m_reducedToTrue;
}
/*
* Values are:
*
* >= 0 reduced DOF index
* -1 unsupported/inactive true DOF
*/
[[nodiscard]]
const mfem::Array<int> &true_to_reduced() const noexcept {
return m_trueToReduced;
}
[[nodiscard]]
bool contains_true_dof(const int trueDof) const {
validate_true_dof(trueDof);
return m_trueToReduced[trueDof] >= 0;
}
[[nodiscard]]
int true_dof(const int reducedDof) const {
if (reducedDof < 0 || reducedDof >= reduced_size()) {
throw std::out_of_range("Reduced DOF index is outside FieldDofMap.");
}
return m_reducedToTrue[reducedDof];
}
[[nodiscard]]
std::optional<int> reduced_dof(const int trueDof) const {
validate_true_dof(trueDof);
const int reducedDof = m_trueToReduced[trueDof];
if (reducedDof < 0) {
return std::nullopt;
}
return reducedDof;
}
/*
* Gather:
*
* full MFEM true vector
* |
* v
* dense reduced solver vector
*/
void gather(
const mfem::Vector &full,
mfem::Vector &reduced
) const {
require_full_size(full);
require_reduced_size(reduced);
for (int reducedDof = 0; reducedDof < reduced_size(); ++reducedDof) {
reduced(reducedDof) = full(m_reducedToTrue[reducedDof]);
}
}
[[nodiscard]]
mfem::Vector gather(const mfem::Vector &full) const {
mfem::Vector reduced(reduced_size());
gather(full, reduced);
return reduced;
}
/*
* Scatter with projection semantics.
*
* All unsupported true DOFs are explicitly zeroed.
*
* This is the normal operation for constructing a complete MFEM
* representation of a supported field from the reduced nonlinear
* state.
*
* The output vector is NOT resized. This is intentional: callers
* may provide an mfem::Vector view into an mfem::BlockVector.
*/
void scatter(
const mfem::Vector &reduced,
mfem::Vector &full
) const {
require_reduced_size(reduced);
require_full_size(full);
full = 0.0;
scatter_into(reduced, full);
}
[[nodiscard]]
mfem::Vector scatter(const mfem::Vector &reduced) const {
mfem::Vector full(full_size());
scatter(reduced, full);
return full;
}
/*
* Scatter while preserving unsupported values already present in
* the full vector.
*
* This is distinct from scatter() because future constrained field
* representations may need to preserve prescribed values outside
* the current reduced/free set.
*/
void scatter_into(
const mfem::Vector &reduced,
mfem::Vector &full
) const {
require_reduced_size(reduced);
require_full_size(full);
for (int reducedDof = 0; reducedDof < reduced_size(); ++reducedDof) {
full(m_reducedToTrue[reducedDof]) = reduced(reducedDof);
}
}
/*
* Add a reduced vector into the selected true DOFs.
*
* Unsupported true DOFs are untouched.
*/
void scatter_add(
const mfem::Vector &reduced,
mfem::Vector &full,
const double scale = 1.0
) const {
require_reduced_size(reduced);
require_full_size(full);
for (int reducedDof = 0; reducedDof < reduced_size(); ++reducedDof) {
full(m_reducedToTrue[reducedDof]) += scale * reduced(reducedDof);
}
}
private:
void validate_true_dof(const int trueDof) const {
if (trueDof < 0 || trueDof >= full_size()) {
throw std::out_of_range("True DOF index is outside FieldDofMap.");
}
}
void require_full_size(const mfem::Vector &vector) const {
if (vector.Size() != full_size()) {
throw std::invalid_argument("FieldDofMap full vector has an incompatible size.");
}
}
void require_reduced_size(const mfem::Vector &vector) const {
if (vector.Size() != reduced_size()) {
throw std::invalid_argument("FieldDofMap reduced vector has an incompatible size.");
}
}
int m_fullTrueDofSize{0};
/*
* Canonical forward mapping:
*
* reduced -> MFEM true
*/
mfem::Array<int> m_reducedToTrue;
/*
* Inverse mapping:
*
* MFEM true -> reduced
*
* Unsupported true DOFs are -1.
*/
mfem::Array<int> m_trueToReduced;
};
/*
* Construct the canonical solver map for a registered spatial field.
*
* Field and domain semantics are used only while constructing the map.
* Consumers receive a plain FieldDofMap and therefore do not need to
* understand DomainSchema or field-support types.
*/
template <
MfemDomainField FieldT,
utils::domain::IsSchema SchemaT>
[[nodiscard]]
FieldDofMap make_field_dof_map(const mfem::ParFiniteElementSpace &finiteElementSpace) {
const FieldDofSupport support = resolve_field_dof_support<FieldT, SchemaT>(finiteElementSpace);
return FieldDofMap(support);
}
} // namespace mean_field::field