Quantitative Methods · Methods
28Transforms, Interpolation and Algorithmic Differentiation
The risk run bumps 400 curve points one at a time and reprices the book 401 times every night. The same 400 sensitivities come out of one recording of the pricing code and one sweep backwards through it, at the cost of a few prices. This chapter collects the numerical tools that pricing and risk code is made of: Fourier methods that price from a characteristic function, interpolation that turns a handful of quotes into a curve, root finders that invert a pricing formula, and algorithmic differentiation, which delivers exact derivatives of whatever the code computes.
28.1 Fourier methods
Definition 28.1 (Discrete and fast Fourier transforms)
The discrete Fourier transform of is , . A fast Fourier transform computes it in operations instead of by splitting it recursively into transforms of half the length (Cooley and Tukey, 1965).
A Lévy model is specified by its characteristic exponent (chapter 6), not its density, so prices come from the characteristic function . Carr and Madan (1999) price a whole strip of strikes with one fast Fourier transform of a damped call; the method below needs no damping and converges faster.
Proposition 28.2 (Fourier inversion)
For a random variable with characteristic function and a continuity point of its distribution, (Gil-Pelaez, 1951).
For a Merton jump diffusion (, jumps at rate 0.5 a year with mean and standard deviation 0.2 in log-price, , one year), the formula with the trapezoidal rule on gives , against the Poisson mixture of lognormals to .
Definition 28.3 (COS method)
The COS method (Fang and Oosterlee, 2008) truncates the density of to an interval , expands it in a cosine series whose coefficients are read off the characteristic function, , and integrates the payoff against each cosine in closed form, so that a price is (the first term halved).
Proposition 28.4 (Convergence of the COS method)
For a density that is smooth on the truncation interval, the cosine coefficients decay faster than any power of , and the error of the COS price decays exponentially in until the truncation error of dominates (Fang and Oosterlee, 2008; stated).
With set by ten standard deviations from the cumulants, a Black–Scholes call is exact to with 64 terms, and the Merton call to with 64 and with 128 (Figure 28.1). The firm prices calls from the put expansion and parity, which keeps the coefficients bounded. For a variance-gamma model, where only simulation is available as a check, 256 terms give 9.5243 and 400 000 simulated paths .
28.2 Interpolation and splines
Definition 28.5 (Cubic, natural and monotone splines, Runge phenomenon)
A cubic spline through is a piecewise cubic with continuous first and second derivatives; a natural cubic spline has zero second derivative at both ends, and its second derivatives at the nodes solve a tridiagonal system. Monotone cubic interpolation chooses the node slopes of a piecewise cubic Hermite interpolant so that it is monotone wherever the data are. The Runge phenomenon (Runge, 1901) is the growth, near the ends of the interval, of the error of polynomial interpolation at equally spaced nodes as the degree increases.
Proposition 28.6 (Minimal curvature of the natural spline)
Among all twice continuously differentiable functions through the data, the natural cubic spline minimises .
Proof. Let be the spline and , which vanishes at the nodes. Integrating by parts on each interval, , because vanishes at the ends and is constant on each interval, where . Hence . ∎
Proposition 28.7 (Fritsch–Carlson condition)
On an interval where the data increase with secant slope , the cubic Hermite interpolant with end slopes is monotone if and satisfy (Fritsch and Carlson, 1980).
Minimal curvature is not what a rates desk wants. On the Treasury curve of 3 July 2023 (constant-maturity yields from one month to thirty years, treated as zero rates for the illustration), the natural spline of the zero rates produces a forward curve that falls to 2.63% at thirty years; the monotone interpolation of , whose derivative is the forward, keeps every forward between 3.32% and 5.67%; linear interpolation of zero rates gives forwards that jump by up to 0.43 percentage points at the pillars (Figure 28.2). Hagan and West (2006) survey the methods used for curves. Global polynomials are worse still: through eleven equally spaced samples of on , the interpolating polynomial errs by 1.92 near the ends.
28.3 Root finding
Definition 28.8 (Bisection, secant and Brent’s methods)
The bisection method halves a bracket with at each step. The secant method replaces the derivative in Newton’s method by the slope through the last two iterates. Brent’s method (Brent, 1973) keeps a bracket and takes an inverse quadratic interpolation or secant step when it stays inside the bracket and shrinks it fast enough, and a bisection step otherwise.
Proposition 28.9 (Convergence of the root finders)
Bisection gains one bit per step and always converges; the secant method converges locally with order at a simple root; Brent’s method always converges, never much more slowly than bisection (at most about the square of the bisection count), and superlinearly near a simple root (Brent, 1973).
Implied volatility is the standard test. For a one-year call (, , true volatility 25%), bisection on needs 36 steps at every strike. At the money, Newton from 20% needs 3 iterations, the secant method 4, Brent 5. At a strike of 400 the price is and the vega at 20% is : Newton’s first step goes to a volatility of 3 062%, where the vega underflows, and the secant method also fails, while Brent converges in 18. The firm’s rule is a bracketing method with a fast interior step, and Newton only from a guess known to be close.
28.4 Forward and adjoint algorithmic differentiation
Definition 28.10 (Algorithmic differentiation, forward and reverse modes, dual numbers)
Algorithmic differentiation computes derivatives of a function given as a program by applying the chain rule to its elementary operations, exactly up to rounding. Forward mode propagates, with each value, its derivative along one input direction; dual numbers with implement it, since (Wengert, 1964). Reverse mode records the operations and then propagates adjoints from the output back to every input (Linnainmaa, 1976, from his 1970 master’s thesis on rounding errors; Griewank, 2012, recounts several independent discoveries); in finance it is called adjoint mode, or AAD.
Definition 28.11 (Tape, checkpointing)
A tape is the record, made during the forward evaluation, of each elementary operation’s inputs and local partial derivatives. Checkpointing stores only some intermediate states during the forward pass and recomputes the operations between them during the reverse pass, trading computation for memory.
Proposition 28.12 (Cheap gradient principle)
For a scalar function of inputs evaluated with elementary operations, each with at most two inputs, the reverse mode computes the whole gradient with a number of operations bounded by a small constant times , independent of ; forward mode and finite differences need passes (Griewank and Walther, 2008).
Proof. The recording evaluates each operation once and computes at most two local partials; the reverse sweep performs one multiply-add per recorded (operation, input) pair. With pairs, the total is in this count, whatever . Forward mode propagates one tangent per pass, so the full gradient needs passes. ∎
The firm’s toy book has 400 zero-rate pillars on thirty years, 120 quarterly cash flows and 119 caplets priced by Black’s formula on the three-month forwards: 2 864 operations for one value. The reverse sweep costs, in the count above, 3.58 evaluations; bumping costs 401 and forward mode 1 433 (Figure 28.3). The adjoint gradient agrees with forward mode to and with central differences to on sensitivities as large as 517 (the bumps’ own error); 213 of the 400 are nonzero, since only the pillars next to a cash-flow date matter under linear interpolation. The C++20 twin, timed in its acceptance test, returns the gradient of a similar 400-input function in less than twenty times one plain evaluation (between five and seven in the runs made for this chapter, tape allocation included).
The price of the reverse mode is memory: the tape holds every operation. A time-stepping loop of 10 000 steps records 60 003 nodes; with a checkpoint every 100 steps, the reverse pass holds at most 201 states (100 checkpoints and one segment) and recomputes the loop once, and the gradient is the same to . Two practical cautions: a payoff with a kink or a jump has a derivative that is zero or undefined almost everywhere, so AAD of a Monte Carlo digital returns zero and needs smoothing or a likelihood-ratio term; and code that branches on values records only the branch taken. Giles and Glasserman (2006) brought the adjoint method to Monte Carlo Greeks in finance.
28.5 Tutorial: one sweep instead of 401
Goal. Price by COS from a Lévy exponent, build three forward curves, invert Black–Scholes with four root finders, and differentiate a 400-input book three ways. End state: the four figures and the gradient agreement.
The tape. Nodes, the reverse sweep and one overloaded operation in the C++20 header.
struct Node { int a = -1, b = -1; double da = 0.0, db = 0.0; }; struct Tape { std::vector<Node> nodes; std::vector<double> adj; int push(int a, double da, int b = -1, double db = 0.0) { nodes.push_back({a, b, da, db}); return static_cast<int>(nodes.size()) - 1; } // one reverse sweep from `out`: adj[j] = sum over the uses i of j of adj[i] * d v_i / d v_j void reverse(int out) { adj.assign(nodes.size(), 0.0); adj[out] = 1.0; for (int i = out; i >= 0; --i) { const Node& n = nodes[i]; const double w = adj[i]; if (w == 0.0) continue; if (n.a >= 0) adj[n.a] += w * n.da; if (n.b >= 0) adj[n.b] += w * n.db; } } void clear() { nodes.clear(); adj.clear(); } }; inline thread_local Tape tape; struct Var { int idx; double v; Var(double x = 0.0) : idx(tape.push(-1, 0.0)), v(x) {} // an input (or a constant promoted to a node) Var(int i, double x) : idx(i), v(x) {} double adjoint() const { return tape.adj[idx]; } }; inline Var operator+(Var x, Var y) { return {tape.push(x.idx, 1.0, y.idx, 1.0), x.v + y.v}; } inline Var operator-(Var x, Var y) { return {tape.push(x.idx, 1.0, y.idx, -1.0), x.v - y.v}; } inline Var operator*(Var x, Var y) { return {tape.push(x.idx, y.v, y.idx, x.v), x.v * y.v}; }Listing 28.1. A tape, its reverse sweep and a recorded multiplication (C++20). code/firm/aad/cpp/firm_aad.hpp Checkpointing. The Python reference recomputes each segment on a fresh tape.
def checkpointed_gradient(step, x0: float, theta: float, n_steps: int, loss, every: int) -> dict: """d loss(x_n) / d(x0, theta) for x_{k+1} = step(x_k, theta), keeping only every `every`-th state in memory and recomputing each segment on a fresh tape during the reverse pass. Returns the gradient and the peak number of states held (checkpoints plus one segment's tape states).""" checkpoints = {0: float(x0)} x = float(x0) for k in range(n_steps): x = step(x, theta) if (k + 1) % every == 0: checkpoints[k + 1] = x tape = Tape() xv = tape.var(x) lo = loss(xv) x_bar = tape.adjoints(lo)[xv.idx] theta_bar = 0.0 starts = sorted(k for k in checkpoints if k < n_steps) peak = len(checkpoints) for s in reversed(starts): end = min(s + every, n_steps) tape = Tape() xs, th = tape.var(checkpoints[s]), tape.var(theta) y = xs for _ in range(s, end): y = step(y, th) peak = max(peak, len(checkpoints) + (end - s)) seed = tape.record(y.val * x_bar, (y.idx,), (x_bar,)) # d(x_bar * y) = x_bar dy bar = tape.adjoints(seed) x_bar, theta_bar = bar[xs.idx], theta_bar + bar[th.idx] return {"d_x0": x_bar, "d_theta": theta_bar, "value": _v(lo), "peak_states": peak}Listing 28.2. Reverse mode through a long loop with checkpoints (Python). code/firm/aad/firm_aad.py - Run
cos_convergence(),vg_check(),gil_pelaez_digital(),forward_curves(),implied_vol_iterations(),gradients()andcheckpoint_demo()inqm_transforms.py; thenfig_transforms.py.
What to change next. Replace linear by monotone interpolation inside the book and watch the number of nonzero sensitivities; differentiate the COS price with respect to the Merton parameters by running cos_call on dual numbers; add a second-order (forward over reverse) gamma.
28.6 Build: algorithmic differentiation
Purpose. Exact sensitivities of any pricing code at the cost of a few evaluations, for the pricing library and the risk engine of later books.
Interface. Python reference: Tape, Var, Dual, exp, log, sqrt, ncdf, maximum, gradient, forward_gradient, bump_gradient, checkpointed_gradient. C++20: firm::aad::Var on a thread-local tape, Tape::reverse, Dual. Rust: Var, reverse, clear, Dual, ncdf.
Rules. Every AAD gradient ships with a bump check on a sample of inputs; the tape is cleared per valuation and per thread; kinks and jumps are smoothed before differentiation; long loops are checkpointed.
Acceptance tests. code/firm/aad/{tests,cpp,rust}: elementary rules against hand derivatives; operation counts; the 400-input test function’s value and three gradient components equal in the three languages; forward against reverse to ; central and one-sided bumps; checkpointing equal to the full tape; the C++ time ratio below twenty.
Stretch. Vector forward mode; second derivatives; expression templates to shrink the tape; adjoints of a linear solve (chapter 25) without taping its inner loops.
Sources and further reading
- J. W. Cooley and J. W. Tukey, Mathematics of Computation 19, 1965; P. Carr and D. Madan, “Option valuation using the fast Fourier transform”, Journal of Computational Finance 2(4), 1999.
- F. Fang and C. W. Oosterlee, “A novel pricing method for European options based on Fourier-cosine series expansions”, SIAM Journal on Scientific Computing 31, 2008; J. Gil-Pelaez, Biometrika 38, 1951.
- F. N. Fritsch and R. E. Carlson, “Monotone piecewise cubic interpolation”, SIAM Journal on Numerical Analysis 17, 1980; P. S. Hagan and G. West, “Interpolation methods for curve construction”, Applied Mathematical Finance 13, 2006; C. Runge, Zeitschrift für Mathematik und Physik 46, 1901.
- R. P. Brent, Algorithms for Minimization without Derivatives, Prentice-Hall, 1973.
- R. E. Wengert, Communications of the ACM 7, 1964; S. Linnainmaa, BIT 16, 1976; A. Griewank, “Who invented the reverse mode of differentiation?”, Documenta Mathematica, Extra Volume ISMP, 2012; A. Griewank and A. Walther, Evaluating Derivatives, 2nd ed., SIAM, 2008; M. Giles and P. Glasserman, “Smoking adjoints: fast Monte Carlo Greeks”, Risk, January 2006.
- Treasury yields: Board of Governors of the Federal Reserve System, H.15, via FRED (series DGS1MO to DGS30), accessed 24 September 2026.
28.7 Exercises
Exercise 28.1 ★
How many complex multiplications does a direct discrete Fourier transform of length 4 096 need, and roughly how many does a radix-2 fast Fourier transform need?
Solution
Solution of Exercise 28.1.
Directly ; radix 2 about , some 700 times fewer.
Exercise 28.2 ★
Evaluate on the dual number and read off .
Solution
Solution of Exercise 28.2.
and , so : .
Exercise 28.3 ★
How many bisection steps reduce a bracket of width 5 below ?
Solution
Solution of Exercise 28.3.
needs : 36 steps, as in the tutorial.
Exercise 28.4 ★★
Write the tape of and run the reverse sweep by hand at .
Solution
Solution of Exercise 28.4.
Tape: , , (partials , ), (partial ), (partials 1, 1). At : . Reverse: ; , ; ; , so ; . Check: , .
Exercise 28.5 ★★
Show that the instantaneous forward is , and explain why a spline of with a large negative slope at the long end produces low forwards there.
Solution
Solution of Exercise 28.5.
, so . At the long end is large, so a spline slope of a year at lowers the forward by 3 percentage points: small wiggles of the zero curve become large wiggles of the forward, most of all at long maturities.
Exercise 28.6 ★★
Why does a pathwise AAD delta of a Monte Carlo digital option return zero, and name two remedies.
Solution
Solution of Exercise 28.6.
The payoff is piecewise constant in the path, so its pathwise derivative is zero almost surely: differentiating each simulated payoff misses the jump. Remedies: smooth the payoff (a narrow call spread), use the likelihood-ratio method for that term, integrate the last step analytically (conditional expectation), or combine them (Vibrato Monte Carlo).
Exercise 28.7 ★★★
Coding. Record the implied-volatility iteration counts of the four root finders at strikes 50, 70, 100, 150, 250 and 400, and explain the failures.
Solution
Solution of Exercise 28.7.
implied_vol_iterations(): bisection 36 at every strike; secant 10, 7, 4, 7, 18 and failure; Newton 9, 6, 3, 6, 13 and failure; Brent 17, 17, 5, 17, 17 and 18. At 400 the price () and the vega at the starting guess () are tiny: Newton’s first step overshoots to a volatility of 30.6, where the vega underflows, and the secant slope is equally unreliable; the bracketing methods cannot leave .
Exercise 28.8 ★★★
Find the flaw. “Our AAD gradient differs from the bumped one by on a sensitivity of 500, so the AAD library has a bug.”
Solution
Solution of Exercise 28.8.
A central difference has truncation error of order and rounding error of order ; at the book’s scale (value about 112, second and third derivatives large near the caplets’ strikes) a discrepancy of on 500 is a relative , what the bump itself is accurate to. The adjoint agrees with forward mode to : the bump is the less accurate of the two.
28.8 Problem: One Sweep Instead of 401
Problem 28.1
Weekend problem — 400 sensitivities for the price of a few
A toy rates book on a 400-pillar zero curve holds 120 quarterly cash flows and 119 caplets. The nightly risk run bumps each pillar.
Part I — Prices from transforms.
- How does the COS error behave with the number of terms for Black–Scholes and Merton?
- What does the Gil-Pelaez formula give for the Merton digital, and against what?
- How is the variance-gamma COS price checked?
- Why price calls from the put expansion?
- When is the fast Fourier transform the better tool?
Part II — Curves and roots.
- What happens to the forwards under the natural spline of zero rates, and where?
- What does monotone interpolation of guarantee, and what range of forwards does it give?
- What is wrong with linear interpolation of zero rates?
- How do the four root finders fare at the money and at a strike of 400?
- What is the Runge error on eleven equispaced nodes?
Part III — The gradient.
- How many operations does one valuation perform, and how many (operation, input) pairs?
- What do bumping, forward mode and reverse mode cost, in evaluations?
- How well do the three gradients agree, and why does the bump differ?
- Why are only 213 of 400 sensitivities nonzero?
- What does checkpointing buy on a 10 000-step loop?
Part IV — Judgement.
- Which sensitivities should still be bumped?
- What must the risk engine check every night?
- What does the C++ timing add to the operation count?
- State the named result: the cost ratio of the adjoint gradient to one evaluation for the 400-input book, against 401 for bumping, and the agreement between the two.
- In one sentence: why is the adjoint mode the natural mode for risk?
Solution
Solution of Problem 28.1.
1. Exponential decay: Black–Scholes exact to at 64 terms; Merton at 64 and at 128. 2. , against the Poisson mixture of lognormals to . 3. Against 400 000 simulated paths: 9.5243 against . 4. The put payoff is bounded, so its cosine coefficients stay small and truncation of the upper tail does not amplify errors; parity gives the call. 5. When a whole strip of strikes is needed at once on a regular grid, as in Carr and Madan’s method. 6. They fall to 2.63% at thirty years: the natural end condition lets the spline slope down steeply, and amplifies the slope at long maturities. 7. Monotone , hence nonnegative forwards whenever the data allow; here every forward lies between 3.32% and 5.67%. 8. Its forwards jump at every pillar, by up to 0.43 percentage points. 9. At the money Newton 3, secant 4, Brent 5, bisection 36; at 400 Newton and secant fail, Brent needs 18 and bisection 36. 10. 1.92 near the ends. 11. 2 864 elementary operations and 3 698 (operation, input) pairs. 12. Bumping 401 evaluations, forward mode 1 433, reverse mode 3.58 by the operation count. 13. Reverse and forward agree to ; reverse and central bumps to , the truncation and rounding error of the bumps. 14. With linear interpolation each cash-flow date depends on the two pillars around it; pillars with no date in their neighbourhood have zero sensitivity. 15. It holds at most 201 states instead of 10 000 (60 003 tape nodes), for one extra forward pass, with the same gradient to . 16. Sensitivities of discontinuous payoffs and of inputs that enter through non-differentiable code (lookups, branches), and a nightly sample as a check. 17. Adjoint against bump on a sample of inputs, tape size and memory, and that the tape was cleared per valuation. 18. The real cost, including tape memory traffic: operation counts ignore it; the C++ test measures under twenty evaluations. 19. Named result: one sweep instead of 401: the adjoint gradient of the 400-input book costs 3.58 evaluations by operation count, against 401 for bumping, and agrees with the bumped gradient to (with forward mode to ). 20. Risk asks for many inputs’ sensitivities of one number, and the reverse mode’s cost does not depend on how many inputs there are.
28.9 Interview questions
Interview question 28.1 ★ researcher, developer
Explain forward and reverse mode differentiation, and when each is cheaper.
Solution
Solution of Interview question 28.1.
Forward mode carries derivatives along one input direction with the values: cost proportional to the number of inputs, cheap for few inputs and many outputs. Reverse mode records the computation and propagates adjoints of one output back to all inputs: cost a small multiple of one evaluation, independent of the number of inputs, cheap for one output and many inputs, at the price of memory.
What the interviewer is looking for: Cost scaling with inputs versus outputs, and the memory of the tape.
Interview question 28.2 ★★ developer
How would you implement reverse mode in C++, and what limits its use in production?
Solution
Solution of Interview question 28.2.
An active type whose operators push (inputs, partials) onto a tape, and a reverse loop over the tape accumulating adjoints. Limits: memory of the tape (checkpointing), thread safety (a tape per thread), third-party code that is not templated, branches and kinks, and the overhead of taping inner loops, which expression templates or hand-written adjoints of kernels reduce.
What the interviewer is looking for: Tape design, memory and at least two production limits.
Interview question 28.3 ★★ researcher
How do you compute an implied volatility robustly for any strike?
Solution
Solution of Interview question 28.3.
Bracket the volatility (the price is increasing in it between known bounds), use a safeguarded method such as Brent’s, or Newton with a fallback to bisection, starting from a good rational approximation; work with normalised prices, and treat prices below intrinsic or above the upper bound as errors.
What the interviewer is looking for: Bracketing with a fast interior step, and bounds checks.
Interview question 28.4 ★★ researcher
Which interpolation would you use for a discount curve, and why not a natural cubic spline of zero rates?
Solution
Solution of Interview question 28.4.
Interpolate something whose shape carries the economics, such as or , with a local monotone scheme, so that forwards stay positive, continuous where desired, and local: a quote change moves the curve near its pillar only. A natural spline of zero rates is global (one quote moves all forwards) and can produce oscillating or negative forwards.
What the interviewer is looking for: Locality and the forward curve.
Interview question 28.5 ★★ researcher
How would you price a European option under a model known only through its characteristic function?
Solution
Solution of Interview question 28.5.
By Fourier methods: the COS expansion or Carr–Madan’s damped transform with a fast Fourier transform, or a Gil-Pelaez integral for probabilities; check the truncation range from the cumulants, and validate against simulation.
What the interviewer is looking for: COS or FFT, with a truncation choice and a check.
Interview question 28.6 ★★★ developer, risk
Your AAD Monte Carlo gives a zero delta for a barrier option. What happened and what do you do?
Solution
Solution of Interview question 28.6.
The barrier indicator is piecewise constant along each path, so the pathwise derivative misses the discontinuity. Use a smoothed barrier (a Brownian-bridge survival probability per step, which is differentiable), a likelihood-ratio term, or conditional expectations at the barrier; then check against a bumped price with common random numbers.
What the interviewer is looking for: Discontinuity and a differentiable reformulation.