#pragma once /** * @file partial.hpp * @author Emily M. Boudreaux * * @brief Compile time tools to represent a partial derivative of one quantity wrt. another quantity. * Note that this system is not responsible for computing partials or in fact derivatives of any kind. * Rather, it is responsible for forcing the developer --- or compiler at least --- to ask * for a specific, explicit, partial and have to spell out what that is. That in turn gives * the compiler enough information to do a lot of downstream things. A motivating example would be automatic verification * of the provided EOS against all partials that might be needed to ensure they are all exported * by the EOS. */ #include "serif/dimensions/quantities.hpp" #include "serif/utils/misc/concepts/numeric.hpp" namespace serif::dimensions { /** * @struct WithRespectTo * * @tparam Quantity A quantity satisfying @ref ThermodynamicQuantityType * @brief A type that is empty at runtime but allows for the developer * to specify to the compiler what quantity the partial is with respect to. */ template struct WithRespectTo final {}; /** @brief * A second EOS partial retains both differentiation quantities in its type. */ template class SecondPartialDerivative final { public: /** @brief Store an explicitly evaluated second partial; this type does not differentiate. */ explicit constexpr SecondPartialDerivative(double value) noexcept : m_value(value) {} /** @brief Return the numeric partial in the caller's physical units. */ [[nodiscard]] constexpr double value() const noexcept { return m_value; } private: double m_value; }; /** * @class PartialDerivative * * @tparam OutputQuantity * @tparam InputQuantity * * @brief Class to represent a compile time derivative relation between two quantities. Note that * this class does not, in fact cannot, compute any derivatives itself. Rather, this is a way * for the compiler to understand higher level mathematical relations. Unless you are really * in the weeds of the development team you will likely not need to interact with this * directly. Further note that this class does include (unlike the more basic dimension system) * some basic dimensional analysis. Specifically, there are some overloaded operators that * allow for certain specializations of PartialDerivative to operate on one and other * conditioned on what their input and output types are. This does not do any thing like * convert area to length^2. However, it will do more basic things like ensure that you * can only add two partials which agree on their dimensions exactly. Or that while you can * multiple a partial by any scalar, you cannot multiply arbitrary partials. */ template class PartialDerivative final { public: explicit constexpr PartialDerivative(const double value) noexcept : m_value(value){} /** * @brief Get the underlying value as a compile time constant if possible. This * allows the compiler, where the constructor may be determined at compile time, * to fully optimize PartialDerivative operations as if they were doubles. * * @note calling value gets the underlying numeric value directly thereby bypassing * the limitations on what a valid operation are (e.g. what the operator overloads * enforce). * * @return m_value: This is whatever was passed at constructor time since there is * no way to modify this value after construction without some truly horrific C++ tricks */ [[nodiscard]] constexpr double value() const noexcept { return m_value; } /** * @brief Overload the + operator between two partials which share the exact same input * and output types * @param lhs One partial * @param rhs Other Partial * @return A new partial with the same input and output types where m_value = lhs.m_value + rhs.m_value */ friend constexpr PartialDerivative operator+( const PartialDerivative& lhs, const PartialDerivative& rhs ) noexcept { return PartialDerivative{lhs.m_value + rhs.m_value}; } /** * @brief Overload the - operator between two partials which share the exact same input * and output types * @param lhs One partial * @param rhs Other Partial * @return A new partial with the same input and output types where m_value = lhs.m_value - rhs.m_value */ friend constexpr PartialDerivative operator-( const PartialDerivative& lhs, const PartialDerivative& rhs ) noexcept { return PartialDerivative{lhs.m_value - rhs.m_value}; } /** * @brief Overload of the * operator for a left partial and a right scalar * and output types * @tparam Scalar Any value satisfying the @ref IsNumeric concept (integral types and float types) * @param lhs Partial with some input and some output type * @param rhs Some numeric quantity * @return A new partial with the same input and output types where m_value = lhs.m_value * rhs */ template friend constexpr PartialDerivative operator*( const PartialDerivative& lhs, const Scalar& rhs ) noexcept { return PartialDerivative{lhs.m_value * rhs}; } /** * @brief Overload of the * operator for a right partial and a left scalar * and output types * @tparam Scalar Any value satisfying the @ref IsNumeric concept (integral types and float types) * @param lhs Some numeric quantity * @param rhs Partial with some input and some output type * @return A new partial with the same input and output types where m_value = lhs * rhs.m_value */ template friend constexpr PartialDerivative operator*( const Scalar& lhs, const PartialDerivative& rhs ) noexcept { return PartialDerivative{lhs * rhs.m_value}; } /** * @brief Overload of the / operator for a left partial and a right scalar * and output types * @tparam Scalar Any value satisfying the @ref IsNumeric concept (integral types and float types) * @param lhs Partial with some input and some output type * @param rhs Some numeric quantity * @return A new partial with the same input and output types where m_value = lhs.m_value / rhs */ template friend constexpr PartialDerivative operator/( const PartialDerivative& lhs, const Scalar& rhs ) noexcept { return PartialDerivative{lhs.m_value / rhs}; } /** * @brief Partial ordering overload to compare a left partial and a right scalar * @tparam Scalar Any value satisfying the @ref IsNumeric concept (integral types and float types) * @param lhs Some numeric quantity * @param rhs Some partial with an input and output type * @return partial ordering comparing rhs.m_value to lhs */ template friend constexpr std::partial_ordering operator<=>( const PartialDerivative& lhs, Scalar rhs ) noexcept { return lhs.m_value <=> rhs; } /** * @brief Partial ordering overload to compare a right partial and a left scalar * @tparam Scalar Any value satisfying the @ref IsNumeric concept (integral types and float types) * @param lhs Some numeric quantity * @param rhs Some partial with an input and output type * @return partial ordering comparing rhs.m_value to lhs */ template friend constexpr std::partial_ordering operator<=>( Scalar lhs, const PartialDerivative& rhs ) noexcept { return lhs <=> rhs.m_value; } /** * @brief Partial ordering comparing two partials with exactly the same input and output types * @param lhs Some Partial * @param rhs Other Partial * @return Partial ordering comparing lhs.m_value to rhs.m_value */ friend constexpr std::partial_ordering operator<=>( const PartialDerivative &lhs, const PartialDerivative &rhs ) noexcept { return lhs.m_value <=> rhs.m_value; } private: double m_value; ///< Numeric value associated with the partial. If this is known at compile time then the compiler can optimize operations with partial.value() to be equivalent to compile time double operations. }; }