feat(libmeanfield): centrifugal + pressure
This commit is contained in:
380
libmeanfield/interface/field/field_mfem.cppm
Normal file
380
libmeanfield/interface/field/field_mfem.cppm
Normal file
@@ -0,0 +1,380 @@
|
||||
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>);
|
||||
} // namespace mean_field::field
|
||||
Reference in New Issue
Block a user