From 0eef62313025cb248040b86420e7889293f95dbc Mon Sep 17 00:00:00 2001 From: Emily Boudreaux Date: Thu, 8 Oct 2026 11:49:03 -0400 Subject: [PATCH] feat(equations): added contribution(s) which allows equations authors to more quickly write equations without writing the boiler plate required by the backend --- src/include/serif/equations/contributions.hpp | 184 ++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100644 src/include/serif/equations/contributions.hpp diff --git a/src/include/serif/equations/contributions.hpp b/src/include/serif/equations/contributions.hpp new file mode 100644 index 00000000..758e2ecc --- /dev/null +++ b/src/include/serif/equations/contributions.hpp @@ -0,0 +1,184 @@ +#pragma once + +/** + * @file contributions.hpp + * @author Emily M. Boudreaux + * + * @brief File containing the Contribution struct. This is effectively a helper struct for + * authoring equations with fewer lines of boiler-plate. The idea here is that every equation must report + * certain members so that the backend can inspect at compile time. An author could certainly + * write these members themselves, but the Contribution struct auto generates + * those members based on its template parameters. Therefore, by inheriting from + * the Contribution struct an equation author can avoid writing those members themselves. Note that while + * this is inheritance it is still a compile-time relation. Contribution does + * not add any runtime overhead to the equation. + */ + +#include +#include +#include +#include "serif/discretization/blocks/traits.hpp" +#include "serif/discretization/forms/operands.hpp" + +namespace serif::equations { + /** + * @struct Contribution + * + * @brief A helper struct for authoring equations with fewer lines of boiler-plate. The idea here is that every equation must report + * certain members so that the backend can inspect at compile time. An author could certainly + * write these members themselves, but the Contribution struct auto generates + * those members based on its template parameters. Therefore, by inheriting from + * the Contribution struct an equation author can avoid writing those members themselves. Note that while + * this is inheritance it is still a compile-time relation. Contribution does + * not add any runtime overhead to the equation. + * + * @tparam TestOperandT The test operation type, which must be a valid operand (@ref IsOperand). + * @tparam SupportT The support type, which must be a valid support (@ref IsSupport). This defaults to the support of the test term. + * + * @note The support type is, by default, read from the test term. There are very few situations where + * you should need to override this default. + * + * @note Read this as "residuals are contributed to the row(s) defined by the test operation. e.g Some density constraint function + * might contribute to the residuals of the overall system through some density row. + */ + template > + struct Contribution { + /** + * @brief The test type of the contribution. + * + * The terminology Test here comes from finite element analysis. Specifically, the test type is the type + * of the test function used in the weak formulation of the equation. More concretely, + * the test type is the type for which the residuals are computed. Therefore think of + * this as the type of the residual row that this contribution contributes to. There are a lot + * of repetitions of the word type here, which I can recognize is a bit confusing. Effectively, the way + * that we have structured much of this code is that we embed particular functions into the type system. Therefore + * when I say "the type of the test function" what I mean is the c++ type that we have defined + * to represent the test function. + * + * @note The above comment would probably make any actual writer shake in their boots. I'm sure there is a better way + * to phrase that; however, I am at a loss for the moment. The TL;DR here is that since we + * encode so much information into the type system we use the word type a lot. If this makes you rationally upset + * please email me... + */ + using Test = TestOperandT::term_type; + using TestOperand = TestOperandT; ///< The operand type of the contribution (associating a test function and a term) + using Support = SupportT; ///< The domain support of the contribution (which then boils down to the dofs which are supported by the term on that test function) + }; + + /** + * @struct TestOperandTraits + * + * @brief A helper struct to extract the test operand type from a contribution or equation. This is used to determine the test type of an equation or contribution. + * + * @note The default void template parameter helps us to use SFINAE to detect if the type has a TestOperand member type. + * + * @todo Can we replace SFINAE here with requires or concepts? + * + * @tparam T The type to extract the test operand from + */ + template + struct TestOperandTraits { + using type = discretization::forms::Operand; ///< The default test operand type is the operand of the test type of the contribution or equation. + }; + + /** + * @brief Specialization of TestOperandTraits for types that have a TestOperand member type. + * This allows us to extract the test operand type from contributions or equations that define a TestOperand type. + */ + template + struct TestOperandTraits> { + using type = T::TestOperand; ///< The test operand type is the TestOperand member type of the contribution or equation. + }; + + /** + * @brief Helper alias to extract the test operand type from a contribution or equation. + * + * @tparam T The type to extract the test operand from + */ + template + using TestOperandOf = TestOperandTraits::type; + + /** + * @brief Helper alias to extract the support type for a given contribution. + * + * @tparam T The type to extract the support type from + */ + template + struct ContributionSupport { + using type = discretization::blocks::SupportOf; ///< The default support type is the support of the test type of the contribution or equation. + }; + + /** + * @brief Specialization of ContributionSupport for types that have a Support member type. + * This allows us to extract the support type from contributions or equations that define a Support type. + */ + template + struct ContributionSupport> { + using type = T::Support; ///< The support type is the Support member type of the contribution or equation. + }; + + /** + * @brief Helper alias to extract the support type for a given contribution. + * + * @tparam T The type to extract the support type from + */ + template + using SupportOf = ContributionSupport::type; + + /** + * @brief Helper struct to pack a list of contributions into a tuple. This is used to define the set of contributions of an equation. + * + * @tparam T The type to pack into a tuple of contributions. This can be a single contribution or a tuple of contributions. + */ + template + struct Contributions { + using type = std::tuple; ///< tuple of whatever is T + }; + + /** + * @brief Type representing the contributions of an equation. + * @tparam TupleT Some tuple (e.g. from Contributions::type). In this default specialization we always let the type be void + */ + template + struct ContributionTuple { + using type = void; ///< The default type is void, indicating that the contributions are not valid. + }; + + /** + * @brief Specialization of ContributionTuple for tuples of contributions. This allows us to extract the contributions from a tuple of contributions. + * @tparam Ts The types of the contributions in the tuple. + * + * @note The syntax of this specialization is not easy to parse. What we say here is + * - We accept some pack of types Ts... + * - We unpack all of those into the first template argument of the default specialization of ContributionTuple, which is a tuple of Ts... + * - We use SFINAE to check if the tuple of contributions is valid by + * - Checking if a tuple of the test operands of the contributions is valid (i.e. all contributions have the same test operand) + * + * @todo Replace SFINAE with requires or concepts if possible. This would make the code more readable and easier to understand. + */ + template + struct ContributionTuple, std::void_t>::type>()...))>> { + /** + * @brief The tuple representing all contributions, Using the pack of contributions Ts... we can extract the contributions of each and then concatenate them into a single tuple. + * using C++17 folding + */ + using type = decltype(std::tuple_cat(std::declval>::type>()...)); + }; + + /** + * @brief Specialization of Contributions for types that have the contributions() method. + * + * @todo Replace SFINAE with requires or concepts if possible. This would make the code more readable and easier to understand. + */ + template + struct Contributions().contributions())>> : + ContributionTuple().contributions())>> {}; + + /** + * @brief Helper alias to extract the contributions (::type) of some type T. + * + * @tparam T The type to extract the contributions from + */ + template + using ContributionsOf = Contributions>::type; +} // namespace serif::equations