File: autodiff.qbk

package info (click to toggle)
scipy 1.16.0-1exp7
  • links: PTS, VCS
  • area: main
  • in suites: experimental
  • size: 234,820 kB
  • sloc: cpp: 503,145; python: 344,611; ansic: 195,638; javascript: 89,566; fortran: 56,210; cs: 3,081; f90: 1,150; sh: 848; makefile: 785; pascal: 284; csh: 135; lisp: 134; xml: 56; perl: 51
file content (313 lines) | stat: -rw-r--r-- 15,082 bytes parent folder | download | duplicates (11)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
[/          Copyright Matthew Pulver 2018 - 2019.
// Distributed under the Boost Software License, Version 1.0.
//    (See accompanying file LICENSE_1_0.txt or copy at
//          https://www.boost.org/LICENSE_1_0.txt)]

[section:autodiff Automatic Differentiation]
[template autodiff_equation[name]  '''<inlinemediaobject><imageobject><imagedata fileref="../equations/autodiff/'''[name]'''"></imagedata></imageobject></inlinemediaobject>''']

[h1:synopsis Synopsis]

    #include <boost/math/differentiation/autodiff.hpp>
    
    namespace boost {
    namespace math {
    namespace differentiation {
    
    // Function returning a single variable of differentiation. Recommended: Use auto for type.
    template <typename RealType, size_t Order, size_t... Orders>
    autodiff_fvar<RealType, Order, Orders...> make_fvar(RealType const& ca);
    
    // Function returning multiple independent variables of differentiation in a std::tuple.
    template<typename RealType, size_t... Orders, typename... RealTypes>
    auto make_ftuple(RealTypes const&... ca);
    
    // Type of combined autodiff types. Recommended: Use auto for return type (C++14).
    template <typename RealType, typename... RealTypes>
    using promote = typename detail::promote_args_n<RealType, RealTypes...>::type;
    
    namespace detail {
    
    // Single autodiff variable. Use make_fvar() or make_ftuple() to instantiate.
    template <typename RealType, size_t Order>
    class fvar {
     public:
      // Query return value of function to get the derivatives.
      template <typename... Orders>
      get_type_at<RealType, sizeof...(Orders) - 1> derivative(Orders... orders) const;
    
      // All of the arithmetic and comparison operators are overloaded.
      template <typename RealType2, size_t Order2>
      fvar& operator+=(fvar<RealType2, Order2> const&);
    
      fvar& operator+=(root_type const&);
    
      // ...
    };
    
    // Standard math functions are overloaded and called via argument-dependent lookup (ADL).
    template <typename RealType, size_t Order>
    fvar<RealType, Order> floor(fvar<RealType, Order> const&);
    
    template <typename RealType, size_t Order>
    fvar<RealType, Order> exp(fvar<RealType, Order> const&);
    
    // ...
    
    }  // namespace detail
    
    }  // namespace differentiation
    }  // namespace math
    }  // namespace boost

[h1:description Description]

Autodiff is a header-only C++ library that facilitates the [@https://en.wikipedia.org/wiki/Automatic_differentiation
automatic differentiation] (forward mode) of mathematical functions of single and multiple variables.

This implementation is based upon the [@https://en.wikipedia.org/wiki/Taylor_series Taylor series] expansion of
an analytic function /f/ at the point ['x[sub 0]]:

[/ Thanks to http://www.tlhiv.org/ltxpreview/ for LaTeX-to-SVG conversions. ]
[/ \Large\begin{align*}
f(x_0+\varepsilon) &= f(x_0) + f'(x_0)\varepsilon + \frac{f''(x_0)}{2!}\varepsilon^2 + \frac{f'''(x_0)}{3!}\varepsilon^3 + \cdots \\
  &= \sum_{n=0}^N\frac{f^{(n)}(x_0)}{n!}\varepsilon^n + O\left(\varepsilon^{N+1}\right).
\end{align*} ]
[:[:[autodiff_equation taylor_series.svg]]]

The essential idea of autodiff is the substitution of numbers with polynomials in the evaluation of ['f(x[sub 0])].
By substituting the number ['x[sub 0]] with the first-order polynomial ['x[sub 0]+\u03b5], and using the same
algorithm to compute ['f(x[sub 0]+\u03b5)], the resulting polynomial in ['\u03b5] contains the function's derivatives
['f'(x[sub 0])], ['f''(x[sub 0])], ['f\'\'\'(x[sub 0])], ... within the coefficients. Each coefficient is equal to the
derivative of its respective order, divided by the factorial of the order.

In greater detail, assume one is interested in calculating the first /N/ derivatives of /f/ at ['x[sub 0]]. Without
loss of precision to the calculation of the derivatives, all terms ['O(\u03b5[super N+1])] that include powers
of ['\u03b5] greater than /N/ can be discarded. (This is due to the fact that each term in a polynomial depends
only upon equal and lower-order terms under arithmetic operations.) Under these truncation rules, /f/ provides a
polynomial-to-polynomial transformation:

[/ \Large$$f \qquad : \qquad x_0+\varepsilon \qquad \mapsto \qquad
    \sum_{n=0}^Ny_n\varepsilon^n=\sum_{n=0}^N\frac{f^{(n)}(x_0)}{n!}\varepsilon^n.$$ ]
[:[:[autodiff_equation polynomial_transform.svg]]]

C++'s ability to overload operators and functions allows for the creation of a class `fvar` ([_f]orward-mode autodiff
[_var]iable) that represents polynomials in ['\u03b5]. Thus the same algorithm /f/ that calculates the numeric value
of ['y[sub 0]=f(x[sub 0])], when written to accept and return variables of a generic (template) type, is also used
to calculate the polynomial ['\u03a3[sub n]y[sub n]\u03b5[super n]=f(x[sub 0]+\u03b5)].  The derivatives
['f[super (n)](x[sub 0])] are then found from the product of the respective factorial ['n!] and coefficient
['y[sub n]]:

[/ \Large$$\frac{d^nf}{dx^n}(x_0)=n!y_n.$$ ]
[:[:[autodiff_equation derivative_formula.svg]]]


[h1:examples Examples]

[h2:example-single-variable Example 1: Single-variable derivatives]

[h3 Calculate derivatives of ['f(x)=x[super 4]] at /x/=2.]

In this example, `make_fvar<double, Order>(2.0)` instantiates the polynomial 2+['\u03b5]. The `Order=5`
means that enough space is allocated (on the stack) to hold a polynomial of up to degree 5 during the proceeding
computation.

Internally, this is modeled by a `std::array<double,6>` whose elements `{2, 1, 0, 0, 0, 0}` correspond
to the 6 coefficients of the polynomial upon initialization. Its fourth power, at the end of the computation, is
a polynomial with coefficients `y = {16, 32, 24, 8, 1, 0}`.  The derivatives are obtained using the formula
['f[super (n)](2)=n!*y[n]].

    #include <boost/math/differentiation/autodiff.hpp>
    #include <iostream>
    
    template <typename T>
    T fourth_power(T const& x) {
      T x4 = x * x;  // retval in operator*() uses x4's memory via NRVO.
      x4 *= x4;      // No copies of x4 are made within operator*=() even when squaring.
      return x4;     // x4 uses y's memory in main() via NRVO.
    }
    
    int main() {
      using namespace boost::math::differentiation;
    
      constexpr unsigned Order = 5;                  // Highest order derivative to be calculated.
      auto const x = make_fvar<double, Order>(2.0);  // Find derivatives at x=2.
      auto const y = fourth_power(x);
      for (unsigned i = 0; i <= Order; ++i)
        std::cout << "y.derivative(" << i << ") = " << y.derivative(i) << std::endl;
      return 0;
    }
    /*
    Output:
    y.derivative(0) = 16
    y.derivative(1) = 32
    y.derivative(2) = 48
    y.derivative(3) = 48
    y.derivative(4) = 24
    y.derivative(5) = 0
    */

The above calculates

[/ \Large\begin{alignat*}{3}
{\tt y.derivative(0)} &=& f(2) =&& \left.x^4\right|_{x=2} &= 16\\
{\tt y.derivative(1)} &=& f'(2) =&& \left.4\cdot x^3\right|_{x=2} &= 32\\
{\tt y.derivative(2)} &=& f''(2) =&& \left.4\cdot 3\cdot x^2\right|_{x=2} &= 48\\
{\tt y.derivative(3)} &=& f'''(2) =&& \left.4\cdot 3\cdot2\cdot x\right|_{x=2} &= 48\\
{\tt y.derivative(4)} &=& f^{(4)}(2) =&& 4\cdot 3\cdot2\cdot1 &= 24\\
{\tt y.derivative(5)} &=& f^{(5)}(2) =&& 0 &
\end{alignat*} ]
[:[:[autodiff_equation example1.svg]]]

[h2:example-multiprecision
Example 2: Multi-variable mixed partial derivatives with multi-precision data type]

[/ \Large$\frac{\partial^{12}f}{\partial w^{3}\partial x^{2}\partial y^{4}\partial z^{3}}(11,12,13,14)$]
[/ \Large$f(w,x,y,z)=\exp\left(w\sin\left(\frac{x\log(y)}{z}\right)+\sqrt{\frac{wz}{xy}}\right)+\frac{w^2}{\tan(z)}$]
[h3 Calculate [autodiff_equation mixed12.svg] with a precision of about 50 decimal digits,
where [autodiff_equation example2f.svg].]

In this example, `make_ftuple<float50, Nw, Nx, Ny, Nz>(11, 12, 13, 14)` returns a `std::tuple` of 4
independent `fvar` variables, with values of 11, 12, 13, and 14, for which the maximum order derivative to
be calculated for each are 3, 2, 4, 3, respectively. The order of the variables is important, as it is the same
order used when calling `v.derivative(Nw, Nx, Ny, Nz)` in the example below.

    #include <boost/math/differentiation/autodiff.hpp>
    #include <boost/multiprecision/cpp_bin_float.hpp>
    #include <iostream>
    
    using namespace boost::math::differentiation;
    
    template <typename W, typename X, typename Y, typename Z>
    promote<W, X, Y, Z> f(const W& w, const X& x, const Y& y, const Z& z) {
      using namespace std;
      return exp(w * sin(x * log(y) / z) + sqrt(w * z / (x * y))) + w * w / tan(z);
    }
    
    int main() {
      using float50 = boost::multiprecision::cpp_bin_float_50;
    
      constexpr unsigned Nw = 3;  // Max order of derivative to calculate for w
      constexpr unsigned Nx = 2;  // Max order of derivative to calculate for x
      constexpr unsigned Ny = 4;  // Max order of derivative to calculate for y
      constexpr unsigned Nz = 3;  // Max order of derivative to calculate for z
      // Declare 4 independent variables together into a std::tuple.
      auto const variables = make_ftuple<float50, Nw, Nx, Ny, Nz>(11, 12, 13, 14);
      auto const& w = std::get<0>(variables);  // Up to Nw derivatives at w=11
      auto const& x = std::get<1>(variables);  // Up to Nx derivatives at x=12
      auto const& y = std::get<2>(variables);  // Up to Ny derivatives at y=13
      auto const& z = std::get<3>(variables);  // Up to Nz derivatives at z=14
      auto const v = f(w, x, y, z);
      // Calculated from Mathematica symbolic differentiation.
      float50 const answer("1976.319600747797717779881875290418720908121189218755");
      std::cout << std::setprecision(std::numeric_limits<float50>::digits10)
                << "mathematica   : " << answer << '\n'
                << "autodiff      : " << v.derivative(Nw, Nx, Ny, Nz) << '\n'
                << std::setprecision(3)
                << "relative error: " << (v.derivative(Nw, Nx, Ny, Nz) / answer - 1) << '\n';
      return 0;
    }
    /*
    Output:
    mathematica   : 1976.3196007477977177798818752904187209081211892188
    autodiff      : 1976.3196007477977177798818752904187209081211892188
    relative error: 2.67e-50
    */

[h2:example-black_scholes
Example 3: Black-Scholes Option Pricing with Greeks Automatically Calculated]
[h3 Calculate greeks directly from the Black-Scholes pricing function.]

Below is the standard Black-Scholes pricing function written as a function template, where the price, volatility
(sigma), time to expiration (tau) and interest rate are template parameters. This means that any greek based on
these 4 variables can be calculated using autodiff. The below example calculates delta and gamma where the variable
of differentiation is only the price. For examples of more exotic greeks, see `example/black_scholes.cpp`.

```
#include <boost/math/differentiation/autodiff.hpp>
#include <iostream>

using namespace boost::math::constants;
using namespace boost::math::differentiation;

// Equations and function/variable names are from
// https://en.wikipedia.org/wiki/Greeks_(finance)#Formulas_for_European_option_Greeks

// Standard normal cumulative distribution function
template <typename X>
X Phi(X const& x) {
  return 0.5 * erfc(-one_div_root_two<X>() * x);
}

enum class CP { call, put };

// Assume zero annual dividend yield (q=0).
template <typename Price, typename Sigma, typename Tau, typename Rate>
promote<Price, Sigma, Tau, Rate> black_scholes_option_price(CP cp,
                                                            double K,
                                                            Price const& S,
                                                            Sigma const& sigma,
                                                            Tau const& tau,
                                                            Rate const& r) {
  using namespace std;
  auto const d1 = (log(S / K) + (r + sigma * sigma / 2) * tau) / (sigma * sqrt(tau));
  auto const d2 = (log(S / K) + (r - sigma * sigma / 2) * tau) / (sigma * sqrt(tau));
  switch (cp) {
    case CP::call:
      return S * Phi(d1) - exp(-r * tau) * K * Phi(d2);
    case CP::put:
      return exp(-r * tau) * K * Phi(-d2) - S * Phi(-d1);
  }
}

int main() {
  double const K = 100.0;                    // Strike price.
  auto const S = make_fvar<double, 2>(105);  // Stock price.
  double const sigma = 5;                    // Volatility.
  double const tau = 30.0 / 365;             // Time to expiration in years. (30 days).
  double const r = 1.25 / 100;               // Interest rate.
  auto const call_price = black_scholes_option_price(CP::call, K, S, sigma, tau, r);
  auto const put_price = black_scholes_option_price(CP::put, K, S, sigma, tau, r);

  std::cout << "black-scholes call price = " << call_price.derivative(0) << '\n'
            << "black-scholes put  price = " << put_price.derivative(0) << '\n'
            << "call delta = " << call_price.derivative(1) << '\n'
            << "put  delta = " << put_price.derivative(1) << '\n'
            << "call gamma = " << call_price.derivative(2) << '\n'
            << "put  gamma = " << put_price.derivative(2) << '\n';
  return 0;
}
/*
Output:
black-scholes call price = 56.5136
black-scholes put  price = 51.4109
call delta = 0.773818
put  delta = -0.226182
call gamma = 0.00199852
put  gamma = 0.00199852
*/
```

[h1 Advantages of Automatic Differentiation]
The above examples illustrate some of the advantages of using autodiff:

* Elimination of code redundancy. The existence of /N/ separate functions to calculate derivatives is a form
  of code redundancy, with all the liabilities that come with it:
    * Changes to one function require /N/ additional changes to other functions. In the 3rd example above,
      consider how much larger and inter-dependent the above code base would be if a separate function were
      written for [@https://en.wikipedia.org/wiki/Greeks_(finance)#Formulas_for_European_option_Greeks
      each Greek] value.
    * Dependencies upon a derivative function for a different purpose will break when changes are made to
      the original function. What doesn't need to exist cannot break.
    * Code bloat, reducing conceptual integrity. Control over the evolution of code is easier/safer when
      the code base is smaller and able to be intuitively grasped.
* Accuracy of derivatives over finite difference methods. Single-iteration finite difference methods always include
  a ['\u0394x] free variable that must be carefully chosen for each application. If ['\u0394x] is too small, then
  numerical errors become large. If ['\u0394x] is too large, then mathematical errors become large.  With autodiff,
  there are no free variables to set and the accuracy of the answer is generally superior to finite difference
  methods even with the best choice of ['\u0394x].

[h1 Manual]
Additional details are in the [@../differentiation/autodiff.pdf autodiff manual].

[endsect]