// Copyright (c) 2010-2023, Lawrence Livermore National Security, LLC. Produced // at the Lawrence Livermore National Laboratory. All Rights reserved. See files // LICENSE and NOTICE for details. LLNL-CODE-806117. // // This file is part of the MFEM library. For more information and source code // availability visit https://mfem.org. // // MFEM is free software; you can redistribute it and/or modify it under the // terms of the BSD-3 license. We welcome feedback and contributions, see file // CONTRIBUTING.md for details. /** * @file dual.hpp * * @brief This file contains the declaration of a dual number class */ #ifndef MFEM_INTERNAL_DUAL_HPP #define MFEM_INTERNAL_DUAL_HPP #include // for is_arithmetic #include #include "../general/backends.hpp" namespace mfem { namespace internal { /** * @brief Dual number struct (value plus gradient) * @tparam gradient_type The type of the gradient (should support addition, * scalar multiplication/division, and unary negation operators) */ template struct dual { /// the actual numerical value value_type value; /// the partial derivatives of value w.r.t. some other quantity gradient_type gradient; /** @brief assignment of a double to a value of a dual. Promotes a double to * a dual with a zero gradient value. */ auto operator=(double a) -> dual& { value = a; gradient = {}; return *this; } }; /** @brief class for checking if a type is a dual number or not */ template struct is_dual_number { /// whether or not type T is a dual number static constexpr bool value = false; }; /** @brief class for checking if a type is a dual number or not */ template struct is_dual_number > { static constexpr bool value = true; ///< whether or not type T is a dual number }; /** @brief addition of a dual number and a non-dual number */ template ::value || is_dual_number::value>::type> MFEM_HOST_DEVICE constexpr auto operator+(dual a, other_type b) -> dual { return {a.value + b, a.gradient}; } // C++17 version of the above // // template // constexpr auto operator+(dual a, value_type b) // { // return dual{a.value + b, a.gradient}; // } /** @brief addition of a dual number and a non-dual number */ template ::value || is_dual_number::value>::type> MFEM_HOST_DEVICE constexpr auto operator+(other_type a, dual b) -> dual { return {a + b.value, b.gradient}; } /** @brief addition of two dual numbers */ template MFEM_HOST_DEVICE constexpr auto operator+(dual a, dual b) -> dual { return {a.value + b.value, a.gradient + b.gradient}; } /** @brief unary negation of a dual number */ template MFEM_HOST_DEVICE constexpr auto operator-(dual x) -> dual { return {-x.value, -x.gradient}; } /** @brief subtraction of a non-dual number from a dual number */ template MFEM_HOST_DEVICE constexpr auto operator-(dual a, double b) -> dual { return {a.value - b, a.gradient}; } /** @brief subtraction of a dual number from a non-dual number */ template MFEM_HOST_DEVICE constexpr auto operator-(double a, dual b) -> dual { return {a - b.value, -b.gradient}; } /** @brief subtraction of two dual numbers */ template MFEM_HOST_DEVICE constexpr auto operator-(dual a, dual b) -> dual { return {a.value - b.value, a.gradient - b.gradient}; } /** @brief multiplication of a dual number and a non-dual number */ template MFEM_HOST_DEVICE constexpr auto operator*(const dual& a, double b) -> dual { return {a.value * b, a.gradient * b}; } /** @brief multiplication of a dual number and a non-dual number */ template MFEM_HOST_DEVICE constexpr auto operator*(double a, const dual& b) -> dual { return {a * b.value, a * b.gradient}; } /** @brief multiplication of two dual numbers */ template MFEM_HOST_DEVICE constexpr auto operator*(dual a, dual b) -> dual { return {a.value * b.value, b.value * a.gradient + a.value * b.gradient}; } /** @brief division of a dual number by a non-dual number */ template MFEM_HOST_DEVICE constexpr auto operator/(const dual& a, double b) -> dual { return {a.value / b, a.gradient / b}; } /** @brief division of a non-dual number by a dual number */ template MFEM_HOST_DEVICE constexpr auto operator/(double a, const dual& b) -> dual { return {a / b.value, -(a / (b.value * b.value)) * b.gradient}; } /** @brief division of two dual numbers */ template MFEM_HOST_DEVICE constexpr auto operator/(dual a, dual b) -> dual { return {a.value / b.value, (a.gradient / b.value) - (a.value * b.gradient) / (b.value * b.value)}; } /** * @brief Generates const + non-const overloads for a binary comparison operator * Comparisons are conducted against the "value" part of the dual number * @param[in] x The comparison operator to overload */ #define mfem_binary_comparator_overload(x) \ template \ MFEM_HOST_DEVICE constexpr bool operator x( \ const dual& a, \ double b) \ { \ return a.value x b; \ } \ \ template \ MFEM_HOST_DEVICE constexpr bool operator x( \ double a, \ const dual& b) \ { \ return a x b.value; \ } \ \ template MFEM_HOST_DEVICE \ constexpr bool operator x( \ const dual& a, \ const dual& b) \ { \ return a.value x b.value; \ } mfem_binary_comparator_overload(<) ///< implement operator< for dual numbers mfem_binary_comparator_overload(<=) ///< implement operator<= for dual numbers mfem_binary_comparator_overload(==) ///< implement operator== for dual numbers mfem_binary_comparator_overload(>=) ///< implement operator>= for dual numbers mfem_binary_comparator_overload(>) ///< implement operator> for dual numbers #undef mfem_binary_comparator_overload /** @brief compound assignment (+) for dual numbers */ template MFEM_HOST_DEVICE dual& operator+=(dual& a, const dual& b) { a.value += b.value; a.gradient += b.gradient; return a; } /** @brief compound assignment (-) for dual numbers */ template MFEM_HOST_DEVICE dual& operator-=(dual& a, const dual& b) { a.value -= b.value; a.gradient -= b.gradient; return a; } /** @brief compound assignment (+) for dual numbers with `double` righthand side */ template MFEM_HOST_DEVICE dual& operator+=(dual& a, double b) { a.value += b; return a; } /** @brief compound assignment (-) for dual numbers with `double` righthand side */ template MFEM_HOST_DEVICE dual& operator-=(dual& a, double b) { a.value -= b; return a; } /** @brief implementation of absolute value function for dual numbers */ template MFEM_HOST_DEVICE dual abs(dual x) { return (x.value >= 0) ? x : -x; } /** @brief implementation of square root for dual numbers */ template MFEM_HOST_DEVICE dual sqrt(dual x) { using std::sqrt; return {sqrt(x.value), x.gradient / (2.0 * sqrt(x.value))}; } /** @brief implementation of cosine for dual numbers */ template MFEM_HOST_DEVICE dual cos(dual a) { using std::cos; using std::sin; return {cos(a.value), -a.gradient * sin(a.value)}; } /** @brief implementation of sine for dual numbers */ template MFEM_HOST_DEVICE dual sin(dual a) { using std::sin; using std::cos; return {sin(a.value), a.gradient * cos(a.value)}; } /** @brief implementation of sinh for dual numbers */ template MFEM_HOST_DEVICE dual sinh(dual a) { using std::sinh; using std::cosh; return {sinh(a.value), a.gradient * cosh(a.value)}; } /** @brief implementation of acos for dual numbers */ template MFEM_HOST_DEVICE dual acos(dual a) { using std::sqrt; using std::acos; return {acos(a.value), -a.gradient / sqrt(value_type{1} - a.value * a.value)}; } /** @brief implementation of asin for dual numbers */ template MFEM_HOST_DEVICE dual asin(dual a) { using std::sqrt; using std::asin; return {asin(a.value), a.gradient / sqrt(value_type{1} - a.value * a.value)}; } /** @brief implementation of tan for dual numbers */ template MFEM_HOST_DEVICE dual tan(dual a) { using std::tan; value_type f = tan(a.value); return {f, a.gradient * (value_type{1} + f * f)}; } /** @brief implementation of atan for dual numbers */ template MFEM_HOST_DEVICE dual atan(dual a) { using std::atan; return {atan(a.value), a.gradient / (value_type{1} + a.value * a.value)}; } /** @brief implementation of exponential function for dual numbers */ template MFEM_HOST_DEVICE dual exp(dual a) { using std::exp; return {exp(a.value), exp(a.value) * a.gradient}; } /** @brief implementation of the natural logarithm function for dual numbers */ template MFEM_HOST_DEVICE dual log(dual a) { using std::log; return {log(a.value), a.gradient / a.value}; } /** @brief implementation of `a` (dual) raised to the `b` (dual) power */ template MFEM_HOST_DEVICE dual pow(dual a, dual b) { using std::log; using std::pow; value_type value = pow(a.value, b.value); return {value, value * (a.gradient * (b.value / a.value) + b.gradient * log(a.value))}; } /** @brief implementation of `a` (non-dual) raised to the `b` (dual) power */ template MFEM_HOST_DEVICE dual pow(double a, dual b) { using std::pow; using std::log; value_type value = pow(a, b.value); return {value, value * b.gradient * log(a)}; } /** @brief implementation of `a` (non-dual) raised to the `b` (non-dual) power */ template MFEM_HOST_DEVICE value_type pow(value_type a, value_type b) { using std::pow; return pow(a, b); } /** @brief implementation of `a` (dual) raised to the `b` (non-dual) power */ template MFEM_HOST_DEVICE dual pow(dual a, double b) { using std::pow; value_type value = pow(a.value, b); return {value, value * a.gradient * b / a.value}; } /** @brief overload of operator<< for `dual` to work with work with standard output streams */ template std::ostream& operator<<(std::ostream& os, dual A) { os << '(' << A.value << ' ' << A.gradient << ')'; return os; } /** @brief promote a value to a dual number of the appropriate type */ MFEM_HOST_DEVICE constexpr dual make_dual(double x) { return {x, 1.0}; } /** @brief return the "value" part from a given type. For non-dual types, this is just the identity function */ template MFEM_HOST_DEVICE T get_value(const T& arg) { return arg; } /** @brief return the "value" part from a dual number type */ template MFEM_HOST_DEVICE gradient_type get_value(dual arg) { return arg.value; } /** @brief return the "gradient" part from a dual number type */ template MFEM_HOST_DEVICE gradient_type get_gradient(dual arg) { return arg.gradient; } } // namespace internal } // namespace mfem #endif