module; #include #include #include #include #include #include export module mean_field:field.mfem; export import :field.registry; namespace mean_field::field::detail { template inline constexpr bool alwaysFalse = false; // ------------------------------------------------------------------------- // MFEM finite-element collection construction // ------------------------------------------------------------------------- template struct FecFor; template <> struct FecFor { static std::unique_ptr make( int familyOrder, int dimension ) { return std::make_unique( familyOrder, dimension ); } }; template <> struct FecFor

{ static std::unique_ptr make( int familyOrder, int dimension ) { return std::make_unique( familyOrder, dimension ); } }; template <> struct FecFor { static std::unique_ptr make( int familyOrder, int dimension ) { return std::make_unique( familyOrder, dimension ); } }; template <> struct FecFor { static std::unique_ptr make( int familyOrder, int dimension ) { return std::make_unique( familyOrder, dimension ); } }; // ------------------------------------------------------------------------- // MFEM polynomial-order interpretation // // familyOrder is the collection constructor argument. // // For RT_p: // value order = p + 1 // divergence order = p // normal-trace order = p // // This distinction is what allows Disc and Disc to form a // compatible pair while still giving different value-shape orders. // ------------------------------------------------------------------------- template struct MfemOperandOrder; template struct MfemOperandOrder> { static constexpr int orderValue = []() consteval { if constexpr (GlobalScalarQuantity) { static_assert( std::same_as, "Global scalars support only the value operation." ); return 0; } else { using Space = typename QuantityT::Space; constexpr int familyOrder = QuantityT::familyOrder; if constexpr (std::same_as) { if constexpr (std::same_as) { return familyOrder + 1; } else { return familyOrder; } } else if constexpr ( std::same_as ) { static_assert( std::same_as, "Only RT quantities currently support the divergence " "polynomial-order rule." ); return familyOrder; } else if constexpr ( std::same_as ) { static_assert( std::same_as, "Only H1 quantities currently support the gradient " "polynomial-order rule." ); return familyOrder > 0 ? familyOrder - 1 : 0; } else if constexpr ( std::same_as ) { static_assert( std::same_as, "Only ND quantities currently support the curl " "polynomial-order rule." ); return familyOrder > 0 ? familyOrder - 1 : 0; } else if constexpr ( std::same_as ) { static_assert( std::same_as, "Only RT quantities currently support the normal-trace " "polynomial-order rule." ); return familyOrder; } else { static_assert( alwaysFalse, "Unsupported MFEM field operation." ); } } }(); }; // ------------------------------------------------------------------------- // Static polynomial-order contribution of an entire form // ------------------------------------------------------------------------- template struct MfemFormOrder; template < auto PolicyKeyV, std::size_t DynamicOrderCountV, FieldOperand... OperandTs> struct MfemFormOrder< FormSpec> { static constexpr int staticOrder = (MfemOperandOrder::orderValue + ... + 0); }; // ------------------------------------------------------------------------- // MFEM vector-dimension and ordering rules // // Vector H1/L2 fields are represented using multiple copies of a scalar // finite-element space. RT and ND elements are intrinsically vector-valued // and therefore use vdim = 1. // ------------------------------------------------------------------------- template int get_vdim(int spaceDimension) { if (spaceDimension <= 0) { throw std::invalid_argument("Space dimension must be positive."); } if constexpr (QuantityT::rankValue == 0) { return 1; } else if constexpr ( std::same_as || std::same_as ) { return spaceDimension; } else { return 1; } } template constexpr mfem::Ordering::Type get_ordering() { if constexpr ( QuantityT::rankValue == 1 && (std::same_as || std::same_as) ) { return mfem::Ordering::byVDIM; } else { return mfem::Ordering::byNODES; } } // ------------------------------------------------------------------------- // Quantity-specific MFEM realization // // Backend choices that are part of a field definition live here rather // than leaking into FEM setup or call sites. // ------------------------------------------------------------------------- template struct MfemQuantityTraits { static std::unique_ptr make_fec(int dimension) { return FecFor::make( QuantityT::familyOrder, dimension ); } static constexpr mfem::Ordering::Type ordering = get_ordering(); }; template <> struct MfemQuantityTraits { static std::unique_ptr make_fec(int dimension) { return std::make_unique( Gravity::Flux::familyOrder, dimension, mfem::BasisType::GaussLobatto, mfem::BasisType::IntegratedGLL ); } static constexpr mfem::Ordering::Type ordering = mfem::Ordering::byNODES; }; template <> struct MfemQuantityTraits { static std::unique_ptr make_fec(int dimension) { return FecFor

::make( Displacement::Vector::familyOrder, dimension ); } static constexpr mfem::Ordering::Type ordering = mfem::Ordering::byNODES; }; } // namespace mean_field::field::detail export namespace mean_field::field { // ------------------------------------------------------------------------- // User-facing field type // // The object itself is currently a zero-cost compile-time descriptor: // // Field gravityField; // // MFEM construction and typed quadrature-query generation are provided as // static operations. Runtime ownership can later be added without changing // Gravity, Displacement, or their form definitions. // ------------------------------------------------------------------------- template class Field { public: using Tag = TagT; // --------------------------------------------------------------------- // MFEM finite-element collection construction // --------------------------------------------------------------------- template requires typeListContains< QuantityT, typename TagT::Quantities> static std::unique_ptr make_fec(int dimension) { if (dimension <= 0) { throw std::invalid_argument("Mesh dimension must be positive."); } return detail::MfemQuantityTraits::make_fec(dimension); } // --------------------------------------------------------------------- // MFEM parallel finite-element space construction // // The finite-element collection must outlive the returned space. // --------------------------------------------------------------------- template requires typeListContains< QuantityT, typename TagT::Quantities> static std::unique_ptr make_fespace( mfem::ParMesh &mesh, mfem::FiniteElementCollection &finiteElementCollection ) { return std::make_unique( &mesh, &finiteElementCollection, detail::get_vdim(mesh.SpaceDimension()), detail::MfemQuantityTraits::ordering ); } // --------------------------------------------------------------------- // Typed quadrature-query construction // // geometryWeightOrder is supplied at runtime because it depends on the // actual element transformation. // // dynamicOrders contains the form-specific polynomial orders that are // not represented by registered compile-time quantities. // // Examples: // // Density::Form::CenterOfMass: // { positionOrder } // // Gravity source forms need no dynamic orders because density and // potential are both registered quantities. // // The completed base order is stored in Query::base_order, so Policy // does not need to understand divergence, RT conventions, or individual // field layouts. // --------------------------------------------------------------------- template requires typeListContains< FormT, typename TagT::FormList> static constexpr quadrature::Query make_query( quadrature::QuadratureRole role, int geometryWeightOrder, std::array< int, FormT::dynamicOrderCount> dynamicOrders = {}, utils::DOMAINS domain = utils::DOMAINS::ALL, quadrature::MappingKind mapping = quadrature::MappingKind::none ) { if (geometryWeightOrder < 0) { throw std::invalid_argument( "Geometry weight order cannot be negative." ); } int baseOrder = detail::MfemFormOrder::staticOrder + geometryWeightOrder; for (const int dynamicOrder : dynamicOrders) { if (dynamicOrder < 0) { throw std::invalid_argument( "Dynamic polynomial orders cannot be negative." ); } baseOrder += dynamicOrder; } return { .term = FormT::policyKey, .role = role, .domain = domain, .mapping = mapping, .trial_order = 0, .test_order = 0, .coefficient_order = 0, .geometry_weight_order = geometryWeightOrder, .base_order = baseOrder }; } }; static_assert(FieldTag); static_assert(FieldTag); static_assert(FieldTag); static_assert(FieldTag); } // namespace mean_field::field