docs(forms): added doxygen comments throughout

This commit is contained in:
2026-10-01 16:10:32 -04:00
parent b030d3010f
commit 05df03b1c1
6 changed files with 436 additions and 34 deletions

View File

@@ -1,10 +1,22 @@
#pragma once
/**
* @file concept.hpp
* @author Emily M. Boudreaux
* @brief This file defines concepts for forms in the discretization framework.
*/
#include "serif/discretization/forms/forms.hpp"
namespace serif::discretization::forms {
/**
* @brief Concept for types that are forms in the discretization framework. In order satisfy this concept a type must derive from the form base class and must define a member type called operand_types which is intended to be a type list of the trial and test terms for the form.
*
* @todo Consider strengthening this concept to enforce that the operand_types type list contains only operand types and that there is an entry for TrialT and TestT. This would require additional compile-time checks to ensure that the types in the operand_types list are valid operand types and that they include the required trial and test terms.
*/
template <typename FormCandidate>
concept Form = std::derived_from<std::remove_cvref_t<FormCandidate>, form> && // A form must derive from form
requires {
typename std::remove_cvref_t<FormCandidate>::operand_types; // a form must define a type list of operand types for the trial and test terms
}; // We could consider strengthening this to enforce that the type list (operand_types) contains only operand types and that there is an entry for TrialT and TestT
};
}

View File

@@ -1,45 +1,85 @@
#pragma once
/**
* @file form_traits.hpp
* @author Emily M. Boudreaux
* @brief Compile-time traits for forms, used to extract types and properties from template parameters.
*/
#include "serif/discretization/forms/concept.hpp"
#include "serif/discretization/forms/forms.hpp"
namespace serif::discretization::forms {
/**
* @brief Compile-time traits for forms, used to extract types and properties from template parameters. This struct is specialized for each form type to provide specific traits such as trial and test order reductions.
* @tparam FormT The form type for which to extract traits.
*/
template <Form FormT>
struct FormTraits {
static constexpr int trial_order_reduction = 0;
static constexpr int test_order_reduction = 0;
};
/**
* @brief Specialization of FormTraits for the mass form, which has no order reduction for either trial or test terms.
* @tparam TrialT The trial term type.
* @tparam TestT The test term type.
*/
template <blocks::Term TrialT, blocks::Term TestT>
struct FormTraits<mass<TrialT, TestT>> {
static constexpr int trial_order_reduction = 0;
static constexpr int test_order_reduction = 0;
};
/**
* @brief Specialization of FormTraits for the divergence form, which has an order reduction of 1, to satisfy the Inf-Sup condition, for the trial term and no order reduction for the test term.
* @tparam TrialT The trial term type.
* @tparam TestT The test term type.
*/
template <blocks::Term TrialT, blocks::Term TestT>
struct FormTraits<divergence<TrialT, TestT>> {
static constexpr int trial_order_reduction = 1;
static constexpr int test_order_reduction = 0;
};
/**
* @brief Specialization of FormTraits for the source form, which has no order reduction for either trial or test terms.
* @tparam TrialT The trial term type.
* @tparam TestT The test term type.
*/
template <blocks::Term TrialT, blocks::Term TestT>
struct FormTraits<source<TrialT, TestT>> {
static constexpr int trial_order_reduction = 0;
static constexpr int test_order_reduction = 0;
};
/**
* @brief Specialization of FormTraits for the force form, which has no order reduction for either trial or test terms.
* @tparam TrialT The trial term type.
* @tparam TestT The test term type.
*/
template <blocks::Term TrialT, blocks::Term TestT>
struct FormTraits<force<TrialT, TestT>> {
static constexpr int trial_order_reduction = 0;
static constexpr int test_order_reduction = 0;
};
/**
* @brief Specialization of FormTraits for the boundary_flux form, which has no order reduction for either trial or test terms.
* @tparam TrialT The trial term type.
* @tparam TestT The test term type.
*/
template <blocks::Term TrialT, blocks::Term TestT>
struct FormTraits<boundary_flux<TrialT, TestT>> {
static constexpr int trial_order_reduction = 0;
static constexpr int test_order_reduction = 0;
};
/**
* @brief Specialization of FormTraits for the projection form, which has no order reduction for either trial or test terms.
* @tparam TrialT The trial term type.
* @tparam TestT The test term type.
*/
template <blocks::Term TrialT, blocks::Term TestT>
struct FormTraits<projection<TrialT, TestT>> {
static constexpr int trial_order_reduction = 0;

View File

@@ -1,5 +1,21 @@
#pragma once
/**
* @file forms.hpp
* @author Emily M. Boudreaux
* @brief This file defines the forms used in the discretization framework, including mass, divergence, source, force, boundary flux, and projection forms.
*
* @note If you are a developer tasked with the likely horrid task of adding some new equation that cannot be represented within the current framework
* that SERiF provides, this should probably be your first stop. This file essentially defines the mathematical operations that SERiF can perform. As you
* can see below none of these forms are particularly complex, and they certainly do not actually describe *how* to perform these operations. Rather,
* these form the base of a chain of compile-time probability that SERiF *knows* how to do something. Effectively this file is a contract with the
* rest of the code that says "If someone asks for the divergence of a trial field against some test field that is okay because I have claimed in this
* file that I know how to do that". The actual implementation lives elsewhere.
*
* @todo Come back and fill in more information about elsewhere. If you are the developer from above and you are reading this todo, then I am, and I mean
* this truly, very sorry....
*/
#include <concepts>
#include <type_traits>
@@ -9,65 +25,158 @@
#include "serif/meta/names.hpp"
namespace serif::discretization::forms {
/**
* @brief The form struct is a tag type that is used to identify a form in the discretization. It is used to group together forms that belong to the same mathematical operation. Each form should have a unique name and a list of operand types that belong to it.
*/
struct form {};
/**
* @brief The mass form represents the mass matrix operation in the discretization framework. It takes a trial term and a test term as template parameters and defines the operand types for the mass operation.
* @tparam TrialT The trial term type.
* @tparam TestT The test term type.
*
* It is important to not that we do not use mass here in the physics sense of the term; rather, mass is a common term in finite-element analysis. The mass matrix is a particular matrix that arrises in the weak discretization
* of a wide variety of PDEs. Consider the following example:
*
* You have some scalar field \f$u(x,t)\f$ on some domain \f$\Omega\f$ which satisfies
*
* \f[
* \frac{\partial u}{\partial t} = f(x,t)
* \f]
*
* In the simplest case let every spatial point be independent such that there are no spatial derivatives. We first take this into weak form
*
* \f[
* \int_{\Omega}v\frac{\partial u}{\partial t}d\Omega = \int_{\Omega}vf(x,t)d\Omega
* \f]
*
* Now we must discretize this over some meshing, lets call the approximated solution \f$u_h\f$ such that, approximating with \f$N\f$ basis functions \f$\phi_j(x)\f$ we have
*
* \f[
* u\approx u_h = \sum_{j=1}^{N}c_{j}(t)\phi_{j}(x)
* \f]
*
* Then we can rewrite our weak form as
*
* \f[
* \int_{\Omega}v\sum_{j=1}^{N}c_{j}'(t)\phi_{j}(x)d\Omega = \int_{\Omega}vf(x,t)d\Omega
* \f]
*
* If we use the galerkin method such that our test function is the same as our basis functions, then we can write this as
*
* \f[
* \int _{\Omega}\phi_{i}(x)\sum_{j=1}^{N}c_{j}'(t)\phi_{j}(x)d\Omega = \int_{\Omega}\phi_{i}(x)f(x,t)d\Omega
* \f]
*
* Since we have defined the problem so that the basis functions are not time-dependent we can rearrange this as
*
* \f[
* \sum_{j}\left(\int_{\Omega}\phi_{i}\phi_{j}d\Omega\right)c_{j}'(t) = \int_{\Omega}\phi_{i}f(x,t)d\Omega
* \f]
*
* When then define
*
* \f[
* M_{ij} = \int_{\Omega}\phi_{i}\phi_{j}d\Omega
* \f]
*
* This is the so called ``mass matrix''. The careful reader will note that this really need have nothing to do with mass. Formally the mass matrix just represents the \f$L^{2}\f$ inner product
* of the basis functions. Historically (from some rudimentary googling) this seems to be a holdover from the early days
* of finite element analysis and models of the longitudinal vibrations of a elastic bar. You can go through that PDE following the same general steps as above
* and you also arrive at some \f$L^{2}\f$ inner product of the basis functions (along with another term). The inner product term
* turns out to be associated with the mass of the bar while the other term turns out to be associated with the stiffness of the bar (and therefore matrices of that
* form have become known as ``stiffness matrices'').
*
* There is likely a good argument for us to use a different name, clarity may be improved. However, most resource on finite element analysis will use the term
* mass matrix so I have decided to stick with that for now.
*/
template <blocks::Term TrialT, blocks::Term TestT>
struct mass final : form {
static constexpr std::string_view name = meta::template_name<mass>();
static constexpr std::string_view name = meta::template_name<mass>(); ///< Compile time reflected name of the form
using trial_type = TrialT;
using test_type = TestT;
using trial_type = TrialT; ///< The trial term type for the mass form.
using test_type = TestT; ///< The test term type for the mass form.
using operand_types = utils::types::TypeList<Operand<TrialT>, Operand<TestT>>;
using operand_types = utils::types::TypeList<Operand<TrialT>, Operand<TestT>>; ///< Type list of operand types for the mass form, used for compile-time reflection and iteration over the operands.
};
/**
* @brief The divergence form represents the divergence operation in the discretization framework. It takes a trial term and a test term as template parameters and defines the operand types for the divergence operation.
* @tparam TrialT The trial term type.
* @tparam TestT The test term type.
*
* @note The divergence form has an order reduction of 1 for the trial term to satisfy the Inf-Sup condition, while the test term has no order reduction. You can see where this
* reduction is defined in code by looking at the @ref form_traits.hpp file (@ref serif::discretization::forms::FormTraits<divergence<TrialT, TestT>> "Specialization for divergence").
*/
template <blocks::Term TrialT, blocks::Term TestT>
struct divergence final : form {
static constexpr std::string_view name = meta::template_name<divergence>();
static constexpr std::string_view name = meta::template_name<divergence>(); ///< Compile time reflected name of the form
using trial_type = TrialT;
using test_type = TestT;
using trial_type = TrialT; ///< The trial term type for the divergence form.
using test_type = TestT; ///< The test term type for the divergence form.
using operand_types = utils::types::TypeList<Operand<TrialT, Operation::Divergence>, Operand<TestT>>;
using operand_types = utils::types::TypeList<Operand<TrialT, Operation::Divergence>, Operand<TestT>>; ///< Type list of operand types for the divergence form, used for compile-time reflection and iteration over the operands.
};
/**
* @brief The source form represents the source term in the discretization framework. It takes a trial term and a test term as template parameters and defines the operand types for the source operation.
* @tparam TrialT The trial term type.
* @tparam TestT The test term type.
*
* There is a similar story here to the mass matrix, though this is much closer to what we expect in physics for a source term.
*/
template <blocks::Term TrialT, blocks::Term TestT>
struct source final : form {
static constexpr std::string_view name = meta::template_name<source>();
static constexpr std::string_view name = meta::template_name<source>(); ///< Compile time reflected name of the form
using trial_type = TrialT;
using test_type = TestT;
using trial_type = TrialT; ///< The trial term type for the source form.
using test_type = TestT; ///< The test term type for the source form.
using operand_types = utils::types::TypeList<Operand<TrialT>, Operand<TestT>>;
using operand_types = utils::types::TypeList<Operand<TrialT>, Operand<TestT>>; ///< Type list of operand types for the source form, used for compile-time reflection and iteration over the operands.
};
/**
* @brief The force form represents the force term in the discretization framework. It takes a trial term and a test term as template parameters and defines the operand types for the force operation.
* @tparam TrialT The trial term type.
* @tparam TestT The test term type.
*/
template <blocks::Term TrialT, blocks::Term TestT>
struct force final : form {
static constexpr std::string_view name = meta::template_name<force>();
static constexpr std::string_view name = meta::template_name<force>(); ///< Compile time reflected name of the form
using trial_type = TrialT;
using test_type = TestT;
using trial_type = TrialT; ///< The trial term type for the force form.
using test_type = TestT; ///< The test term type for the force form.
using operand_types = utils::types::TypeList<Operand<TrialT>, Operand<TestT>>;
using operand_types = utils::types::TypeList<Operand<TrialT>, Operand<TestT>>; ///< Type list of operand types for the force form, used for compile-time reflection and iteration over the operands.
};
/**
* @brief The boundary_flux form represents the boundary flux operation in the discretization framework. It takes a trial term and a test term as template parameters and defines the operand types for the boundary flux operation.
* @tparam TrialT The trial term type.
* @tparam TestT The test term type.
*/
template <blocks::Term TrialT, blocks::Term TestT>
struct boundary_flux final : form {
static constexpr std::string_view name = meta::template_name<boundary_flux>();
static constexpr std::string_view name = meta::template_name<boundary_flux>(); ///< Compile time reflected name of the form
using trial_type = TrialT;
using test_type = TestT;
using trial_type = TrialT; ///< The trial term type for the boundary flux form.
using test_type = TestT; ///< The test term type for the boundary flux form.
using operand_types = utils::types::TypeList<Operand<TrialT, Operation::NormalTrace>, Operand<TestT, Operation::NormalTrace>>;
using operand_types = utils::types::TypeList<Operand<TrialT, Operation::NormalTrace>, Operand<TestT, Operation::NormalTrace>>; ///< Type list of operand types for the boundary flux form, used for compile-time reflection and iteration over the operands.
};
/**
* @brief The projection form represents the projection operation in the discretization framework. It takes a trial term and a test term as template parameters and defines the operand types for the projection operation.
* @tparam TrialT The trial term type.
* @tparam TestT The test term type.
*/
template <blocks::Term TrialT, blocks::Term TestT>
struct projection final : form {
static constexpr std::string_view name = meta::template_name<projection>();
static constexpr std::string_view name = meta::template_name<projection>(); ///< Compile time reflected name of the form
using trial_type = TrialT;
using test_type = TestT;
using trial_type = TrialT; ///< The trial term type for the projection form.
using test_type = TestT; ///< The test term type for the projection form.
using operand_types = utils::types::TypeList<Operand<TrialT>, Operand<TestT>>;
using operand_types = utils::types::TypeList<Operand<TrialT>, Operand<TestT>>; ///< Type list of operand types for the projection form, used for compile-time reflection and iteration over the operands.
};
}

View File

@@ -1,19 +1,42 @@
#pragma once
/**
* @file operands.hpp
* @author Emily M. Boudreaux
* @brief This file defines the Operand type, which represents a term and an operation to be applied to that term in the context of a form.
*
* An Operand is a compile-time type that encapsulates a term and an operation to be applied to that term. The operation can be one of several predefined operations, such as value, gradient, divergence, curl, or normal trace. The Operand type is used in the definition of forms to specify the terms and operations involved in the form.
*/
#include <concepts>
#include <type_traits>
#include "serif/discretization/blocks/concepts.hpp"
namespace serif::discretization::forms {
/**
* @brief The Operation struct defines a set of operations that can be applied to terms in the context of a form. These operations include value, gradient, divergence, curl, and normal trace.
* Similar to the statement I made at the top of @ref forms.hpp, if you are a developer looking to extend the mathematical capabilities of SERiF this is a place you should be looking
* early on.
*
* @note NormalTrace I find also to be somewhat confusing. Like many parts of the vocab at this level of SERiF this originates from finite element analysis (though I
* am sure they got it from somewhere else), this just means the normal fluxes along a boundary.
*
* @note If you extend this struct you should also extend @ref IsOperation (just below it in the file)
*/
struct Operation {
struct Value {};
struct Gradient {};
struct Divergence {};
struct Curl {};
struct NormalTrace {};
struct Value {}; ///< Operation tag for evaluating a value
struct Gradient {}; ///< Operation tag for evaluating a gradient
struct Divergence {}; ///< Operation tag for evaluating a divergence
struct Curl {}; ///< Operation tag for evaluating a curl
struct NormalTrace {}; ///< Operation tag for evaluating the normal flux along a boundary
};
/**
* @breif Concept to validate that sone type is a valid operation, if you extend @ref Operation you should extend this concept.
*
* @todo Could C++26 reflection make keeping this and Operation parallel easier / more automatic since this code would live in the same translation unit?
*/
template <typename OperationCandidate>
concept IsOperation =
std::same_as<std::remove_cvref_t<OperationCandidate>, Operation::Value> ||
@@ -22,12 +45,29 @@ namespace serif::discretization::forms {
std::same_as<std::remove_cvref_t<OperationCandidate>, Operation::Curl> ||
std::same_as<std::remove_cvref_t<OperationCandidate>, Operation::NormalTrace>;
/**
* @brief Compile time representation of some operand. An operand associates a term to an operation type (e.g. the gradient of gravitational potential where
* gravitational potential is the term and gradient is the operation).
* @tparam TermT Term Type (e.g. gravitational potential, mass density)
* @tparam OperationT The operation to associate to that term, must be one of the operations from @ref Operation (enforced by satisfying @ref IsOperation).
*
* @note An operand does not know how to do anything. Like a lot of the other parts of SERiF at this level this is compile time metadata which allows
* the compiler to select the correct overloads, without runtime overhead, in order to run the correct computations. Do not be fooled into thinking
* that this actually has any computations burried within it.
*/
template <blocks::Term TermT, IsOperation OperationT = Operation::Value> // We choose to default to the value operation because it is the most common.
struct Operand final {
using term_type = TermT;
using operation_type = OperationT;
};
/**
* @brief Concept to validate a valid operand. An operand is valid if the following conditions are satisfied
* - it has a member term_type
* - it has a member operation_type
* - term_type satisfies the @ref Term concept (e.g. is a valid term)
* - operation_type satisfies the @ref IsOperation concept (e..g is a valid operation)
*/
template <typename OperandCandidate>
concept IsOperand = requires { // Recall that requires expressions are not evaluated. What this checks is that it is valid syntax given the type to requires the term type and operation type. It does not check that the term type is a valid term or that the operation type is a valid operation.
typename std::remove_cvref_t<OperandCandidate>::term_type;

View File

@@ -1,5 +1,12 @@
#pragma once
/**
* @file runtime.hpp
* @author Emily M. Boudreaux
*
* @brief file defining some utilities to project form information from compile time into runtime
*/
#include <string_view>
#include <format>
@@ -7,24 +14,40 @@
#include "serif/discretization/forms/concept.hpp"
namespace serif::discretization::forms {
/**
* @struct FormDescriptor
* @brief Runtime form descriptor including runtime term information. Used to represented the compile-time form type at runtime.
*/
struct FormDescriptor final {
std::string_view name;
std::string_view name; ///< Name of the form usable at runtime
blocks::TermDescriptor trial;
blocks::TermDescriptor test;
blocks::TermDescriptor trial; ///< Term descriptor for the trial term
blocks::TermDescriptor test; ///< Term descriptor for the test term
/**
* @brief The standard struct equality operator. Two form descriptors are considered equal if and only if all members are considered equal. This means that
* the names are the same, the trial terms are the same, and the test terms are the same.
* @return
*/
[[nodiscard]] friend constexpr bool operator==(const FormDescriptor&, const FormDescriptor&) noexcept = default;
};
/**
* @brief Specialization of the FormDescriptor class at compile time for some particular compile-time form. This allows us to generate
* constexpr instances of this struct at compile time and usable at runtime.
* @tparam FormT The form to generate a descriptor for
*/
template <Form FormT>
inline constexpr FormDescriptor form_descriptor{
.name = FormT::name,
.trial = blocks::term_descriptor<typename FormT::trial_type>,
.test = blocks::term_descriptor<typename FormT::test_type>
};
}
/**
* @brief Overload to allow println for FormDescriptors.
*/
template <>
struct std::formatter<serif::discretization::forms::FormDescriptor> : std::formatter<std::string> {
template <typename FormatContext>

View File

@@ -0,0 +1,178 @@
#pragma once
/**
* @file value.hpp
* @author Emily M. Boudreaux
*
* @brief Encode compile time information about the shape (rank) and data type of the result of various Operations
*
* @todo This should be consolidated and removed. This information could be injected into the Operation itself.
*/
#include <array>
#include <type_traits>
#include "serif/discretization/blocks/traits.hpp"
#include "serif/discretization/forms/operands.hpp"
namespace serif::discretization::forms {
/**
* @brief Alias for a rank 1 tensor of dimension 3 with elements doubles
*
* @todo This file was a mistake, the information it is is helpful yes, however, it has lost generality, lost good type information, and does not need to be separate
* Really we should refactor this to be part of Operation, remove the assumption of 3 dimensionality, and probably fall back to something like a span rather than an array.
*/
using VectorValue = std::array<double, 3>;
/**
* @brief Alias for a rank 2 tensor of dimension 3 with cells as doubles
*/
using MatrixValue = std::array<VectorValue, 3>;
/**
* @brief General case for compile time metadata associating a tensor rank to an operation. The specialization of this will also specific what the return value
* of an operation of that rank will be.
* @tparam Rank The rank of the input
* @tparam OperationT the operation to conduct on that input
*/
template <std::size_t Rank, typename OperationT>
struct OperationValue;
/**
* @brief The value of a rank 0 tensor is a scalar, a double in the case of SERiF where we restrict ourselves to double precision.
*/
template <>
struct OperationValue<0, Operation::Value> {
using type = double;
};
/**
* @brief The value of a rank 1 tensor is a rank 1 tensor (a vector of doubles in SERiF)
*/
template <>
struct OperationValue<1, Operation::Value> {
using type = VectorValue;
};
/**
* @brief The divergence of a rank one tensor is a scalar (a double)
*/
template <>
struct OperationValue<1, Operation::Divergence> {
using type = double;
};
/**
* @brief The gradient of a rank 0 tensor is a rank 1 tensor (a vector of doubles)
*/
template <>
struct OperationValue<0, Operation::Gradient> {
using type = VectorValue;
};
/**
* @brief The curl of a rank 1 tensor is a rank 1 tensor (a vector of doubles)
*/
template <>
struct OperationValue<1, Operation::Curl> {
using type = VectorValue;
};
/**
* The normal trace (flux normal to a boundary surface) of a rank one tensor is a scalar (a double)
*/
template <>
struct OperationValue<1, Operation::NormalTrace> {
using type = double;
};
/**
* @brief General case for compile time metadata associating some operand to the value type of its result. These use the OperationValue struct and the information in the operand to determine the value type of the result of that operand.
* @tparam OperandT Operand Type
*/
template <typename OperandT>
struct OperandResult;
/**
* @brief Specialization of the OperandResult struct for operands that are valid operands (satisfy the IsOperand concept) and are spatial terms (e.g defined over the domain, think mass density). This specialization uses the OperationValue struct to determine the value type of the result of the operand based on its term type and operation type.
* @tparam OperandT The operand type to determine the result value type for.
*
* Think of this a compile time way to say "this operand (which recall is an association between a form and a term) will result in a tensor of rank x when evaluated on a term of rank y".
*/
template <IsOperand OperandT>
requires blocks::SpatialTerm<typename OperandT::term_type>
struct OperandResult<OperandT> : OperationValue<blocks::SpatialTermTraits<typename OperandT::term_type>::rank, typename OperandT::operation_type> {};
/**
* @brief Specialization of the OperandResult struct for operands that are valid operands (satisfy the IsOperand concept) and are global scalar terms (e.g one value over the domain, think total mass). This specialization defines that any operand that is a global scalar term will result in a double when evaluated.
* @tparam OperandT The operand type to determine the result value type for.
*
*
*/
template <IsOperand OperandT>
requires blocks::GlobalScalarTerm<typename OperandT::term_type> && std::same_as<typename OperandT::operation_type, Operation::Value>
struct OperandResult<OperandT> {
using type = double;
};
/**
* @brief Alias for the value type of an operand. This is a convenience alias that allows you to easily access the value type of an operand without having to explicitly use the OperandResult struct.
* @tparam OperandT The operand type to determine the value type for.
*/
template <IsOperand OperandT>
using OperandValue = OperandResult<OperandT>::type;
/**
* @brief Helper variable template to determine the number of components in the value type of an operand. This is a convenience variable that allows you to easily access the number of components in the value type of an operand without having to explicitly use the OperandResult struct.
* @tparam OperandT The operand type to determine the number of components for.
*
* @todo This introduces an assumption of three-dimensionality, we say "this is either a scalar or a tensor of dimension 3". We should probably relax this assumption.
*/
template <typename ValueT>
inline constexpr int component_count = std::same_as<ValueT, double> ? 1 : 3;
/**
* @brief Alias for the partial derivative of some output type with respect to some input type. This is a convenience alias that allows you to easily access the partial derivative type without having to explicitly use the OperandResult struct.
* @tparam OutputT The output type of the partial derivative.
* @tparam InputT The input type of the partial derivative.
*
* @todo Should probably be consolidated with the other PartialDerivative system... No, should *definitely* be consolidated...
*/
template <typename OutputT, typename InputT>
using PartialDerivative = std::conditional_t<std::same_as<OutputT, double>, InputT, std::conditional_t<std::same_as<InputT, double>, OutputT, MatrixValue>>;
/**
* @brief Read one entry of a scalar/vector/matrix value. This is a convenience function that allows you to easily access the components of a value type without having to explicitly use the OperandResult struct.
* @tparam ValueT The value type to read from.
* @param value The value to read from.
* @param index The index of the component to read.
* @return The component of the value at the specified index.
*/
template <typename ValueT>
decltype(auto) component(ValueT& value, int index) {
if constexpr (std::same_as<std::remove_cv_t<ValueT>, double>) {
return (value);
} else {
return (value[index]);
}
}
/**
* @brief Read one entry of a scalar/vector/matrix local derivative.
* @tparam OutputT The output type of the partial derivative.
* @tparam InputT The input type of the partial derivative.
* @param value The partial derivative to read from.
* @param output The index of the output component to read.
* @param input The index of the input component to read.
* @return The component of the partial derivative at the specified indices.
*/
template <typename OutputT, typename InputT>
double derivative_component(const PartialDerivative<OutputT, InputT>& value, int output, int input) {
if constexpr (std::same_as<OutputT, double>) {
return component(value, input);
} else if constexpr (std::same_as<InputT, double>) {
return value[output];
} else {
return value[output][input];
}
}
} // namespace serif::discretization::forms