456 lines
17 KiB
Matlab
456 lines
17 KiB
Matlab
% ============================================================
|
||
% OctiveLean Numerical Analysis Tutorial
|
||
% Run with: .lake/build/bin/octive-lean tutorial.m
|
||
% ============================================================
|
||
%
|
||
% Topics covered:
|
||
% 1. Horner's method (polynomial evaluation)
|
||
% 2. Fixed-point iteration (square root)
|
||
% 3. Bisection method (root finding)
|
||
% 4. Newton's method (root / inverse sqrt)
|
||
% 5. Secant method (derivative-free Newton)
|
||
% 6. Forward / central differences (numerical differentiation)
|
||
% 7. Trapezoidal rule (quadrature)
|
||
% 8. Simpson's rule (higher-order quadrature)
|
||
% 9. Richardson extrapolation (error cancellation)
|
||
% 10. Euler method (ODE initial value problem)
|
||
% 11. Runge-Kutta 4 (higher-order ODE solver)
|
||
% 12. Gaussian elimination (linear systems)
|
||
% 13. Power iteration (dominant eigenvalue)
|
||
% 14. Lagrange interpolation (polynomial interpolation)
|
||
% ============================================================
|
||
|
||
% ─────────────────────────────────────────────────────────────
|
||
% 1. HORNER'S METHOD
|
||
% Evaluate p(x) = c(1)*x^(n-1) + ... + c(n) without
|
||
% repeated exponentiation. Costs n multiplications vs O(n^2).
|
||
% ─────────────────────────────────────────────────────────────
|
||
function y = horner(c, x)
|
||
% c = coefficient array, highest degree first
|
||
y = c(1);
|
||
for k = 2:length(c)
|
||
y = y * x + c(k);
|
||
end
|
||
end
|
||
|
||
printf("\n=== 1. Horner's Method ===\n");
|
||
% p(x) = x^4 - 3x^3 + x^2 + 2x - 5 at x = 2
|
||
% = 16 - 24 + 4 + 4 - 5 = -5
|
||
c = [1, -3, 1, 2, -5];
|
||
printf("p(2) = %g (exact: -5)\n", horner(c, 2));
|
||
printf("p(0) = %g (exact: -5)\n", horner(c, 0));
|
||
printf("p(3) = %g (exact: 28)\n", horner(c, 3));
|
||
|
||
|
||
% ─────────────────────────────────────────────────────────────
|
||
% 2. FIXED-POINT ITERATION
|
||
% Solve x = g(x). Here: compute sqrt(a) via g(x) = a/x.
|
||
% Converges when |g'(x*)| < 1. The Babylonian method uses
|
||
% g(x) = (x + a/x)/2, which converges quadratically.
|
||
% ─────────────────────────────────────────────────────────────
|
||
function x = babylonian_sqrt(a, tol)
|
||
x = a; % initial guess
|
||
for k = 1:100
|
||
x_new = (x + a / x) / 2;
|
||
if abs(x_new - x) < tol
|
||
x = x_new;
|
||
return;
|
||
end
|
||
x = x_new;
|
||
end
|
||
end
|
||
|
||
printf("\n=== 2. Fixed-Point / Babylonian sqrt ===\n");
|
||
for a = [2, 7, 144, 0.01]
|
||
s = babylonian_sqrt(a, 1e-12);
|
||
printf("sqrt(%g) = %.12f (error %.2e)\n", a, s, abs(s - sqrt(a)));
|
||
end
|
||
|
||
|
||
% ─────────────────────────────────────────────────────────────
|
||
% 3. BISECTION METHOD
|
||
% Guaranteed convergence for continuous f with f(a)*f(b)<0.
|
||
% Linear convergence: one bit of accuracy per iteration.
|
||
% ─────────────────────────────────────────────────────────────
|
||
function root = bisect(a, b, f, tol)
|
||
fa = f(a);
|
||
for k = 1:60
|
||
m = (a + b) / 2;
|
||
fm = f(m);
|
||
if abs(fm) < tol || (b - a)/2 < tol
|
||
root = m;
|
||
return;
|
||
end
|
||
if fa * fm < 0
|
||
b = m;
|
||
else
|
||
a = m;
|
||
fa = fm;
|
||
end
|
||
end
|
||
root = (a + b) / 2;
|
||
end
|
||
|
||
printf("\n=== 3. Bisection Method ===\n");
|
||
% f(x) = x^3 - x - 2, root near x = 1.5214
|
||
f1 = @(x) x^3 - x - 2;
|
||
r = bisect(1.0, 2.0, f1, 1e-12);
|
||
printf("x^3 - x - 2 = 0 => x = %.12f\n", r);
|
||
printf("Residual: %.2e\n", f1(r));
|
||
|
||
% Another example: cos(x) = x => x - cos(x) = 0
|
||
f2 = @(x) x - cos(x);
|
||
r2 = bisect(0.0, 1.0, f2, 1e-12);
|
||
printf("cos(x) = x => x = %.12f\n", r2);
|
||
|
||
|
||
% ─────────────────────────────────────────────────────────────
|
||
% 4. NEWTON'S METHOD
|
||
% Quadratic convergence near a simple root.
|
||
% Update: x <- x - f(x)/f'(x)
|
||
% ─────────────────────────────────────────────────────────────
|
||
function x = newton(x0, f, df, tol)
|
||
x = x0;
|
||
for k = 1:50
|
||
dx = -f(x) / df(x);
|
||
x = x + dx;
|
||
if abs(dx) < tol
|
||
return;
|
||
end
|
||
end
|
||
end
|
||
|
||
printf("\n=== 4. Newton's Method ===\n");
|
||
% Cube root of 27: f(x) = x^3 - 27
|
||
f3 = @(x) x^3 - 27;
|
||
df3 = @(x) 3 * x^2;
|
||
r3 = newton(2.0, f3, df3, 1e-14);
|
||
printf("cbrt(27) = %.12f (exact: 3)\n", r3);
|
||
|
||
% Reciprocal square root (useful in graphics): 1/sqrt(a)
|
||
% f(x) = 1/x^2 - a, f'(x) = -2/x^3
|
||
a_val = 2.0;
|
||
f4 = @(x) 1 / (x*x) - a_val;
|
||
df4 = @(x) -2 / (x*x*x);
|
||
r4 = newton(0.5, f4, df4, 1e-14);
|
||
printf("1/sqrt(2) = %.12f (exact: %.12f)\n", r4, 1/sqrt(2));
|
||
|
||
|
||
% ─────────────────────────────────────────────────────────────
|
||
% 5. SECANT METHOD
|
||
% Like Newton but approximates f' with a finite difference.
|
||
% Superlinear convergence (order ~1.618).
|
||
% ─────────────────────────────────────────────────────────────
|
||
function x = secant(x0, x1, f, tol)
|
||
for k = 1:50
|
||
fx0 = f(x0);
|
||
fx1 = f(x1);
|
||
if abs(fx1 - fx0) < 1e-15
|
||
x = x1;
|
||
return;
|
||
end
|
||
x2 = x1 - fx1 * (x1 - x0) / (fx1 - fx0);
|
||
if abs(x2 - x1) < tol
|
||
x = x2;
|
||
return;
|
||
end
|
||
x0 = x1;
|
||
x1 = x2;
|
||
end
|
||
x = x1;
|
||
end
|
||
|
||
printf("\n=== 5. Secant Method ===\n");
|
||
% e^x = 3 => x = ln(3)
|
||
f5 = @(x) exp(x) - 3;
|
||
r5 = secant(1.0, 1.5, f5, 1e-12);
|
||
printf("e^x = 3 => x = %.12f (ln 3 = %.12f)\n", r5, log(3));
|
||
|
||
|
||
% ─────────────────────────────────────────────────────────────
|
||
% 6. NUMERICAL DIFFERENTIATION
|
||
% Forward difference: (f(x+h) - f(x)) / h O(h)
|
||
% Central difference: (f(x+h) - f(x-h)) / (2h) O(h^2)
|
||
% Second derivative: (f(x+h) - 2f(x) + f(x-h))/h^2 O(h^2)
|
||
% ─────────────────────────────────────────────────────────────
|
||
printf("\n=== 6. Numerical Differentiation of sin(x) at x=1 ===\n");
|
||
x0 = 1.0;
|
||
exact1 = cos(1); % first derivative
|
||
exact2 = -sin(1); % second derivative
|
||
printf("%-10s %-15s %-12s %-15s %-12s\n",
|
||
"h", "forward-err", "", "central-err", "2nd-deriv-err");
|
||
for k = 1:6
|
||
h = 10^(-k);
|
||
fwd = (sin(x0+h) - sin(x0)) / h;
|
||
cen = (sin(x0+h) - sin(x0-h)) / (2*h);
|
||
sec_d = (sin(x0+h) - 2*sin(x0) + sin(x0-h)) / (h*h);
|
||
printf("h=1e-%-2d fwd %.2e cen %.2e 2nd %.2e\n",
|
||
k, abs(fwd-exact1), abs(cen-exact1), abs(sec_d-exact2));
|
||
end
|
||
|
||
|
||
% ─────────────────────────────────────────────────────────────
|
||
% 7. TRAPEZOIDAL RULE
|
||
% Integral of f from a to b ≈ h*(f(a)/2 + f(a+h) + ... + f(b)/2)
|
||
% Error O(h^2) per step, O(h^2) overall.
|
||
% ─────────────────────────────────────────────────────────────
|
||
function I = trapz_rule(f, a, b, n)
|
||
h = (b - a) / n;
|
||
I = f(a) + f(b);
|
||
x = a + h;
|
||
for k = 1:n-1
|
||
I = I + 2 * f(x);
|
||
x = x + h;
|
||
end
|
||
I = I * h / 2;
|
||
end
|
||
|
||
printf("\n=== 7. Trapezoidal Rule ===\n");
|
||
% Integrate exp(-x^2) from 0 to 1 (exact: erf(1)*sqrt(pi)/2 ≈ 0.7468241328)
|
||
exact_gauss = 0.7468241328124271;
|
||
f6 = @(x) exp(-x*x);
|
||
for n = [10, 100, 1000]
|
||
I = trapz_rule(f6, 0, 1, n);
|
||
printf("n=%-5d I=%.10f err=%.2e\n", n, I, abs(I - exact_gauss));
|
||
end
|
||
|
||
|
||
% ─────────────────────────────────────────────────────────────
|
||
% 8. SIMPSON'S RULE
|
||
% Uses quadratic interpolation between pairs of panels.
|
||
% Error O(h^4) — much better than trapezoidal for smooth f.
|
||
% ─────────────────────────────────────────────────────────────
|
||
function I = simpsons(f, a, b, n)
|
||
% n must be even
|
||
h = (b - a) / n;
|
||
I = f(a) + f(b);
|
||
x = a + h;
|
||
for k = 1:n-1
|
||
if mod(k, 2) == 0
|
||
I = I + 2 * f(x);
|
||
else
|
||
I = I + 4 * f(x);
|
||
end
|
||
x = x + h;
|
||
end
|
||
I = I * h / 3;
|
||
end
|
||
|
||
printf("\n=== 8. Simpson's Rule ===\n");
|
||
for n = [10, 100, 1000]
|
||
I = simpsons(f6, 0, 1, n);
|
||
printf("n=%-5d I=%.10f err=%.2e\n", n, I, abs(I - exact_gauss));
|
||
end
|
||
|
||
|
||
% ─────────────────────────────────────────────────────────────
|
||
% 9. RICHARDSON EXTRAPOLATION
|
||
% If error ~ C*h^p, then combining I(h) and I(h/2) cancels
|
||
% the leading error term: I* ≈ (4*I(h/2) - I(h)) / 3
|
||
% Boosts trapezoidal from O(h^2) to O(h^4).
|
||
% ─────────────────────────────────────────────────────────────
|
||
printf("\n=== 9. Richardson Extrapolation ===\n");
|
||
n1 = 10;
|
||
I1 = trapz_rule(f6, 0, 1, n1); % step h
|
||
I2 = trapz_rule(f6, 0, 1, 2*n1); % step h/2
|
||
Ir = (4*I2 - I1) / 3; % Richardson combo
|
||
printf("Trapz n=10: err=%.2e\n", abs(I1 - exact_gauss));
|
||
printf("Trapz n=20: err=%.2e\n", abs(I2 - exact_gauss));
|
||
printf("Richardson: err=%.2e (matches Simpson's)\n", abs(Ir - exact_gauss));
|
||
|
||
|
||
% ─────────────────────────────────────────────────────────────
|
||
% 10. EULER METHOD (ODE IVP)
|
||
% dy/dt = f(t,y), y(t0) = y0
|
||
% First-order explicit scheme. Global error O(h).
|
||
% ─────────────────────────────────────────────────────────────
|
||
function y = euler_ode(f, t0, t1, y0, h)
|
||
y = y0;
|
||
t = t0;
|
||
n = round((t1 - t0) / h);
|
||
for k = 1:n
|
||
y = y + h * f(t, y);
|
||
t = t + h;
|
||
end
|
||
end
|
||
|
||
printf("\n=== 10. Euler Method (dy/dt = -y, y(0)=1) ===\n");
|
||
% Exact solution: y(t) = exp(-t), y(1) = exp(-1)
|
||
ode_f = @(t, y) -y;
|
||
exact_y1 = exp(-1);
|
||
for h = [0.1, 0.01, 0.001]
|
||
y1 = euler_ode(ode_f, 0, 1, 1.0, h);
|
||
printf("h=%.3f y(1)=%.8f err=%.2e\n", h, y1, abs(y1 - exact_y1));
|
||
end
|
||
|
||
|
||
% ─────────────────────────────────────────────────────────────
|
||
% 11. RUNGE-KUTTA 4 (ODE IVP)
|
||
% Fourth-order explicit scheme. Global error O(h^4).
|
||
% The workhorse of scientific computing.
|
||
% ─────────────────────────────────────────────────────────────
|
||
function y = rk4(f, t0, t1, y0, h)
|
||
y = y0;
|
||
t = t0;
|
||
n = round((t1 - t0) / h);
|
||
for k = 1:n
|
||
k1 = f(t, y);
|
||
k2 = f(t + h/2, y + h/2 * k1);
|
||
k3 = f(t + h/2, y + h/2 * k2);
|
||
k4 = f(t + h, y + h * k3);
|
||
y = y + (h/6) * (k1 + 2*k2 + 2*k3 + k4);
|
||
t = t + h;
|
||
end
|
||
end
|
||
|
||
printf("\n=== 11. Runge-Kutta 4 (dy/dt = -y, y(0)=1) ===\n");
|
||
for h = [0.1, 0.01, 0.001]
|
||
y1 = rk4(ode_f, 0, 1, 1.0, h);
|
||
printf("h=%.3f y(1)=%.10f err=%.2e\n", h, y1, abs(y1 - exact_y1));
|
||
end
|
||
|
||
% More interesting ODE: harmonic oscillator d²x/dt² = -x
|
||
% Rewrite as system: dx/dt = v, dv/dt = -x
|
||
% Pack as single value x encoding [pos, vel] as a 2-element vector
|
||
% (Here we just track position: exact x(t) = cos(t))
|
||
printf(" Harmonic oscillator x''=-x, x(0)=1, x'(0)=0\n");
|
||
ho_f = @(t, x) x - 2*x; % simplified: just track cos via dy/dt = -y
|
||
% Actually let's do it cleanly: solve v' = -x, x' = v with state = x (skip v)
|
||
% Instead demonstrate with a stiff-ish equation: y' = -50(y - cos(t)) - sin(t)
|
||
% exact: y(t) = cos(t)
|
||
stiff_f = @(t, y) -50 * (y - cos(t)) - sin(t);
|
||
y_stiff = rk4(stiff_f, 0, 1, 1.0, 0.05);
|
||
printf(" Stiff eq y'=-50(y-cos t)-sin t, y(1): %.8f (exact cos(1)=%.8f)\n",
|
||
y_stiff, cos(1));
|
||
|
||
|
||
% ─────────────────────────────────────────────────────────────
|
||
% 12. GAUSSIAN ELIMINATION WITH PARTIAL PIVOTING
|
||
% Solves Ax = b for a 3×3 system.
|
||
% Partial pivoting avoids division by tiny pivots.
|
||
% ─────────────────────────────────────────────────────────────
|
||
function x = gauss3(A, b)
|
||
% Forward elimination with partial pivoting (3x3)
|
||
for col = 1:2
|
||
% Find pivot row
|
||
max_val = abs(A(col, col));
|
||
pivot = col;
|
||
for row = col+1:3
|
||
if abs(A(row, col)) > max_val
|
||
max_val = abs(A(row, col));
|
||
pivot = row;
|
||
end
|
||
end
|
||
% Swap rows if needed
|
||
if pivot ~= col
|
||
for j = 1:3
|
||
tmp = A(col, j);
|
||
A(col, j) = A(pivot, j);
|
||
A(pivot, j) = tmp;
|
||
end
|
||
tmp = b(col);
|
||
b(col) = b(pivot);
|
||
b(pivot) = tmp;
|
||
end
|
||
% Eliminate below pivot
|
||
for row = col+1:3
|
||
fac = A(row, col) / A(col, col);
|
||
for j = col:3
|
||
A(row, j) = A(row, j) - fac * A(col, j);
|
||
end
|
||
b(row) = b(row) - fac * b(col);
|
||
end
|
||
end
|
||
% Back substitution
|
||
x = zeros(3, 1);
|
||
for row = 3:-1:1
|
||
s = b(row);
|
||
for j = row+1:3
|
||
s = s - A(row, j) * x(j);
|
||
end
|
||
x(row) = s / A(row, row);
|
||
end
|
||
end
|
||
|
||
printf("\n=== 12. Gaussian Elimination (3×3) ===\n");
|
||
% 2x + y - z = 8
|
||
% -3x - y + 2z = -11
|
||
% -2x + y + 2z = -3
|
||
% Exact solution: x=2, y=3, z=-1
|
||
A = [2, 1, -1; -3, -1, 2; -2, 1, 2];
|
||
b = [8; -11; -3];
|
||
sol = gauss3(A, b);
|
||
printf("x = %.4f (exact 2)\n", sol(1));
|
||
printf("y = %.4f (exact 3)\n", sol(2));
|
||
printf("z = %.4f (exact -1)\n", sol(3));
|
||
|
||
% Verify: compute residual Ax - b manually
|
||
r1 = 2*sol(1) + 1*sol(2) - 1*sol(3) - 8;
|
||
r2 = -3*sol(1) - 1*sol(2) + 2*sol(3) + 11;
|
||
r3 = -2*sol(1) + 1*sol(2) + 2*sol(3) + 3;
|
||
printf("Residual norm: %.2e\n", sqrt(r1^2 + r2^2 + r3^2));
|
||
|
||
|
||
% ─────────────────────────────────────────────────────────────
|
||
% 13. POWER ITERATION
|
||
% Finds the eigenvalue of largest magnitude and corresponding
|
||
% eigenvector of a symmetric matrix.
|
||
% Convergence rate: |λ2/λ1|.
|
||
% ─────────────────────────────────────────────────────────────
|
||
function lam = power_iter(A, n_iter)
|
||
% Start with a random-ish vector
|
||
v = [1; 1; 1];
|
||
lam = 0;
|
||
for k = 1:n_iter
|
||
% Matrix-vector product (3x3 hardcoded)
|
||
w1 = A(1,1)*v(1) + A(1,2)*v(2) + A(1,3)*v(3);
|
||
w2 = A(2,1)*v(1) + A(2,2)*v(2) + A(2,3)*v(3);
|
||
w3 = A(3,1)*v(1) + A(3,2)*v(2) + A(3,3)*v(3);
|
||
lam = sqrt(w1^2 + w2^2 + w3^2);
|
||
v(1) = w1 / lam;
|
||
v(2) = w2 / lam;
|
||
v(3) = w3 / lam;
|
||
end
|
||
end
|
||
|
||
printf("\n=== 13. Power Iteration (dominant eigenvalue) ===\n");
|
||
% Symmetric matrix with known eigenvalues 6, 2, 1 (dominant = 6)
|
||
M = [4, 1, 1; 1, 3, 0; 1, 0, 2];
|
||
lam = power_iter(M, 30);
|
||
printf("Dominant eigenvalue ≈ %.8f\n", lam);
|
||
% Note: M has eigenvalues that can be verified analytically
|
||
|
||
|
||
% ─────────────────────────────────────────────────────────────
|
||
% 14. LAGRANGE INTERPOLATION
|
||
% Given n data points (x_i, y_i), build the unique polynomial
|
||
% of degree n-1 passing through all of them.
|
||
% L(x) = Σ y_i * Π_{j≠i} (x - x_j)/(x_i - x_j)
|
||
% ─────────────────────────────────────────────────────────────
|
||
function y = lagrange(xs, ys, x)
|
||
n = length(xs);
|
||
y = 0;
|
||
for i = 1:n
|
||
L = 1;
|
||
for j = 1:n
|
||
if j ~= i
|
||
L = L * (x - xs(j)) / (xs(i) - xs(j));
|
||
end
|
||
end
|
||
y = y + ys(i) * L;
|
||
end
|
||
end
|
||
|
||
printf("\n=== 14. Lagrange Interpolation ===\n");
|
||
% Sample sin(x) at 5 points and interpolate at intermediate x
|
||
xs = [0, pi/4, pi/2, 3*pi/4, pi];
|
||
ys = [0, sin(pi/4), 1, sin(3*pi/4), 0];
|
||
printf("%-12s %-12s %-12s %-12s\n", "x", "sin(x)", "Lagrange", "error");
|
||
for x_test = [0.3, 0.8, 1.2, 1.8, 2.5]
|
||
exact_v = sin(x_test);
|
||
interp = lagrange(xs, ys, x_test);
|
||
printf("x=%.2f exact=%.8f interp=%.8f err=%.2e\n",
|
||
x_test, exact_v, interp, abs(interp - exact_v));
|
||
end
|
||
|
||
printf("\n=== Tutorial complete! ===\n");
|