feat(libmeanfield): centrifugal + pressure
This commit is contained in:
356
libmeanfield/interface/field/field_base.cppm
Normal file
356
libmeanfield/interface/field/field_base.cppm
Normal file
@@ -0,0 +1,356 @@
|
||||
module;
|
||||
|
||||
#include <concepts>
|
||||
#include <cstddef>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
|
||||
export module mean_field:field.base;
|
||||
|
||||
export namespace mean_field::field {
|
||||
template <typename... Ts> struct TypeList { };
|
||||
|
||||
template <typename T, typename ListT> struct TypeListContains;
|
||||
|
||||
template <typename T, typename... Ts>
|
||||
struct TypeListContains<T, TypeList<Ts...>>
|
||||
: std::bool_constant<(std::same_as<T, Ts> || ...)> { };
|
||||
|
||||
template <typename T, typename ListT>
|
||||
inline constexpr bool typeListContains = TypeListContains<T, ListT>::value;
|
||||
|
||||
enum class StorageKind { finite_element, global_scalar };
|
||||
|
||||
inline constexpr int dynamicBlockSize = -1;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Function-space tags
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
struct L2 {
|
||||
static constexpr std::string_view name = "L2";
|
||||
};
|
||||
|
||||
struct H1 {
|
||||
static constexpr std::string_view name = "H1";
|
||||
};
|
||||
|
||||
struct RT {
|
||||
static constexpr std::string_view name = "RT";
|
||||
};
|
||||
|
||||
struct ND {
|
||||
static constexpr std::string_view name = "ND";
|
||||
};
|
||||
|
||||
template <typename SpaceT>
|
||||
concept SpaceTag = std::same_as<SpaceT, L2> || std::same_as<SpaceT, H1> ||
|
||||
std::same_as<SpaceT, RT> || std::same_as<SpaceT, ND>;
|
||||
|
||||
template <SpaceTag SpaceT, int RankV>
|
||||
inline constexpr bool spaceSupportsRank =
|
||||
(std::same_as<SpaceT, H1> && (RankV == 0 || RankV == 1)) ||
|
||||
(std::same_as<SpaceT, L2> && (RankV == 0 || RankV == 1)) ||
|
||||
(std::same_as<SpaceT, RT> && RankV == 1) ||
|
||||
(std::same_as<SpaceT, ND> && RankV == 1);
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Discretization descriptors
|
||||
//
|
||||
// familyOrder is the order passed to the backend's FE collection
|
||||
// constructor. It is deliberately not called polynomialOrder because those
|
||||
// values differ for some spaces, notably Raviart-Thomas elements in MFEM.
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
template <SpaceTag SpaceT, int FamilyOrderV> struct Disc {
|
||||
using Space = SpaceT;
|
||||
|
||||
static constexpr int familyOrder = FamilyOrderV;
|
||||
|
||||
static_assert(
|
||||
FamilyOrderV >= 0,
|
||||
"Finite-element family order must be non-negative."
|
||||
);
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
concept DiscretizationTag = requires {
|
||||
typename T::Space;
|
||||
{ T::familyOrder } -> std::convertible_to<int>;
|
||||
} && SpaceTag<typename T::Space>;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Physical relations between quantities
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
struct FieldRelation {
|
||||
struct Independent { };
|
||||
|
||||
template <typename SourceT> struct Gradient {
|
||||
using Source = SourceT;
|
||||
};
|
||||
|
||||
template <typename SourceT> struct Divergence {
|
||||
using Source = SourceT;
|
||||
};
|
||||
|
||||
template <typename SourceT> struct Curl {
|
||||
using Source = SourceT;
|
||||
};
|
||||
};
|
||||
|
||||
template <typename T> struct IsGradient : std::false_type { };
|
||||
|
||||
template <typename T> struct IsDivergence : std::false_type { };
|
||||
|
||||
template <typename T> struct IsCurl : std::false_type { };
|
||||
|
||||
template <typename SourceT>
|
||||
struct IsGradient<FieldRelation::Gradient<SourceT>> : std::true_type { };
|
||||
|
||||
template <typename SourceT>
|
||||
struct IsDivergence<FieldRelation::Divergence<SourceT>> : std::true_type {
|
||||
};
|
||||
|
||||
template <typename SourceT>
|
||||
struct IsCurl<FieldRelation::Curl<SourceT>> : std::true_type { };
|
||||
|
||||
template <typename RelationT>
|
||||
concept ValidRelation =
|
||||
std::same_as<RelationT, FieldRelation::Independent> ||
|
||||
IsGradient<RelationT>::value || IsDivergence<RelationT>::value ||
|
||||
IsCurl<RelationT>::value;
|
||||
|
||||
template <typename RelationT> struct RelationTarget {
|
||||
using Type = void;
|
||||
};
|
||||
|
||||
template <typename SourceT>
|
||||
struct RelationTarget<FieldRelation::Gradient<SourceT>> {
|
||||
using Type = SourceT;
|
||||
};
|
||||
|
||||
template <typename SourceT>
|
||||
struct RelationTarget<FieldRelation::Divergence<SourceT>> {
|
||||
using Type = SourceT;
|
||||
};
|
||||
|
||||
template <typename SourceT>
|
||||
struct RelationTarget<FieldRelation::Curl<SourceT>> {
|
||||
using Type = SourceT;
|
||||
};
|
||||
|
||||
template <typename QuantityT>
|
||||
using RelationTargetT =
|
||||
typename RelationTarget<typename QuantityT::Relation>::Type;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Field quantities
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
template <int RankV, ValidRelation RelationT, DiscretizationTag DiscT>
|
||||
struct Quantity {
|
||||
using Relation = RelationT;
|
||||
using Discretization = DiscT;
|
||||
using Space = typename DiscT::Space;
|
||||
|
||||
static constexpr int rankValue = RankV;
|
||||
static constexpr int familyOrder = DiscT::familyOrder;
|
||||
|
||||
static constexpr StorageKind storageKind = StorageKind::finite_element;
|
||||
static constexpr int staticBlockSize = dynamicBlockSize;
|
||||
|
||||
static_assert(
|
||||
RankV >= 0,
|
||||
"A field quantity cannot have a negative tensor rank."
|
||||
);
|
||||
|
||||
static_assert(
|
||||
spaceSupportsRank<
|
||||
Space,
|
||||
RankV>,
|
||||
"This function space cannot represent a quantity of this rank."
|
||||
);
|
||||
};
|
||||
|
||||
template <ValidRelation RelationT, DiscretizationTag DiscT>
|
||||
using ScalarQ = Quantity<0, RelationT, DiscT>;
|
||||
|
||||
template <ValidRelation RelationT, DiscretizationTag DiscT>
|
||||
using VectorQ = Quantity<1, RelationT, DiscT>;
|
||||
|
||||
struct GlobalScalarQ {
|
||||
using Relation = FieldRelation::Independent;
|
||||
|
||||
static constexpr int rankValue = 0;
|
||||
static constexpr StorageKind storageKind = StorageKind::global_scalar;
|
||||
static constexpr int staticBlockSize = 1;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
concept FieldQuantity =
|
||||
requires {
|
||||
typename T::Relation;
|
||||
typename T::Discretization;
|
||||
typename T::Space;
|
||||
|
||||
{ T::rankValue } -> std::convertible_to<int>;
|
||||
{ T::familyOrder } -> std::convertible_to<int>;
|
||||
{ T::storageKind } -> std::convertible_to<StorageKind>;
|
||||
{ T::staticBlockSize } -> std::convertible_to<int>;
|
||||
} && SpaceTag<typename T::Space> &&
|
||||
T::storageKind == StorageKind::finite_element;
|
||||
|
||||
template <typename T>
|
||||
concept GlobalScalarQuantity =
|
||||
requires {
|
||||
typename T::Relation;
|
||||
|
||||
{ T::rankValue } -> std::convertible_to<int>;
|
||||
{ T::storageKind } -> std::convertible_to<StorageKind>;
|
||||
{ T::staticBlockSize } -> std::convertible_to<int>;
|
||||
} && T::rankValue == 0 &&
|
||||
T::storageKind == StorageKind::global_scalar && T::staticBlockSize == 1;
|
||||
|
||||
template <typename T>
|
||||
concept RegisteredQuantity = FieldQuantity<T> || GlobalScalarQuantity<T>;
|
||||
|
||||
template <typename QuantityT>
|
||||
concept DerivedQuantity = FieldQuantity<QuantityT> &&
|
||||
(!std::same_as<RelationTargetT<QuantityT>, void>);
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Compile-time discretization constraints
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
template <FieldQuantity FluxT, FieldQuantity PotentialT>
|
||||
struct RtL2StablePair {
|
||||
static consteval void validate() {
|
||||
static_assert(
|
||||
std::same_as<typename FluxT::Space, RT>,
|
||||
"The flux in an RT/L2 pair must use Raviart-Thomas elements."
|
||||
);
|
||||
|
||||
static_assert(
|
||||
std::same_as<typename PotentialT::Space, L2>,
|
||||
"The potential in an RT/L2 pair must use L2 elements."
|
||||
);
|
||||
|
||||
static_assert(
|
||||
FluxT::rankValue == 1,
|
||||
"The flux in an RT/L2 pair must be vector-valued."
|
||||
);
|
||||
|
||||
static_assert(
|
||||
PotentialT::rankValue == 0,
|
||||
"The potential in an RT/L2 pair must be scalar-valued."
|
||||
);
|
||||
|
||||
static_assert(
|
||||
FluxT::familyOrder == PotentialT::familyOrder,
|
||||
"The MFEM RT and L2 family orders must match."
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename... ConstraintTs>
|
||||
consteval bool validate_constraints(TypeList<ConstraintTs...>) {
|
||||
(ConstraintTs::validate(), ...);
|
||||
return true;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Operations applied to quantities inside weak forms
|
||||
//
|
||||
// These describe the mathematics. Backend-specific polynomial-order rules
|
||||
// are provided by field.mfem.
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
struct FieldOperation {
|
||||
struct Value { };
|
||||
struct Gradient { };
|
||||
struct Divergence { };
|
||||
struct Curl { };
|
||||
struct NormalTrace { };
|
||||
};
|
||||
|
||||
template <typename OperationT>
|
||||
concept FieldOperationTag =
|
||||
std::same_as<OperationT, FieldOperation::Value> ||
|
||||
std::same_as<OperationT, FieldOperation::Gradient> ||
|
||||
std::same_as<OperationT, FieldOperation::Divergence> ||
|
||||
std::same_as<OperationT, FieldOperation::Curl> ||
|
||||
std::same_as<OperationT, FieldOperation::NormalTrace>;
|
||||
|
||||
template <
|
||||
RegisteredQuantity QuantityT,
|
||||
FieldOperationTag OperationT = FieldOperation::Value>
|
||||
struct Operand {
|
||||
using Quantity = QuantityT;
|
||||
using Operation = OperationT;
|
||||
|
||||
static_assert(
|
||||
FieldQuantity<QuantityT> || std::same_as<
|
||||
OperationT,
|
||||
FieldOperation::Value>,
|
||||
"Global scalar quantities support only the value operation."
|
||||
);
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
concept FieldOperand =
|
||||
requires {
|
||||
typename T::Quantity;
|
||||
typename T::Operation;
|
||||
} && RegisteredQuantity<typename T::Quantity> &&
|
||||
FieldOperationTag<typename T::Operation>;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Weak-form descriptions
|
||||
//
|
||||
// PolicyKeyV associates the form with a runtime quadrature-policy key.
|
||||
//
|
||||
// DynamicOrderCountV is the number of polynomial-order contributions that
|
||||
// cannot yet be derived from registered quantities. For example, a source
|
||||
// coefficient supplied at runtime contributes one dynamic order.
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
template <
|
||||
auto PolicyKeyV,
|
||||
std::size_t DynamicOrderCountV,
|
||||
FieldOperand... OperandTs>
|
||||
struct FormSpec {
|
||||
static constexpr auto policyKey = PolicyKeyV;
|
||||
static constexpr std::size_t dynamicOrderCount = DynamicOrderCountV;
|
||||
|
||||
using Operands = TypeList<OperandTs...>;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
concept FieldForm = requires {
|
||||
typename T::Operands;
|
||||
|
||||
T::policyKey;
|
||||
|
||||
{ T::dynamicOrderCount } -> std::convertible_to<std::size_t>;
|
||||
};
|
||||
|
||||
template <typename ListT>
|
||||
struct IsRegisteredQuantityList : std::false_type { };
|
||||
|
||||
template <RegisteredQuantity... QuantityTs>
|
||||
struct IsRegisteredQuantityList<TypeList<QuantityTs...>> : std::true_type {
|
||||
};
|
||||
|
||||
template <typename ListT>
|
||||
inline constexpr bool isRegisteredQuantityList =
|
||||
IsRegisteredQuantityList<ListT>::value;
|
||||
|
||||
template <typename ListT> struct IsFieldFormList : std::false_type { };
|
||||
|
||||
template <FieldForm... FormTs>
|
||||
struct IsFieldFormList<TypeList<FormTs...>> : std::true_type { };
|
||||
|
||||
template <typename ListT>
|
||||
inline constexpr bool isFieldFormList = IsFieldFormList<ListT>::value;
|
||||
} // namespace mean_field::field
|
||||
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
|
||||
379
libmeanfield/interface/field/field_registry.cppm
Normal file
379
libmeanfield/interface/field/field_registry.cppm
Normal file
@@ -0,0 +1,379 @@
|
||||
module;
|
||||
|
||||
#include <concepts>
|
||||
#include <string_view>
|
||||
|
||||
export module mean_field:field.registry;
|
||||
|
||||
export import :field.base;
|
||||
export import :quadrature.policy;
|
||||
|
||||
export namespace mean_field::field {
|
||||
// =========================================================================
|
||||
// Density
|
||||
// =========================================================================
|
||||
|
||||
struct Density {
|
||||
static constexpr std::string_view name = "density";
|
||||
static constexpr int scalarOrder = 2;
|
||||
|
||||
struct Scalar final
|
||||
: ScalarQ<FieldRelation::Independent, Disc<L2, scalarOrder>> {
|
||||
static constexpr std::string_view symbol = "ρ";
|
||||
};
|
||||
|
||||
using Quantities = TypeList<Scalar>;
|
||||
using Constraints = TypeList<>;
|
||||
|
||||
static constexpr bool constraintsAreValid =
|
||||
validate_constraints(Constraints{});
|
||||
|
||||
static_assert(constraintsAreValid);
|
||||
|
||||
struct Form {
|
||||
// Density-space mass matrix: (rho, q).
|
||||
using ProjectionMass = FormSpec<
|
||||
quadrature::Term::density_projection,
|
||||
0,
|
||||
Operand<Scalar>,
|
||||
Operand<Scalar>>;
|
||||
|
||||
// Projection RHS with one runtime coefficient order.
|
||||
using ProjectionSource = FormSpec<
|
||||
quadrature::Term::density_projection,
|
||||
1,
|
||||
Operand<Scalar>>;
|
||||
|
||||
// Density-space contribution to the barotropic EOS closure:
|
||||
// (rho, q_rho).
|
||||
using EosClosureMass = FormSpec<
|
||||
quadrature::Term::eos_closure,
|
||||
0,
|
||||
Operand<Scalar>,
|
||||
Operand<Scalar>>;
|
||||
|
||||
// Integral of density over the physical volume.
|
||||
using MassConservation = FormSpec<
|
||||
quadrature::Term::mass_conservation,
|
||||
0,
|
||||
Operand<Scalar>>;
|
||||
|
||||
// The same physical integral used as a nonlinear normalization
|
||||
// constraint. It has a distinct policy key so solver assembly and
|
||||
// diagnostics can be overintegrated independently.
|
||||
using MassNormalization = FormSpec<
|
||||
quadrature::Term::mass_normalization,
|
||||
0,
|
||||
Operand<Scalar>>;
|
||||
|
||||
// Integral of rho * x. The combined position-coefficient order is
|
||||
// supplied as one dynamic order.
|
||||
using CenterOfMass =
|
||||
FormSpec<quadrature::Term::center_of_mass, 1, Operand<Scalar>>;
|
||||
|
||||
// Integral of rho times the quadratic position tensor. The
|
||||
// combined tensor-coefficient order is supplied dynamically.
|
||||
using Quadrupole =
|
||||
FormSpec<quadrature::Term::quadrupole, 1, Operand<Scalar>>;
|
||||
|
||||
using ErrorNorm = FormSpec<
|
||||
quadrature::Term::error_norm,
|
||||
0,
|
||||
Operand<Scalar>,
|
||||
Operand<Scalar>>;
|
||||
};
|
||||
|
||||
using FormList = TypeList<
|
||||
Form::ProjectionMass,
|
||||
Form::ProjectionSource,
|
||||
Form::EosClosureMass,
|
||||
Form::MassConservation,
|
||||
Form::MassNormalization,
|
||||
Form::CenterOfMass,
|
||||
Form::Quadrupole,
|
||||
Form::ErrorNorm>;
|
||||
};
|
||||
|
||||
// =========================================================================
|
||||
// Gravity
|
||||
// =========================================================================
|
||||
|
||||
struct Gravity {
|
||||
static constexpr std::string_view name = "gravity";
|
||||
|
||||
static constexpr int potentialOrder = 2;
|
||||
static constexpr int fluxOrder = 2;
|
||||
|
||||
struct Potential final
|
||||
: ScalarQ<FieldRelation::Independent, Disc<L2, potentialOrder>> {
|
||||
static constexpr std::string_view symbol = "φ";
|
||||
};
|
||||
|
||||
struct Flux final
|
||||
: VectorQ<FieldRelation::Gradient<Potential>, Disc<RT, fluxOrder>> {
|
||||
static constexpr std::string_view symbol = "∇φ";
|
||||
};
|
||||
|
||||
using Quantities = TypeList<Potential, Flux>;
|
||||
|
||||
using Constraints = TypeList<RtL2StablePair<Flux, Potential>>;
|
||||
|
||||
static constexpr bool constraintsAreValid =
|
||||
validate_constraints(Constraints{});
|
||||
|
||||
static_assert(constraintsAreValid);
|
||||
|
||||
struct Form {
|
||||
using HDivMass = FormSpec<
|
||||
quadrature::Term::gravity_hdiv_mass,
|
||||
0,
|
||||
Operand<Flux>,
|
||||
Operand<Flux>>;
|
||||
|
||||
using DivergenceCoupling = FormSpec<
|
||||
quadrature::Term::gravity_divergence,
|
||||
0,
|
||||
Operand<Flux, FieldOperation::Divergence>,
|
||||
Operand<Potential>>;
|
||||
|
||||
using Boundary = FormSpec<
|
||||
quadrature::Term::gravity_boundary,
|
||||
0,
|
||||
Operand<Flux, FieldOperation::NormalTrace>,
|
||||
Operand<Flux, FieldOperation::NormalTrace>>;
|
||||
|
||||
// Density is a registered coefficient field and potential is the
|
||||
// test field, so the full polynomial order is compile-time data.
|
||||
using SourceLinear = FormSpec<
|
||||
quadrature::Term::gravity_source,
|
||||
0,
|
||||
Operand<Density::Scalar>,
|
||||
Operand<Potential>>;
|
||||
|
||||
// Mixed density-to-potential projection. Both trial and test
|
||||
// orders are registered quantities.
|
||||
using SourceProjection = FormSpec<
|
||||
quadrature::Term::gravity_source,
|
||||
0,
|
||||
Operand<Density::Scalar>,
|
||||
Operand<Potential>>;
|
||||
|
||||
using PotentialErrorNorm = FormSpec<
|
||||
quadrature::Term::error_norm,
|
||||
0,
|
||||
Operand<Potential>,
|
||||
Operand<Potential>>;
|
||||
|
||||
using FluxErrorNorm = FormSpec<
|
||||
quadrature::Term::error_norm,
|
||||
0,
|
||||
Operand<Flux>,
|
||||
Operand<Flux>>;
|
||||
};
|
||||
|
||||
using FormList = TypeList<
|
||||
Form::HDivMass,
|
||||
Form::DivergenceCoupling,
|
||||
Form::Boundary,
|
||||
Form::SourceLinear,
|
||||
Form::SourceProjection,
|
||||
Form::PotentialErrorNorm,
|
||||
Form::FluxErrorNorm>;
|
||||
};
|
||||
|
||||
// =========================================================================
|
||||
// Displacement
|
||||
// =========================================================================
|
||||
|
||||
struct Displacement {
|
||||
static constexpr std::string_view name = "displacement";
|
||||
static constexpr int vectorOrder = 3;
|
||||
|
||||
struct Vector final
|
||||
: VectorQ<FieldRelation::Independent, Disc<H1, vectorOrder>> {
|
||||
static constexpr std::string_view symbol = "d";
|
||||
};
|
||||
|
||||
using Quantities = TypeList<Vector>;
|
||||
using Constraints = TypeList<>;
|
||||
|
||||
static constexpr bool constraintsAreValid =
|
||||
validate_constraints(Constraints{});
|
||||
|
||||
static_assert(constraintsAreValid);
|
||||
|
||||
struct Form {
|
||||
// Harmonic or pseudoelastic interior mesh extension. For the
|
||||
// initial Laplacian model this is (grad d, grad w).
|
||||
using MeshExtension = FormSpec<
|
||||
quadrature::Term::mesh_extension,
|
||||
0,
|
||||
Operand<Vector, FieldOperation::Gradient>,
|
||||
Operand<Vector, FieldOperation::Gradient>>;
|
||||
|
||||
using ErrorNorm = FormSpec<
|
||||
quadrature::Term::error_norm,
|
||||
0,
|
||||
Operand<Vector>,
|
||||
Operand<Vector>>;
|
||||
};
|
||||
|
||||
using FormList = TypeList<Form::MeshExtension, Form::ErrorNorm>;
|
||||
};
|
||||
|
||||
struct BarotropicConstant {
|
||||
static constexpr std::string_view name = "barotropic_constant";
|
||||
|
||||
struct Scalar final : GlobalScalarQ {
|
||||
static constexpr std::string_view symbol = "C";
|
||||
};
|
||||
|
||||
using Quantities = TypeList<Scalar>;
|
||||
using Constraints = TypeList<>;
|
||||
using FormList = TypeList<>;
|
||||
|
||||
static constexpr bool constraintsAreValid =
|
||||
validate_constraints(Constraints{});
|
||||
|
||||
static_assert(constraintsAreValid);
|
||||
};
|
||||
|
||||
// =========================================================================
|
||||
// Specific enthalpy
|
||||
//
|
||||
// Pressure is deliberately not registered as an independent field. For a
|
||||
// barotrope it is derived from h through the EOS, while h supplies the
|
||||
// continuous H1 trace used to define the isobaric stellar surface.
|
||||
// =========================================================================
|
||||
|
||||
struct Enthalpy {
|
||||
static constexpr std::string_view name = "specific_enthalpy";
|
||||
static constexpr int scalarOrder = 3;
|
||||
|
||||
struct Scalar final
|
||||
: ScalarQ<FieldRelation::Independent, Disc<H1, scalarOrder>> {
|
||||
static constexpr std::string_view symbol = "h";
|
||||
};
|
||||
|
||||
using Quantities = TypeList<Scalar>;
|
||||
using Constraints = TypeList<>;
|
||||
|
||||
static constexpr bool constraintsAreValid =
|
||||
validate_constraints(Constraints{});
|
||||
|
||||
static_assert(constraintsAreValid);
|
||||
|
||||
struct Form {
|
||||
// EOS source contribution (rho(h), q_rho). The dynamic order is
|
||||
// the extra polynomial order introduced by the nonlinear EOS
|
||||
// beyond the registered order of h. For an n=3 polytrope this is
|
||||
// 2 * hOrder, making rho(h) cubic in h.
|
||||
using EosClosureSource = FormSpec<
|
||||
quadrature::Term::eos_closure,
|
||||
1,
|
||||
Operand<Scalar>,
|
||||
Operand<Density::Scalar>>;
|
||||
|
||||
// (h, q_h) contribution to
|
||||
// h + phi - Psi_rotation - C = 0.
|
||||
using EquilibriumEnthalpy = FormSpec<
|
||||
quadrature::Term::hydrostatic_equilibrium,
|
||||
0,
|
||||
Operand<Scalar>,
|
||||
Operand<Scalar>>;
|
||||
|
||||
// (phi, q_h) contribution to hydrostatic equilibrium.
|
||||
using EquilibriumGravity = FormSpec<
|
||||
quadrature::Term::hydrostatic_equilibrium,
|
||||
0,
|
||||
Operand<Gravity::Potential>,
|
||||
Operand<Scalar>>;
|
||||
|
||||
// (Psi_rotation, q_h). The rotation-potential order is supplied
|
||||
// dynamically because it belongs to runtime rotation data.
|
||||
using EquilibriumRotation = FormSpec<
|
||||
quadrature::Term::hydrostatic_equilibrium,
|
||||
1,
|
||||
Operand<Scalar>>;
|
||||
|
||||
// (C, q_h), where C is spatially constant.
|
||||
using EquilibriumConstant = FormSpec<
|
||||
quadrature::Term::hydrostatic_equilibrium,
|
||||
0,
|
||||
Operand<BarotropicConstant::Scalar>,
|
||||
Operand<Scalar>>;
|
||||
|
||||
// Boundary trace form available for weak enforcement, testing, or
|
||||
// a future multiplier formulation of h|Gamma_star = 0.
|
||||
using IsobaricSurface = FormSpec<
|
||||
quadrature::Term::isobaric_surface,
|
||||
0,
|
||||
Operand<Scalar>,
|
||||
Operand<Scalar>>;
|
||||
|
||||
// Integral of P(h). The dynamic order is the extra EOS order
|
||||
// beyond the registered order of h.
|
||||
using PressureIntegral = FormSpec<
|
||||
quadrature::Term::pressure_integral,
|
||||
1,
|
||||
Operand<Scalar>>;
|
||||
|
||||
// Weak pressure force in the displacement test space:
|
||||
//
|
||||
// -int P(h) I : grad(w) dV
|
||||
//
|
||||
// which is equivalent to -int P(h) div(w) dV. The dynamic order
|
||||
// is the extra EOS order beyond the registered order of h. For an
|
||||
// n=3 polytrope this is 3 * hOrder, making P(h) quartic in h.
|
||||
using PressureForce = FormSpec<
|
||||
quadrature::Term::pressure_force,
|
||||
1,
|
||||
Operand<Scalar>,
|
||||
Operand<Displacement::Vector, FieldOperation::Gradient>>;
|
||||
|
||||
using ErrorNorm = FormSpec<
|
||||
quadrature::Term::error_norm,
|
||||
0,
|
||||
Operand<Scalar>,
|
||||
Operand<Scalar>>;
|
||||
};
|
||||
|
||||
using FormList = TypeList<
|
||||
Form::EosClosureSource,
|
||||
Form::EquilibriumEnthalpy,
|
||||
Form::EquilibriumGravity,
|
||||
Form::EquilibriumRotation,
|
||||
Form::EquilibriumConstant,
|
||||
Form::IsobaricSurface,
|
||||
Form::PressureIntegral,
|
||||
Form::PressureForce,
|
||||
Form::ErrorNorm>;
|
||||
};
|
||||
|
||||
// =========================================================================
|
||||
// Field definition concept
|
||||
// =========================================================================
|
||||
|
||||
template <typename T>
|
||||
concept FieldTag =
|
||||
requires {
|
||||
typename T::Quantities;
|
||||
typename T::Constraints;
|
||||
typename T::FormList;
|
||||
|
||||
{ T::name } -> std::convertible_to<std::string_view>;
|
||||
} && isRegisteredQuantityList<typename T::Quantities> &&
|
||||
isFieldFormList<typename T::FormList>;
|
||||
|
||||
static_assert(FieldTag<Gravity>);
|
||||
static_assert(FieldTag<Displacement>);
|
||||
static_assert(FieldTag<Density>);
|
||||
static_assert(FieldTag<Enthalpy>);
|
||||
static_assert(FieldTag<BarotropicConstant>);
|
||||
|
||||
static_assert(DerivedQuantity<Gravity::Flux>);
|
||||
|
||||
static_assert(std::same_as<
|
||||
RelationTargetT<Gravity::Flux>,
|
||||
Gravity::Potential>);
|
||||
} // namespace mean_field::field
|
||||
Reference in New Issue
Block a user