feat(equations): added contribution(s) which allows equations authors to more quickly write equations without writing the boiler plate required by the backend

This commit is contained in:
2026-10-08 11:49:03 -04:00
parent 78c78f6bd4
commit 0eef623130

View File

@@ -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 <tuple>
#include <type_traits>
#include <utility>
#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 <discretization::forms::IsOperand TestOperandT, typename SupportT = discretization::blocks::SupportOf<typename TestOperandT::term_type>>
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 <typename T, typename = void>
struct TestOperandTraits {
using type = discretization::forms::Operand<typename T::Test>; ///< 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 <typename T>
struct TestOperandTraits<T, std::void_t<typename T::TestOperand>> {
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 <typename T>
using TestOperandOf = TestOperandTraits<T>::type;
/**
* @brief Helper alias to extract the support type for a given contribution.
*
* @tparam T The type to extract the support type from
*/
template <typename T, typename = void>
struct ContributionSupport {
using type = discretization::blocks::SupportOf<typename T::Test>; ///< 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 <typename T>
struct ContributionSupport<T, std::void_t<typename T::Support>> {
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 <typename T>
using SupportOf = ContributionSupport<T>::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 <typename T, typename = void>
struct Contributions {
using type = std::tuple<T>; ///< 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 <typename TupleT, typename = void>
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 <typename... Ts>
struct ContributionTuple<std::tuple<Ts...>, std::void_t<decltype(std::tuple_cat(std::declval<typename Contributions<std::remove_cvref_t<Ts>>::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<typename Contributions<std::remove_cvref_t<Ts>>::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 <typename T>
struct Contributions<T, std::void_t<decltype(std::declval<const T&>().contributions())>> :
ContributionTuple<std::remove_cvref_t<decltype(std::declval<const T&>().contributions())>> {};
/**
* @brief Helper alias to extract the contributions (::type) of some type T.
*
* @tparam T The type to extract the contributions from
*/
template <typename T>
using ContributionsOf = Contributions<std::remove_cvref_t<T>>::type;
} // namespace serif::equations