We will cover many topics in the first three fields.
A rough schedule of the class:
Week | Main Topics | Practical Problems | Instructor |
---|---|---|---|
1 | Introduction and Error Analysis | Error and floating point numbers | Y. Li |
2-3 | Linear Algebra | Portfolio optimization, PCA, least square | Y. Li |
4-5 | Rootfinding and interpolation | Curve building | Y. Li |
6 | Derivatives and integration | Hedging, Risk Transformation | Y. Li |
7-8 | Monte Carlo simulation | Exotic pricing | Y. Li |
9-10 | Optimization | Model calibration | H. Cheng |
11-12 | ODE/PDE | Pricing | H. Cheng |
13 | Entropy and Allocation | Advanced practical problems | Y. Li |
D Bindel and J Goodman, 2009
L Anderson and V. Piterbarg, 2010
P. Glasserman 2010
Available online at: http://yadongli.github.io/nyumath2048
Roberto Waltman: In the one and only true way. The object-oriented version of "Spaghetti code" is, of course, "Lasagna code".
Memory hierarchy
Storage | Bandwidth | Latency | Capacity |
---|---|---|---|
L-1 Cache | 98GB/s | 4 cycles | 32K/core |
L-2 Cache | 50GB/s | 10 cycles | 256K/core |
L-3 Cache | 30GB/s | 40 - 75 cycles | 8MB shared |
RAM | 15GB/s | 60-100 ns | ~TB |
SDD | up to 4GB/s | ~0.1ms | $\infty$ |
Network | up to 12GB/s | much longer | $\infty$ |
In practice, what often trumps everything else is:
These are good advice, but quite superficial.
by Paul Phillips "We're doing it all wrong":
(quoting George Orwell's 1984)
Sun Tzu (孙子): "the supreme art of quant modeling is to solve problems without coding"
There is no single right answer
Eric Raymond, "How to Become a Hacker":
"Lisp is worth learning for the profound enlightenment experience you will have when you finally get it; that experience will make you a better programmer for the rest of your days, even if you never actually use Lisp itself a lot."
Python
Python ecosystem
Python resources
Python version:
Python setup instructions:
James Gosling: "95% of the folks out there are completely clueless about floating-point"
If the true value $a$ is approximated by $\hat{a}$:
Both measure are useful in practice.
Imprecision in the theory or model itself
Errors in numerical algorithm
Error due to machine representation
We cover the last category in this lecture.
First published in 1985, the IEEE 754 standard includes:
Default rule: round to the nearest, ties to even digit
IEEE 754 is a tremendously successful standard:
The IEEE 754 standard is widely adopted in modern hardware and software,
Format | Sign | Fraction | Exponent | $p$ | (Exponent-p) range |
---|---|---|---|---|---|
IEEE 754 - 32 bit | 1 | 23 | 8 | 127 | -126 to 127 |
IEEE 754 - 64 bit | 1 | 52 | 11 | 1023 | -1022 to 1023 |
f2parts takes a floating number and its bit length (32 or 64) and shows the three parts.
f2parts(10.7, 32);
Sign | Exp | Fraction | |
---|---|---|---|
Bits | 0 | 10000010 | 01010110011001100110011 |
Decimal | 1.0 | 3.0 | 1.337499976158142 |
f2parts(-23445.25, 64)
Sign | Exp | Fraction | |
---|---|---|---|
Bits | 1 | 10000001101 | 0110111001010101000000000000000000000000000000000000 |
Decimal | -1.0 | 14.0 | 1.4309844970703125 |
prec32 = 2**(-24)
prec64 = 2**(-53)
f32 = [parts2f(0, -126, 1), parts2f(0, 254-127, (2.-prec32*2)), prec32, -np.log10(prec32)]
f64 = [parts2f(0, -1022, 1), parts2f(0, 2046-1023, (2.-prec64*2)), prec64, -np.log10(prec64)]
fmt.displayDF(pd.DataFrame(np.array([f32, f64]), index=['32 bit', '64 bit'],
columns=["Min", "Max", "Machine Precision", "# of Significant Digits"]), "5g")
Min | Max | Machine Precision | # of Significant Digits | |
---|---|---|---|---|
32 bit | 1.1755e-38 | 3.4028e+38 | 5.9605e-08 | 7.2247 |
64 bit | 2.2251e-308 | 1.7977e+308 | 1.1102e-16 | 15.955 |
f2parts(-0., 32);
Sign | Exp | Fraction | |
---|---|---|---|
Bits | 1 | 00000000 | 00000000000000000000000 |
Decimal | -1.0 | -127.0 | 0.0 |
f2parts(np.NaN, 32);
Sign | Exp | Fraction | |
---|---|---|---|
Bits | 0 | 11111111 | 10000000000000000000000 |
Decimal | 1.0 | 128.0 | 1.5 |
f2parts(-np.Inf, 32);
Sign | Exp | Fraction | |
---|---|---|---|
Bits | 1 | 11111111 | 00000000000000000000000 |
Decimal | -1.0 | 128.0 | 1.0 |
f2parts(-1e-43, 32);
Sign | Exp | Fraction | |
---|---|---|---|
Bits | 1 | 00000000 | 00000000000000001000111 |
Decimal | -1.0 | -127.0 | 1.6927719116210938e-05 |
a = 1./3
b = a + a + 1. - 1.
c = 2*a
print("b == c? {} !".format(b == c))
print("b - c = {:e}".format(b - c))
b == c? False ! b - c = -1.110223e-16
f2parts(b, 64);
Sign | Exp | Fraction | |
---|---|---|---|
Bits | 0 | 01111111110 | 0101010101010101010101010101010101010101010101010100 |
Decimal | 1.0 | -1.0 | 1.333333333333333 |
f2parts(c, 64);
Sign | Exp | Fraction | |
---|---|---|---|
Bits | 0 | 01111111110 | 0101010101010101010101010101010101010101010101010101 |
Decimal | 1.0 | -1.0 | 1.3333333333333333 |
x = 1e-308
print("inverse of {:e} is {:e}".format(x, 1/x))
inverse of 1.000000e-308 is 1.000000e+308
x = 1e-309
print("inverse of {:e} is {:e}".format(x, 1/x))
inverse of 1.000000e-309 is inf
print(1. - 1e-16 == 1.)
False
print(1. + 1e-16 == 1.)
True
largeint = 2**32 - 1
print("Integer arithmetic:")
a = math.factorial(30) # in Python's bignum
print("a = ", a)
print("a - {:d} == a ?".format(largeint), (a - largeint) == a)
print("\nFloating point arithmetic:")
b = float(math.factorial(30))
print("b = ", b)
print("b - {:d} == b ?".format(largeint), (b - largeint) == b)
Integer arithmetic: a = 265252859812191058636308480000000 a - 4294967295 == a ? False Floating point arithmetic: b = 2.6525285981219107e+32 b - 4294967295 == b ? True
The limited precision can cause subtle errors that are puzzling even for experiened programmers.
a = np.arange(0, 3. - 1., 1.)
print("a = ", a)
print("a/3 = ", a/3.)
a = [0. 1.] a/3 = [0. 0.33333333]
b = np.arange(0, 1 - 1./3., 1./3.)
print("b = ", b)
b = [0. 0.33333333 0.66666667]
Equality test in floating point number is always a bad idea:
It should become your instinct to:
numpy.allclose()
Dramatic loss of precision can happen when very similar numbers are subtracted
The straight forward implementation failed spectacularly for small $x$.
def f(x) :
return (1.-np.cos(x))/x**2
def g(x) :
return (np.sin(.5*x)**2*2)/x**2
x = np.arange(-4e-8, 4e-8, 1e-9)
df = pd.DataFrame(np.array([f(x), g(x)]).T, index=x, columns=['Numerical f(x)', 'True Value']);
df.plot();
x = 1.1e-8
print("cos({:.16e}) = {:.16f}".format(x, cos(x)))
print("1-cos({:.0e}) = {:.16e}".format(x, 1.-cos(x)))
print("true value of 1-cos({:.0e}) = {:.16e}".format(x, 2*sin(.5*x)**2))
cos(1.0999999999999999e-08) = 0.9999999999999999 1-cos(1e-08) = 1.1102230246251565e-16 true value of 1-cos(1e-08) = 6.0499999999999997e-17
the CIR process is widely used in quant finance, eg, in the short rate and Heston model:
$$ dr_t = \kappa(\mu - r) dt + \sigma \sqrt{r_t} dW_t $$the variance of $r_t$ at $t > 0$:
$$\text{Var}[r_t | r_0] = \frac{r_0\sigma^2}{\kappa}(e^{-\kappa t} - e^{-2\kappa t}) + \frac{\mu\sigma^2}{2\kappa}(1-e^{-\kappa t})^2$$how the catastrophic cancellation arises?
k = arange(-3e-8, 3e-8, 1e-9)*1e-7
t = 1
v = (1-exp(-k*t))/k
plot(k, v, '.-')
title(r'$\frac{1}{\kappa}(1-\exp(-\kappa))$')
xlabel(r'$\kappa$');
How to choose the $h$ so that the following finite difference approximation is the most accurate?
$$ f'(x) \approx \frac{1}{h}(f(x+h) - f(x)) $$The best $h$ is when truncation error equals to the rounding error:
$$ \frac{1}{2} f''(x) h^2 = f(x) \epsilon_m $$Thus the optimal $h^* = \sqrt{\frac{2 f(x)\epsilon_m}{f''(x)}} $.
It is however better off to simply use central difference:
$$ f'(x) \approx \frac{1}{2h}\left(f(x+h) - f(x-h)\right) $$Consider the relative error of a function with multiple arguments: $f = f(x_1, ..., x_n)$:
$$ \small df = \sum_i \frac{\partial f}{\partial x_i} dx_i \iff \frac{df}{f} = \sum_i \frac{x_i}{f}\frac{\partial f}{\partial x_i} \frac{dx_i}{x_i} $$Assuming input argument's relative errors are $\epsilon_i$ (could be negative):
$$ \small \left| \frac{\Delta f}{f} \right| = \left| \sum_i \frac{x_i}{f}\frac{\partial f}{\partial x_i} \epsilon_i \right| \le \sum_i \left| \frac{x_i}{f}\frac{\partial f}{\partial x_i} \right| \left|\epsilon_i\right| \le \sum_i \left| \frac{x_i}{f}\frac{\partial f}{\partial x_i} \right| \epsilon_m \equiv k(f) \epsilon_m $$where $\epsilon_m$ is the maximum relative error of all inputs.
Condition number is the systematic approach to detect potential numerical problems:
$f(x_1, x_2) = x_1 - x_2$
$k(f) = \frac{|x_1| + |x_2|}{|x_1 - x_2|}$, ill conditioned when $x_1 \approx x_2$, catestrophic cancellation
$f(x_1, x_2) = x_1 x_2$
$k(f) = 2$, the multiplication is well conditioned
The problem is ill-posed if its condition number is large.
Consider the previous example of $f(x) = \frac{1}{x^2}(1-\cos(x))$:
$$ k(f) = \left| 2 + \frac{x \sin{\left (x \right )}}{\cos{\left (x \right )} - 1} \right| $$The numerical algorithm can be unstable even if the problem itself is well-posed.
But an ill-posed problem is always numerically unstable by definition.
Ill-posed problem are generally not suitable for numerical solutions.
The Mandelbrot set is a famous example:
Required Reading:
Recommended Reading:
Homework