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