feat(field-support): added field support system, mid migration
currently the barotope and the pressure force operator are migrated to the new support system
This commit is contained in:
@@ -6,6 +6,7 @@ module;
|
||||
#include <type_traits>
|
||||
|
||||
export module mean_field:field.base;
|
||||
export import :utils.domain;
|
||||
|
||||
export namespace mean_field::field {
|
||||
template <typename... Ts> struct TypeList { };
|
||||
@@ -13,11 +14,9 @@ export namespace mean_field::field {
|
||||
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> || ...)> { };
|
||||
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;
|
||||
template <typename T, typename ListT> inline constexpr bool typeListContains = TypeListContains<T, ListT>::value;
|
||||
|
||||
enum class StorageKind { finite_element, global_scalar };
|
||||
|
||||
@@ -44,14 +43,13 @@ export namespace mean_field::field {
|
||||
};
|
||||
|
||||
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>;
|
||||
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, L2> && (RankV == 0 || RankV == 1)) || (std::same_as<SpaceT, RT> && RankV == 1) ||
|
||||
(std::same_as<SpaceT, ND> && RankV == 1);
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
@@ -105,51 +103,39 @@ export namespace mean_field::field {
|
||||
|
||||
template <typename T> struct IsCurl : std::false_type { };
|
||||
|
||||
template <typename SourceT>
|
||||
struct IsGradient<FieldRelation::Gradient<SourceT>> : std::true_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 IsDivergence<FieldRelation::Divergence<SourceT>> : std::true_type { };
|
||||
|
||||
template <typename SourceT>
|
||||
struct IsCurl<FieldRelation::Curl<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;
|
||||
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>> {
|
||||
template <typename SourceT> struct RelationTarget<FieldRelation::Gradient<SourceT>> {
|
||||
using Type = SourceT;
|
||||
};
|
||||
|
||||
template <typename SourceT>
|
||||
struct RelationTarget<FieldRelation::Divergence<SourceT>> {
|
||||
template <typename SourceT> struct RelationTarget<FieldRelation::Divergence<SourceT>> {
|
||||
using Type = SourceT;
|
||||
};
|
||||
|
||||
template <typename SourceT>
|
||||
struct RelationTarget<FieldRelation::Curl<SourceT>> {
|
||||
template <typename SourceT> struct RelationTarget<FieldRelation::Curl<SourceT>> {
|
||||
using Type = SourceT;
|
||||
};
|
||||
|
||||
template <typename QuantityT>
|
||||
using RelationTargetT =
|
||||
typename RelationTarget<typename QuantityT::Relation>::Type;
|
||||
template <typename QuantityT> using RelationTargetT = typename RelationTarget<typename QuantityT::Relation>::Type;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Field quantities
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
template <int RankV, ValidRelation RelationT, DiscretizationTag DiscT>
|
||||
struct Quantity {
|
||||
template <int RankV, ValidRelation RelationT, DiscretizationTag DiscT> struct Quantity {
|
||||
using Relation = RelationT;
|
||||
using Discretization = DiscT;
|
||||
using Space = typename DiscT::Space;
|
||||
@@ -173,11 +159,9 @@ export namespace mean_field::field {
|
||||
);
|
||||
};
|
||||
|
||||
template <ValidRelation RelationT, DiscretizationTag DiscT>
|
||||
using ScalarQ = Quantity<0, RelationT, DiscT>;
|
||||
template <ValidRelation RelationT, DiscretizationTag DiscT> using ScalarQ = Quantity<0, RelationT, DiscT>;
|
||||
|
||||
template <ValidRelation RelationT, DiscretizationTag DiscT>
|
||||
using VectorQ = Quantity<1, RelationT, DiscT>;
|
||||
template <ValidRelation RelationT, DiscretizationTag DiscT> using VectorQ = Quantity<1, RelationT, DiscT>;
|
||||
|
||||
struct GlobalScalarQ {
|
||||
using Relation = FieldRelation::Independent;
|
||||
@@ -188,73 +172,57 @@ export namespace mean_field::field {
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
concept FieldQuantity =
|
||||
requires {
|
||||
typename T::Relation;
|
||||
typename T::Discretization;
|
||||
typename T::Space;
|
||||
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;
|
||||
{ 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;
|
||||
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;
|
||||
{ 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>);
|
||||
concept DerivedQuantity = FieldQuantity<QuantityT> && (!std::same_as<RelationTargetT<QuantityT>, void>);
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Compile-time discretization constraints
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
template <FieldQuantity FluxT, FieldQuantity PotentialT>
|
||||
struct RtL2StablePair {
|
||||
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."
|
||||
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."
|
||||
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(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(
|
||||
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."
|
||||
FluxT::familyOrder == PotentialT::familyOrder, "The MFEM RT and L2 family orders must match."
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename... ConstraintTs>
|
||||
consteval bool validate_constraints(TypeList<ConstraintTs...>) {
|
||||
template <typename... ConstraintTs> consteval bool validate_constraints(TypeList<ConstraintTs...>) {
|
||||
(ConstraintTs::validate(), ...);
|
||||
return true;
|
||||
}
|
||||
@@ -276,16 +244,11 @@ export namespace mean_field::field {
|
||||
|
||||
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::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 {
|
||||
template <RegisteredQuantity QuantityT, FieldOperationTag OperationT = FieldOperation::Value> struct Operand {
|
||||
using Quantity = QuantityT;
|
||||
using Operation = OperationT;
|
||||
|
||||
@@ -298,12 +261,10 @@ export namespace mean_field::field {
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
concept FieldOperand =
|
||||
requires {
|
||||
typename T::Quantity;
|
||||
typename T::Operation;
|
||||
} && RegisteredQuantity<typename T::Quantity> &&
|
||||
FieldOperationTag<typename T::Operation>;
|
||||
concept FieldOperand = requires {
|
||||
typename T::Quantity;
|
||||
typename T::Operation;
|
||||
} && RegisteredQuantity<typename T::Quantity> && FieldOperationTag<typename T::Operation>;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Weak-form descriptions
|
||||
@@ -315,11 +276,7 @@ export namespace mean_field::field {
|
||||
// coefficient supplied at runtime contributes one dynamic order.
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
template <
|
||||
auto PolicyKeyV,
|
||||
std::size_t DynamicOrderCountV,
|
||||
FieldOperand... OperandTs>
|
||||
struct FormSpec {
|
||||
template <auto PolicyKeyV, std::size_t DynamicOrderCountV, FieldOperand... OperandTs> struct FormSpec {
|
||||
static constexpr auto policyKey = PolicyKeyV;
|
||||
static constexpr std::size_t dynamicOrderCount = DynamicOrderCountV;
|
||||
|
||||
@@ -335,22 +292,46 @@ export namespace mean_field::field {
|
||||
{ T::dynamicOrderCount } -> std::convertible_to<std::size_t>;
|
||||
};
|
||||
|
||||
template <typename ListT>
|
||||
struct IsRegisteredQuantityList : std::false_type { };
|
||||
template <typename ListT> struct IsRegisteredQuantityList : std::false_type { };
|
||||
|
||||
template <RegisteredQuantity... QuantityTs>
|
||||
struct IsRegisteredQuantityList<TypeList<QuantityTs...>> : std::true_type {
|
||||
};
|
||||
struct IsRegisteredQuantityList<TypeList<QuantityTs...>> : std::true_type { };
|
||||
|
||||
template <typename ListT>
|
||||
inline constexpr bool isRegisteredQuantityList =
|
||||
IsRegisteredQuantityList<ListT>::value;
|
||||
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 <FieldForm... FormTs> struct IsFieldFormList<TypeList<FormTs...>> : std::true_type { };
|
||||
|
||||
template <typename ListT> inline constexpr bool isFieldFormList = IsFieldFormList<ListT>::value;
|
||||
|
||||
struct FieldSupport { };
|
||||
|
||||
template <utils::domain::IsDomainOrSet DomainT> struct DomainSupport final : FieldSupport {
|
||||
using Domain = DomainT;
|
||||
};
|
||||
|
||||
struct NonSpatialSupport final : FieldSupport { };
|
||||
|
||||
template <typename T> constexpr bool isDomainSupportV = false;
|
||||
|
||||
template <utils::domain::IsDomainOrSet DomainT> constexpr bool isDomainSupportV<DomainSupport<DomainT>> = true;
|
||||
|
||||
template <typename T>
|
||||
concept IsDomainSupport = isDomainSupportV<T>;
|
||||
|
||||
template <typename T>
|
||||
concept IsFieldSupport = std::derived_from<T, FieldSupport>;
|
||||
|
||||
template <typename FieldT> using FieldSupportT = typename FieldT::Support;
|
||||
|
||||
template <typename FieldT>
|
||||
concept DomainSupportedField = requires { typename FieldT::Support; } && IsDomainSupport<FieldSupportT<FieldT>>;
|
||||
|
||||
template <typename FieldT>
|
||||
concept NonSpatialField =
|
||||
requires { typename FieldT::Support; } && std::same_as<FieldSupportT<FieldT>, NonSpatialSupport>;
|
||||
|
||||
template <DomainSupportedField FieldT> using FieldDomainT = typename FieldSupportT<FieldT>::Domain;
|
||||
|
||||
template <typename ListT>
|
||||
inline constexpr bool isFieldFormList = IsFieldFormList<ListT>::value;
|
||||
} // namespace mean_field::field
|
||||
@@ -26,9 +26,7 @@ namespace mean_field::field::detail {
|
||||
int familyOrder,
|
||||
int dimension
|
||||
) {
|
||||
return std::make_unique<mfem::L2_FECollection>(
|
||||
familyOrder, dimension
|
||||
);
|
||||
return std::make_unique<mfem::L2_FECollection>(familyOrder, dimension);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -37,9 +35,7 @@ namespace mean_field::field::detail {
|
||||
int familyOrder,
|
||||
int dimension
|
||||
) {
|
||||
return std::make_unique<mfem::H1_FECollection>(
|
||||
familyOrder, dimension
|
||||
);
|
||||
return std::make_unique<mfem::H1_FECollection>(familyOrder, dimension);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -48,9 +44,7 @@ namespace mean_field::field::detail {
|
||||
int familyOrder,
|
||||
int dimension
|
||||
) {
|
||||
return std::make_unique<mfem::RT_FECollection>(
|
||||
familyOrder, dimension
|
||||
);
|
||||
return std::make_unique<mfem::RT_FECollection>(familyOrder, dimension);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -59,9 +53,7 @@ namespace mean_field::field::detail {
|
||||
int familyOrder,
|
||||
int dimension
|
||||
) {
|
||||
return std::make_unique<mfem::ND_FECollection>(
|
||||
familyOrder, dimension
|
||||
);
|
||||
return std::make_unique<mfem::ND_FECollection>(familyOrder, dimension);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -86,8 +78,7 @@ namespace mean_field::field::detail {
|
||||
static constexpr int orderValue = []() consteval {
|
||||
if constexpr (GlobalScalarQuantity<QuantityT>) {
|
||||
static_assert(
|
||||
std::same_as<OperationT, FieldOperation::Value>,
|
||||
"Global scalars support only the value operation."
|
||||
std::same_as<OperationT, FieldOperation::Value>, "Global scalars support only the value operation."
|
||||
);
|
||||
|
||||
return 0;
|
||||
@@ -101,51 +92,36 @@ namespace mean_field::field::detail {
|
||||
} else {
|
||||
return familyOrder;
|
||||
}
|
||||
} else if constexpr (
|
||||
std::same_as<OperationT, FieldOperation::Divergence>
|
||||
) {
|
||||
} 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."
|
||||
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>
|
||||
) {
|
||||
} 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."
|
||||
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>
|
||||
) {
|
||||
} 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."
|
||||
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>
|
||||
) {
|
||||
} 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."
|
||||
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_assert(alwaysFalse<OperationT>, "Unsupported MFEM field operation.");
|
||||
}
|
||||
}
|
||||
}();
|
||||
@@ -157,14 +133,9 @@ namespace mean_field::field::detail {
|
||||
|
||||
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);
|
||||
template <auto PolicyKeyV, std::size_t DynamicOrderCountV, FieldOperand... OperandTs>
|
||||
struct MfemFormOrder<FormSpec<PolicyKeyV, DynamicOrderCountV, OperandTs...>> {
|
||||
static constexpr int staticOrder = (MfemOperandOrder<OperandTs>::orderValue + ... + 0);
|
||||
};
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
@@ -183,8 +154,7 @@ namespace mean_field::field::detail {
|
||||
if constexpr (QuantityT::rankValue == 0) {
|
||||
return 1;
|
||||
} else if constexpr (
|
||||
std::same_as<typename QuantityT::Space, H1> ||
|
||||
std::same_as<typename QuantityT::Space, L2>
|
||||
std::same_as<typename QuantityT::Space, H1> || std::same_as<typename QuantityT::Space, L2>
|
||||
) {
|
||||
return spaceDimension;
|
||||
} else {
|
||||
@@ -192,12 +162,10 @@ namespace mean_field::field::detail {
|
||||
}
|
||||
}
|
||||
|
||||
template <FieldQuantity QuantityT>
|
||||
constexpr mfem::Ordering::Type get_ordering() {
|
||||
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>)
|
||||
(std::same_as<typename QuantityT::Space, H1> || std::same_as<typename QuantityT::Space, L2>)
|
||||
) {
|
||||
return mfem::Ordering::byVDIM;
|
||||
} else {
|
||||
@@ -213,40 +181,29 @@ namespace mean_field::field::detail {
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
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 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>();
|
||||
static constexpr mfem::Ordering::Type ordering = get_ordering<QuantityT>();
|
||||
};
|
||||
|
||||
template <> struct MfemQuantityTraits<Gravity::Flux> {
|
||||
static std::unique_ptr<mfem::FiniteElementCollection>
|
||||
make_fec(int dimension) {
|
||||
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
|
||||
Gravity::Flux::familyOrder, dimension, mfem::BasisType::GaussLobatto, mfem::BasisType::IntegratedGLL
|
||||
);
|
||||
}
|
||||
|
||||
static constexpr mfem::Ordering::Type ordering =
|
||||
mfem::Ordering::byNODES;
|
||||
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 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;
|
||||
static constexpr mfem::Ordering::Type ordering = mfem::Ordering::byNODES;
|
||||
};
|
||||
} // namespace mean_field::field::detail
|
||||
|
||||
@@ -275,8 +232,7 @@ export namespace mean_field::field {
|
||||
requires typeListContains<
|
||||
QuantityT,
|
||||
typename TagT::Quantities>
|
||||
static std::unique_ptr<mfem::FiniteElementCollection>
|
||||
make_fec(int dimension) {
|
||||
static std::unique_ptr<mfem::FiniteElementCollection> make_fec(int dimension) {
|
||||
if (dimension <= 0) {
|
||||
throw std::invalid_argument("Mesh dimension must be positive.");
|
||||
}
|
||||
@@ -299,8 +255,7 @@ export namespace mean_field::field {
|
||||
mfem::FiniteElementCollection &finiteElementCollection
|
||||
) {
|
||||
return std::make_unique<mfem::ParFiniteElementSpace>(
|
||||
&mesh, &finiteElementCollection,
|
||||
detail::get_vdim<QuantityT>(mesh.SpaceDimension()),
|
||||
&mesh, &finiteElementCollection, detail::get_vdim<QuantityT>(mesh.SpaceDimension()),
|
||||
detail::MfemQuantityTraits<QuantityT>::ordering
|
||||
);
|
||||
}
|
||||
@@ -338,22 +293,17 @@ export namespace mean_field::field {
|
||||
int,
|
||||
FormT::dynamicOrderCount> dynamicOrders = {},
|
||||
utils::DOMAINS domain = utils::DOMAINS::ALL,
|
||||
quadrature::MappingKind mapping = quadrature::MappingKind::none
|
||||
quadrature::MappingKind mapping = quadrature::MappingKind::none
|
||||
) {
|
||||
if (geometryWeightOrder < 0) {
|
||||
throw std::invalid_argument(
|
||||
"Geometry weight order cannot be negative."
|
||||
);
|
||||
throw std::invalid_argument("Geometry weight order cannot be negative.");
|
||||
}
|
||||
|
||||
int baseOrder =
|
||||
detail::MfemFormOrder<FormT>::staticOrder + geometryWeightOrder;
|
||||
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."
|
||||
);
|
||||
throw std::invalid_argument("Dynamic polynomial orders cannot be negative.");
|
||||
}
|
||||
|
||||
baseOrder += dynamicOrder;
|
||||
@@ -377,4 +327,580 @@ export namespace mean_field::field {
|
||||
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
|
||||
|
||||
@@ -7,6 +7,7 @@ export module mean_field:field.registry;
|
||||
|
||||
export import :field.base;
|
||||
export import :quadrature.policy;
|
||||
export import :utils.domain;
|
||||
|
||||
export namespace mean_field::field {
|
||||
// =========================================================================
|
||||
@@ -17,70 +18,47 @@ export namespace mean_field::field {
|
||||
static constexpr std::string_view name = "density";
|
||||
static constexpr int scalarOrder = 2;
|
||||
|
||||
struct Scalar final
|
||||
: ScalarQ<FieldRelation::Independent, Disc<L2, scalarOrder>> {
|
||||
using Support = DomainSupport<utils::domain::Stellar>;
|
||||
|
||||
struct Scalar final : ScalarQ<FieldRelation::Independent, Disc<L2, scalarOrder>> {
|
||||
static constexpr std::string_view symbol = "ρ";
|
||||
};
|
||||
|
||||
using Quantities = TypeList<Scalar>;
|
||||
using Constraints = TypeList<>;
|
||||
using Quantities = TypeList<Scalar>;
|
||||
using Constraints = TypeList<>;
|
||||
|
||||
static constexpr bool constraintsAreValid =
|
||||
validate_constraints(Constraints{});
|
||||
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>>;
|
||||
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>>;
|
||||
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>>;
|
||||
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>>;
|
||||
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>>;
|
||||
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>>;
|
||||
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 Quadrupole = FormSpec<quadrature::Term::quadrupole, 1, Operand<Scalar>>;
|
||||
|
||||
using ErrorNorm = FormSpec<
|
||||
quadrature::Term::error_norm,
|
||||
0,
|
||||
Operand<Scalar>,
|
||||
Operand<Scalar>>;
|
||||
using ErrorNorm = FormSpec<quadrature::Term::error_norm, 0, Operand<Scalar>, Operand<Scalar>>;
|
||||
};
|
||||
|
||||
using FormList = TypeList<
|
||||
@@ -104,31 +82,26 @@ export namespace mean_field::field {
|
||||
static constexpr int potentialOrder = 2;
|
||||
static constexpr int fluxOrder = 2;
|
||||
|
||||
struct Potential final
|
||||
: ScalarQ<FieldRelation::Independent, Disc<L2, potentialOrder>> {
|
||||
using Support = DomainSupport<utils::domain::All>;
|
||||
|
||||
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>> {
|
||||
struct Flux final : VectorQ<FieldRelation::Gradient<Potential>, Disc<RT, fluxOrder>> {
|
||||
static constexpr std::string_view symbol = "∇φ";
|
||||
};
|
||||
|
||||
using Quantities = TypeList<Potential, Flux>;
|
||||
using Quantities = TypeList<Potential, Flux>;
|
||||
|
||||
using Constraints = TypeList<RtL2StablePair<Flux, Potential>>;
|
||||
using Constraints = TypeList<RtL2StablePair<Flux, Potential>>;
|
||||
|
||||
static constexpr bool constraintsAreValid =
|
||||
validate_constraints(Constraints{});
|
||||
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 HDivMass = FormSpec<quadrature::Term::gravity_hdiv_mass, 0, Operand<Flux>, Operand<Flux>>;
|
||||
|
||||
using DivergenceCoupling = FormSpec<
|
||||
quadrature::Term::gravity_divergence,
|
||||
@@ -144,31 +117,18 @@ export namespace mean_field::field {
|
||||
|
||||
// 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>>;
|
||||
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 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 PotentialErrorNorm =
|
||||
FormSpec<quadrature::Term::error_norm, 0, Operand<Potential>, Operand<Potential>>;
|
||||
|
||||
using FluxErrorNorm = FormSpec<
|
||||
quadrature::Term::error_norm,
|
||||
0,
|
||||
Operand<Flux>,
|
||||
Operand<Flux>>;
|
||||
using FluxErrorNorm = FormSpec<quadrature::Term::error_norm, 0, Operand<Flux>, Operand<Flux>>;
|
||||
};
|
||||
|
||||
using FormList = TypeList<
|
||||
@@ -189,16 +149,16 @@ export namespace mean_field::field {
|
||||
static constexpr std::string_view name = "displacement";
|
||||
static constexpr int vectorOrder = 3;
|
||||
|
||||
struct Vector final
|
||||
: VectorQ<FieldRelation::Independent, Disc<H1, vectorOrder>> {
|
||||
using Support = DomainSupport<utils::domain::All>;
|
||||
|
||||
struct Vector final : VectorQ<FieldRelation::Independent, Disc<H1, vectorOrder>> {
|
||||
static constexpr std::string_view symbol = "d";
|
||||
};
|
||||
|
||||
using Quantities = TypeList<Vector>;
|
||||
using Constraints = TypeList<>;
|
||||
using Quantities = TypeList<Vector>;
|
||||
using Constraints = TypeList<>;
|
||||
|
||||
static constexpr bool constraintsAreValid =
|
||||
validate_constraints(Constraints{});
|
||||
static constexpr bool constraintsAreValid = validate_constraints(Constraints{});
|
||||
|
||||
static_assert(constraintsAreValid);
|
||||
|
||||
@@ -211,29 +171,50 @@ export namespace mean_field::field {
|
||||
Operand<Vector, FieldOperation::Gradient>,
|
||||
Operand<Vector, FieldOperation::Gradient>>;
|
||||
|
||||
using ErrorNorm = FormSpec<
|
||||
quadrature::Term::error_norm,
|
||||
// Positive gravitational contribution to the displacement row:
|
||||
//
|
||||
// int rho grad(phi) . w dV.
|
||||
//
|
||||
// Both the base geometry Jacobian and the displacement test
|
||||
// function contribute to the polynomial order. The RT flux is
|
||||
// mapped to physical space by the contravariant Piola map.
|
||||
using GravityForce = FormSpec<
|
||||
quadrature::Term::gravity_force,
|
||||
0,
|
||||
Operand<Vector>,
|
||||
Operand<Density::Scalar>,
|
||||
Operand<Gravity::Flux>,
|
||||
Operand<Vector, FieldOperation::Gradient>,
|
||||
Operand<Vector>>;
|
||||
|
||||
// Rigid-rotation contribution to the displacement row:
|
||||
//
|
||||
// -int rho grad(Psi_rotation) . w dV.
|
||||
//
|
||||
// grad(Psi_rotation) is linear in physical position, so its
|
||||
// polynomial order is supplied as one runtime contribution.
|
||||
using CentrifugalForce =
|
||||
FormSpec<quadrature::Term::centrifugal, 1, Operand<Density::Scalar>, Operand<Vector>>;
|
||||
|
||||
using ErrorNorm = FormSpec<quadrature::Term::error_norm, 0, Operand<Vector>, Operand<Vector>>;
|
||||
};
|
||||
|
||||
using FormList = TypeList<Form::MeshExtension, Form::ErrorNorm>;
|
||||
using FormList = TypeList<Form::MeshExtension, Form::GravityForce, Form::CentrifugalForce, Form::ErrorNorm>;
|
||||
};
|
||||
|
||||
struct BarotropicConstant {
|
||||
static constexpr std::string_view name = "barotropic_constant";
|
||||
|
||||
using Support = NonSpatialSupport;
|
||||
|
||||
struct Scalar final : GlobalScalarQ {
|
||||
static constexpr std::string_view symbol = "C";
|
||||
};
|
||||
|
||||
using Quantities = TypeList<Scalar>;
|
||||
using Constraints = TypeList<>;
|
||||
using FormList = TypeList<>;
|
||||
using Quantities = TypeList<Scalar>;
|
||||
using Constraints = TypeList<>;
|
||||
using FormList = TypeList<>;
|
||||
|
||||
static constexpr bool constraintsAreValid =
|
||||
validate_constraints(Constraints{});
|
||||
static constexpr bool constraintsAreValid = validate_constraints(Constraints{});
|
||||
|
||||
static_assert(constraintsAreValid);
|
||||
};
|
||||
@@ -250,16 +231,16 @@ export namespace mean_field::field {
|
||||
static constexpr std::string_view name = "specific_enthalpy";
|
||||
static constexpr int scalarOrder = 3;
|
||||
|
||||
struct Scalar final
|
||||
: ScalarQ<FieldRelation::Independent, Disc<H1, scalarOrder>> {
|
||||
using Support = DomainSupport<utils::domain::Stellar>;
|
||||
|
||||
struct Scalar final : ScalarQ<FieldRelation::Independent, Disc<H1, scalarOrder>> {
|
||||
static constexpr std::string_view symbol = "h";
|
||||
};
|
||||
|
||||
using Quantities = TypeList<Scalar>;
|
||||
using Constraints = TypeList<>;
|
||||
using Quantities = TypeList<Scalar>;
|
||||
using Constraints = TypeList<>;
|
||||
|
||||
static constexpr bool constraintsAreValid =
|
||||
validate_constraints(Constraints{});
|
||||
static constexpr bool constraintsAreValid = validate_constraints(Constraints{});
|
||||
|
||||
static_assert(constraintsAreValid);
|
||||
|
||||
@@ -268,33 +249,21 @@ export namespace mean_field::field {
|
||||
// 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>>;
|
||||
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>>;
|
||||
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>>;
|
||||
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>>;
|
||||
using EquilibriumRotation = FormSpec<quadrature::Term::hydrostatic_equilibrium, 1, Operand<Scalar>>;
|
||||
|
||||
// (C, q_h), where C is spatially constant.
|
||||
using EquilibriumConstant = FormSpec<
|
||||
@@ -305,18 +274,11 @@ export namespace mean_field::field {
|
||||
|
||||
// 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>>;
|
||||
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>>;
|
||||
using PressureIntegral = FormSpec<quadrature::Term::pressure_integral, 1, Operand<Scalar>>;
|
||||
|
||||
// Weak pressure force in the displacement test space:
|
||||
//
|
||||
@@ -325,17 +287,13 @@ export namespace mean_field::field {
|
||||
// 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<
|
||||
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 ErrorNorm = FormSpec<quadrature::Term::error_norm, 0, Operand<Scalar>, Operand<Scalar>>;
|
||||
};
|
||||
|
||||
using FormList = TypeList<
|
||||
@@ -360,9 +318,10 @@ export namespace mean_field::field {
|
||||
typename T::Quantities;
|
||||
typename T::Constraints;
|
||||
typename T::FormList;
|
||||
typename T::Support;
|
||||
|
||||
{ T::name } -> std::convertible_to<std::string_view>;
|
||||
} && isRegisteredQuantityList<typename T::Quantities> &&
|
||||
} && IsFieldSupport<typename T::Support> && isRegisteredQuantityList<typename T::Quantities> &&
|
||||
isFieldFormList<typename T::FormList>;
|
||||
|
||||
static_assert(FieldTag<Gravity>);
|
||||
@@ -376,4 +335,22 @@ export namespace mean_field::field {
|
||||
static_assert(std::same_as<
|
||||
RelationTargetT<Gravity::Flux>,
|
||||
Gravity::Potential>);
|
||||
|
||||
static_assert(std::same_as<
|
||||
FieldDomainT<Density>,
|
||||
utils::domain::Stellar>);
|
||||
|
||||
static_assert(std::same_as<
|
||||
FieldDomainT<Enthalpy>,
|
||||
utils::domain::Stellar>);
|
||||
|
||||
static_assert(std::same_as<
|
||||
FieldDomainT<Gravity>,
|
||||
utils::domain::All>);
|
||||
|
||||
static_assert(std::same_as<
|
||||
FieldDomainT<Displacement>,
|
||||
utils::domain::All>);
|
||||
|
||||
static_assert(NonSpatialField<BarotropicConstant>);
|
||||
} // namespace mean_field::field
|
||||
|
||||
Reference in New Issue
Block a user