From 87a1026be630c8d179fc0d4332c765cde7b3bf95 Mon Sep 17 00:00:00 2001 From: Emily Boudreaux Date: Mon, 9 Nov 2026 13:23:08 -0500 Subject: [PATCH] feat(operators): Moved most of operators to backend Operators defines how SERiF concepts map into MFEM (or generally any backend) concenpts and types. Since we are trying to seperate the specific MFEM implementation out of SERiF most of the code that was here has moved to backend. However, what remains is some general code related to the block layout of a systen and the contributions of different equations to different parts of that block layout --- src/include/serif/operators/concepts.hpp | 100 ++++++++++++++++++ src/include/serif/operators/contributions.hpp | 85 +++++++++++++++ .../serif/operators/linearized_block.hpp | 8 ++ .../operators/linearized_system_view.hpp | 64 +++++++++++ src/include/serif/operators/plan.hpp | 85 +++++++++++++++ 5 files changed, 342 insertions(+) create mode 100644 src/include/serif/operators/concepts.hpp create mode 100644 src/include/serif/operators/contributions.hpp create mode 100644 src/include/serif/operators/linearized_block.hpp create mode 100644 src/include/serif/operators/linearized_system_view.hpp create mode 100644 src/include/serif/operators/plan.hpp diff --git a/src/include/serif/operators/concepts.hpp b/src/include/serif/operators/concepts.hpp new file mode 100644 index 00000000..a8dba3a3 --- /dev/null +++ b/src/include/serif/operators/concepts.hpp @@ -0,0 +1,100 @@ +#pragma once + +#include +#include + +#include "serif/discretization/blocks/traits.hpp" +#include "serif/discretization/functional_space/space.hpp" +#include "serif/equations/concepts.hpp" + +namespace serif::operators { + // These restrictions describe the existing volume implementation, + // not which physical equations may be declared in serif::equations. + template + concept ScalarValueTerm = + discretization::blocks::GlobalScalarTerm || + (discretization::blocks::SpatialTerm && + discretization::blocks::SpatialTermTraits::rank == 0 && + ( + discretization::blocks::SpatialTermTraits::family == discretization::functional_space::Family::h1 || + discretization::blocks::SpatialTermTraits::family == discretization::functional_space::Family::l2 + )); + + template + concept ScalarValueOperand = + discretization::forms::IsOperand && + ScalarValueTerm && + std::same_as; + + /// A conforming scalar gradient; discontinuous fields also require face terms. + template + concept ScalarGradientOperand = + discretization::forms::IsOperand && + discretization::blocks::SpatialTerm && + discretization::blocks::SpatialTermTraits::rank == 0 && + discretization::blocks::SpatialTermTraits::family == discretization::functional_space::Family::h1 && + std::same_as; + + /** @brief Full vector gradients are supported for componentwise H1 fields. */ + template + concept VectorGradientOperand = + discretization::forms::IsOperand && + discretization::blocks::SpatialTerm && + discretization::blocks::SpatialTermTraits::rank == 1 && + discretization::blocks::SpatialTermTraits::family == discretization::functional_space::Family::h1 && + std::same_as; + + template + struct HasScalarVolumeOperands : std::false_type {}; + + template + struct HasScalarVolumeOperands> : + std::bool_constant< + sizeof...(Operands) != 0 && + (ScalarValueOperand && ...) && + (requires(const EquationT& equation, Operands operand) { + {equation.additional_order(operand, 1)}->std::same_as; + } && ...)> {}; + + /// Equations executable by the current scalar-value volume operator. + template + concept ScalarVolumeEquation = + equations::Equation && + ScalarValueTerm && + HasScalarVolumeOperands::value; + /// Mathematical operand combinations implemented by the current volume backend. + template + concept MappedOperand = + discretization::forms::IsOperand && + ( + ScalarValueOperand || ScalarGradientOperand || VectorGradientOperand || + ( + discretization::blocks::SpatialTerm && + discretization::blocks::SpatialTermTraits::rank == 1 && + discretization::blocks::SpatialTermTraits::family == discretization::functional_space::Family::h1 && + ( + std::same_as || + std::same_as + ) + ) + ); + + template struct VolumeOperands : std::false_type {}; + template + struct VolumeOperands> : std::bool_constant< + sizeof...(O) != 0 && (MappedOperand && ...) && + (requires(const C& contribution, O operand) { + {contribution.additional_order(operand, 1)}->std::same_as; } && ...)> {}; + + template struct VolumeContributions : std::false_type {}; + template + struct VolumeContributions> + : std::bool_constant<((discretization::domain::IsDomainOrSet> && + MappedOperand> && VolumeOperands::value) && + ...)> {}; + + /// One or more supported weak volume contributions assembled into a single residual row. + template + concept VolumeEquation = equations::Equation && VolumeContributions>::value; + +} // namespace serif::operators diff --git a/src/include/serif/operators/contributions.hpp b/src/include/serif/operators/contributions.hpp new file mode 100644 index 00000000..e35b7328 --- /dev/null +++ b/src/include/serif/operators/contributions.hpp @@ -0,0 +1,85 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "serif/equations/concepts.hpp" + +namespace serif::operators { + /// Retain one leaf equation and the constant coefficient of its arithmetic expression. + template + struct ContributionBinding { + std::shared_ptr equation; + double coefficient; + }; + + namespace detail { + /// A returned tuple may own generated equations or borrow an equation's existing children. + /// Retaining its parent keeps reference tuples valid without copying move-only equations. + template + struct ContributionChildren { + std::shared_ptr parent; + decltype(std::declval().contributions()) values; + + /// Evaluate contributions once, retaining either its returned value or its returned reference. + explicit ContributionChildren(std::shared_ptr equation) : + parent(std::move(equation)), + values(parent->contributions()) { + } + }; + + /// Flatten equation arithmetic while preserving every generated tuple and borrowed parent. + template + [[nodiscard]] auto bind_contributions(std::shared_ptr equation, double coefficient) { + if constexpr (requires { equation->contributions(); }) { + const auto children = std::make_shared>(std::move(equation)); + return std::apply([&](const auto&... child) { + // Aliasing pointers address each child but retain the tuple and its parent together. + return std::tuple_cat(bind_contributions( + std::shared_ptr>(children, &child), coefficient + )...); + }, children->values); + } else { + return std::tuple{ContributionBinding{ + .equation = std::move(equation), + .coefficient = coefficient + }}; + } + } + + /// Visit arithmetic leaves without retaining them beyond the supplied callback. + template + void visit_contributions( + const EquationT& equation, + FunctionT& function, + std::size_t& index, + double coefficient + ) { + if constexpr (requires { equation.contributions(); }) { + std::apply([&](const auto&... child) {(visit_contributions(child, function, index, coefficient), ...);},equation.contributions()); + } else { + function(equation, index++, coefficient); + } + } + } // namespace detail + + /// Give each retained leaf a compile-time tuple position for independently cached contribution results. + template + [[nodiscard]] auto bind_contributions(std::shared_ptr equation) { + return detail::bind_contributions(std::move(equation), 1.0); + } + + /// Visit leaves in declaration order, multiplying constant coefficients along each path. + /// The callback receives (leaf equation, unique contribution index, coefficient). + template + void for_each_contribution( + const EquationT& equation, + FunctionT&& function + ) { + std::size_t index = 0; + detail::visit_contributions(equation, function, index, 1.0); + } +} // namespace serif::operators diff --git a/src/include/serif/operators/linearized_block.hpp b/src/include/serif/operators/linearized_block.hpp new file mode 100644 index 00000000..60428d36 --- /dev/null +++ b/src/include/serif/operators/linearized_block.hpp @@ -0,0 +1,8 @@ +#pragma once + +#include "serif/backend/mfem/operators/linearized_block.hpp" + +namespace serif::operators { + using backend::mfem::LinearizedBlock; + using backend::mfem::linearized_block; +} diff --git a/src/include/serif/operators/linearized_system_view.hpp b/src/include/serif/operators/linearized_system_view.hpp new file mode 100644 index 00000000..d8a4a42b --- /dev/null +++ b/src/include/serif/operators/linearized_system_view.hpp @@ -0,0 +1,64 @@ +#pragma once + +#include + +#include "serif/discretization/blocks/system_layout.hpp" +#include "serif/utils/dependencies/dependency_context.hpp" + +namespace serif::operators +{ + /** @brief Identify a physical Jacobian block by its equation rows and unknown coordinates. */ + template + struct PhysicalBlock final + { + using Rows = RowsT; + using Values = ValuesT; + }; + + /** @brief Borrow an accepted collection of physical blocks and its independently ordered system layout. */ + template + class LinearizedSystemView final + { + public: + using LayoutType = std::remove_cvref_t; + using BlockSources = std::remove_cvref_t; + + /** @brief Both owners must outlive the view and remain at this accepted linearization while it is used. */ + LinearizedSystemView(const LayoutType& layout, const BlockSources& blocks) : m_layout(layout), m_blocks(blocks) + { + } + + /** @brief A borrowed system cannot retain temporary coordinate metadata. */ + LinearizedSystemView(const LayoutType&&, const BlockSources&) = delete; + /** @brief A borrowed system cannot retain temporary block sources. */ + LinearizedSystemView(const LayoutType&, const BlockSources&&) = delete; + + /** @brief Access a declared physical coupling; absent blocks are compile-time errors. */ + template + requires utils::dependencies::BindingsWithBinding> + [[nodiscard]] const auto& block() const + { + return m_blocks.template get>(); + } + + /** @brief Access the shared coordinate description without taking ownership. */ + [[nodiscard]] const LayoutType& layout() const noexcept + { + return m_layout; + } + + /** @brief Supply accepted block sources to ordinary dependency preparation. */ + [[nodiscard]] const BlockSources& blocks() const noexcept + { + return m_blocks; + } + + private: + const LayoutType& m_layout; + const BlockSources& m_blocks; + }; + + /** @brief Infer canonical owner types while retaining the explicit borrowing lifetime checks. */ + template + LinearizedSystemView(const Layout&, const Blocks&) -> LinearizedSystemView; +} // namespace serif::operators diff --git a/src/include/serif/operators/plan.hpp b/src/include/serif/operators/plan.hpp new file mode 100644 index 00000000..4ff219f0 --- /dev/null +++ b/src/include/serif/operators/plan.hpp @@ -0,0 +1,85 @@ +#pragma once + +#include +#include "serif/utils/dependencies/key_list_operations.hpp" +#include "serif/discretization/forms/forms.hpp" +#include "serif/operators/concepts.hpp" + +namespace serif::operators { + namespace detail { + template struct AsKeyList; + template struct AsKeyList> { + using type = utils::dependencies::CombinedKeyList...>; + }; + template struct Join { + using type = typename utils::dependencies::CombinedKeyList::type...>::Keys; + }; + template struct ContributionOperands; + template struct ContributionOperands> : Join {}; + template struct Columns; + template + struct Columns> { + using type = typename utils::dependencies::CombinedKeyList>...>::Keys; + }; + + template struct CouplingForm { + using Trial = typename Input::term_type; + using Test = typename TestOperand::term_type; + using type = std::conditional_t< + std::same_as, + discretization::forms::divergence, + std::conditional_t, + discretization::forms::divergence, + std::conditional_t, discretization::forms::mass, + discretization::forms::source>>>; + }; + } // namespace detail + + template >::type> + struct EquationPlan; + + template struct EquationPlan> { + using Test = typename EquationT::Test; + using Support = discretization::blocks::SupportOf; + using Operands = utils::types::TypeList; + using Columns = typename detail::Columns::type; + static constexpr std::size_t operand_count = sizeof...(O); + static constexpr std::size_t column_count = equations::OperandValues::count; + static constexpr int component_count = (discretization::forms::component_count> + ... + 0); + + template + using Form = typename detail::CouplingForm>::type; + + /// Locate the unique coefficient column shared by every operation on a term. + template static consteval std::size_t column_index() { + return equations::OperandValues::template index(); + } + /// Locate the first scalar component of an operand in pointwise derivative storage. + template static consteval int component_offset() { + constexpr std::array counts{discretization::forms::component_count>...}; + constexpr auto index = equations::OperandValues::template index(); + int offset = 0; + for (std::size_t i = 0; i < index; ++i) { + offset += counts[i]; + } + return offset; + } + /// Visit all sampled operations in stable first-appearance order. + template static void for_each_operand(FunctionT&& function) { + (function(O{}, equations::OperandValues::template index()), ...); + } + /// Visit only the operands needed by this contribution, retaining global sample indices. + template static void for_each_input(FunctionT&& function) { + [&](utils::types::TypeList) { + (function(Input{}, equations::OperandValues::template index()), ...); + }(typename ContributionT::Operands{}); + } + /// Visit each physical coefficient vector once for binding and packed actions. + template static void for_each_column(FunctionT&& function) { + [&](utils::types::TypeList) { + (function(Column{}, equations::OperandValues::template index()), ...); + }(Columns{}); + } + }; +} // namespace serif::operators