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:
2026-08-23 10:13:53 -04:00
parent dc912fd15e
commit 0f3ca8050b
137 changed files with 29975 additions and 16389 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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