cppduals  0.5.4
Dual numbers for C++
cppduals

duals/dual is a single-header Dual number C++ template library that implements numbers of the type \((a + b \cdot \epsilon)\) where \( \epsilon \ne 0\), and \(\epsilon^2 = 0\).

duals::dual<> can be used for "automatic" differentiation, it can also recursively nest with itself for higher orders of differentiation (ie for the second derivative use duals::dual<duals::dual<T>>). It can also be used to differentiate parts of complex functions as std::complex<duals::dual<T>>. This file can be used stand-alone for dual number support, but for Eigen vectorization support rather the file duals/dual_eigen should be included.

#include <duals/dual>
using namespace duals::literals;
template <class T> T f(T x) { return pow(x,pow(x,x)); }
template <class T> T df(T x) { return pow(x,-1. + x + pow(x,x)) * (1. + x*log(x) + x*pow(log(x),2.)); }
template <class T> T ddf(T x) { return (pow(x,pow(x,x)) * pow(pow(x,x - 1.) + pow(x,x)*log(x)*(log(x) + 1.), 2.) +
pow(x,pow(x,x)) * (pow(x,x - 1.) * log(x) +
pow(x,x - 1.) * (log(x) + 1.) +
pow(x,x - 1.) * ((x - 1.)/x + log(x)) +
pow(x,x) * log(x) * pow(log(x) + 1., 2.) )); }
int main()
{
std::cout << " f(2.) = " << f(2.) << "\n";
std::cout << " df(2.) = " << df(2.) << "\n";
std::cout << "ddf(2.) = " << ddf(2.) << "\n";
std::cout << " f(2+1_e) = " << f(2+1_e) << "\n";
std::cout << " f(2+1_e).dpart() = " << f(2+1_e).dpart() << "\n";
duals::hyperduald x(2+1_e,1+0_e);
std::cout << " f((2+1_e) + (1+0_e)_e).dpart().dpart() = " << f(x).dpart().dpart() << "\n";
}

Produces (notice the derivative in the dual-part):

f(2.) = 16
df(2.) = 107.11
ddf(2.) = 958.755
f(2+1_e) = (16+107.11_e)
f(2+1_e).dpart() = 107.11
f((2+1_e) + (1+0_e)_e).dpart().dpart() = 958.755

How this works can be seen by inspecting the infinite Taylor series expansion of a function \( f(x) \) at \( a \) with \( x = a + b\epsilon \). The series truncates itself due to the property \( \epsilon^2 = 0 \), leaving the function's derivative at \( a \) in the "dual part" of the result (when \( b = 1 \)):

\[ \begin{split} f(a + b \epsilon) &= f(a) + f'(a)(b \epsilon) + \frac{f''(a)}{2!}(b \epsilon)^2 + \frac{f'''(a)}{3!}(b \epsilon)^3 + \ldots \\ \ &= f(a) + f'(a)(b \epsilon) \end{split} \]

The class is contained in a single c++11 header #include <duals/dual>, for extended Eigen support, instead include the header #include <duals/dual_eigen>.

Type X in the templates below can be any value which can be assigned to value_type.

Type X also indicates a limitation to dual numbers of the same depth but (possibly) different value_type as duals::dual<T>. For example, you can assign (or add/sub/mul/div) duals::dual<float> and duals::dual<double>, but you can not assign duals::dual<duals::dual<float>> to duals::dual<float>.

Here is a synopsis of the class:

namespace duals {
template<class T> class dual {
typedef T value_type;
dual(const & re = T(), const & du = T());
dual(const dual &);
template<class X> dual(const dual<X> &);
T rpart() const;
T dpart() const;
void rpart(T);
void dpart(T);
dual<T> operator-() const;
dual<T> operator+() const;
dual<T> & operator= (const T &);
dual<T> & operator+=(const T &);
dual<T> & operator-=(const T &);
dual<T> & operator*=(const T &);
dual<T> & operator/=(const T &);
dual<T> & operator=(const dual<T> &);
template<class X> dual<T> & operator= (const dual<X> &);
template<class X> dual<T> & operator+=(const dual<X> &);
template<class X> dual<T> & operator-=(const dual<X> &);
template<class X> dual<T> & operator*=(const dual<X> &);
template<class X> dual<T> & operator/=(const dual<X> &);
// The comparison operators are not strictly well-defined,
// they are implemented as comparison of the real part.
bool operator ==(const X &b) const;
bool operator !=(const X &b) const;
bool operator <(const X &b) const;
bool operator >(const X &b) const;
bool operator <=(const X &b) const;
bool operator >=(const X &b) const;
bool operator <(const dual<X> &b) const;
bool operator >(const dual<X> &b) const;
bool operator <=(const dual<X> &b) const;
bool operator >=(const dual<X> &b) const;
};
// Non-member functions:
T rpart(dual<T>) // Real part
T dpart(dual<T>) // Dual part
dual<T> dconj(dual<T>) // Dual-conjugate
dual<T> random(dual<T> low = {0,0}, dual<T> high = {1,0})
dual<T> exp(dual<T>)
dual<T> log(dual<T>)
dual<T> log10(dual<T>)
dual<T> pow(dual<T>, U)
dual<T> pow(U, dual<T>)
dual<T> pow(dual<T>, dual<T>)
dual<T> sqrt(dual<T>)
dual<T> cbrt(dual<T>)
dual<T> sin(dual<T>)
dual<T> cos(dual<T>)
dual<T> tan(dual<T>)
dual<T> asin(dual<T>)
dual<T> acos(dual<T>)
dual<T> atan(dual<T>)
// TODO:
dual<T> sinh(dual<T>)
dual<T> cosh(dual<T>)
dual<T> tanh(dual<T>)
dual<T> asinh(dual<T>)
dual<T> acosh(dual<T>)
dual<T> atanh(dual<T>)
// Non-differentiable operations on the real part.
T frexp(duals::dual<T> arg, int* exp );
duals::dual<T> ldexp(duals::dual<T> arg, int exp );
T trunc(duals::dual<T> d);
T floor(duals::dual<T> d);
T ceil(duals::dual<T> d);
T round(duals::dual<T> d);
int fpclassify(duals::dual<T> d);
bool isfinite(duals::dual<T> d);
bool isnormal(duals::dual<T> d);
bool isinf(duals::dual<T> d);
bool isnan(duals::dual<T> d);
// Stream IO
template<T> operator>>(basic_istream<charT, traits> &, dual<T> &);
template<T> operator<<(basic_ostream<charT, traits> &, const dual<T> &);
}

Some useful typedefs:

typedef dual<float> dualf;
typedef dual<double> duald;
typedef dual<long double> dualld;
typedef dual<dualf> hyperdualf;
typedef dual<duald> hyperduald;
typedef dual<dualld> hyperdualld;
typedef std::complex<dualf> cdualf;
typedef std::complex<duald> cduald;
typedef std::complex<dualld> cdualld;

There are also literals for dual parts defined in the inline namespace duals::literals. They are available with using namespace duals; or using namespace duals::literals.

using namespace duals::literals;
dualf x = 3 + 4_ef;
duald y = 3 + 4_e;
dualld z = 3 + 4_el;

And in case you dislike iostreams, there are some formatters for the {fmt} formatting library. These are disabled by default, but can be enabled by #define CPPDUALS_LIBFMT for the dual<> formatter, and/or #define CPPDUALS_LIBFMT_COMPLEX for the std::complex<> formatter. There are three custom formatting flags that control how the dual numbers are printed, these must come before the normal {fmt} formatting spec: '$', '*', ','. For me these are about 3x faster than iostreams.

#define CPPDUALS_LIBFMT
#define CPPDUALS_LIBFMT_COMPLEX
#inlcude <duals/dual>
using namespace duals::literals;
...
string s = fmt::format("{:}", 1 + 2_e); // s = "(1.0+2.0_e)"
string s = fmt::format("{:g}", 1 + 2_e); // s = "(1+2_e)"
string s = fmt::format("{:*}", 1 + 2_e); // s = "(1.0+2.0*e)"
string s = fmt::format("{:,}", 1 + 2_e); // s = "(1.0,2.0)"
string s = fmt::format("{:*,g}", complexd(1,2_e)); // s = "((1)+(0,2)*i)"

The "archaic Greek epsilon" logo is from Wikimedia commons

Some casual reading material: