numericalanalysisiiser 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- numericalanalysisiiser/__init__.py +35 -0
- numericalanalysisiiser/bisection.py +38 -0
- numericalanalysisiiser/euler.py +39 -0
- numericalanalysisiiser/finite_differences.py +33 -0
- numericalanalysisiiser/fixed_point.py +44 -0
- numericalanalysisiiser/gauss_legendre.py +27 -0
- numericalanalysisiiser/gauss_seidel.py +42 -0
- numericalanalysisiiser/hermite.py +39 -0
- numericalanalysisiiser/jacobi.py +42 -0
- numericalanalysisiiser/lagrange.py +28 -0
- numericalanalysisiiser/natural_cubic_spline.py +48 -0
- numericalanalysisiiser/newton.py +36 -0
- numericalanalysisiiser/newton_interpolation.py +51 -0
- numericalanalysisiiser/richardson.py +68 -0
- numericalanalysisiiser/romberg.py +30 -0
- numericalanalysisiiser/secant.py +42 -0
- numericalanalysisiiser/simpson.py +38 -0
- numericalanalysisiiser/sor.py +42 -0
- numericalanalysisiiser/trapezoid.py +33 -0
- numericalanalysisiiser/tridiagonal_matrix.py +13 -0
- numericalanalysisiiser-0.1.0.dist-info/METADATA +622 -0
- numericalanalysisiiser-0.1.0.dist-info/RECORD +25 -0
- numericalanalysisiiser-0.1.0.dist-info/WHEEL +5 -0
- numericalanalysisiiser-0.1.0.dist-info/licenses/LICENSE +21 -0
- numericalanalysisiiser-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Numerical analysis methods that return pandas DataFrames.
|
|
2
|
+
|
|
3
|
+
from numericalanalysis import bisection, richardson, jacobi, lagrange
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
__version__ = "0.1.0"
|
|
7
|
+
|
|
8
|
+
from .lagrange import lagrange
|
|
9
|
+
from .newton_interpolation import newton_interpolation, divided_differences
|
|
10
|
+
from .hermite import hermite
|
|
11
|
+
from .natural_cubic_spline import natural_cubic_spline
|
|
12
|
+
from .finite_differences import finite_difference
|
|
13
|
+
from .richardson import richardson, richardson_triangle
|
|
14
|
+
from .trapezoid import trapezoid, composite_trapezoid
|
|
15
|
+
from .simpson import simpson, composite_simpson
|
|
16
|
+
from .romberg import romberg
|
|
17
|
+
from .gauss_legendre import gauss_legendre
|
|
18
|
+
from .bisection import bisection
|
|
19
|
+
from .fixed_point import fixed_point
|
|
20
|
+
from .newton import newton
|
|
21
|
+
from .secant import secant
|
|
22
|
+
from .jacobi import jacobi
|
|
23
|
+
from .gauss_seidel import gauss_seidel
|
|
24
|
+
from .sor import sor
|
|
25
|
+
from .tridiagonal_matrix import tridiagonal_matrix
|
|
26
|
+
from .euler import euler
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
"lagrange", "newton_interpolation", "divided_differences", "hermite", "natural_cubic_spline",
|
|
30
|
+
"finite_difference", "richardson", "richardson_triangle",
|
|
31
|
+
"trapezoid", "composite_trapezoid", "simpson", "composite_simpson", "romberg", "gauss_legendre",
|
|
32
|
+
"bisection", "fixed_point", "newton", "secant",
|
|
33
|
+
"jacobi", "gauss_seidel", "sor", "tridiagonal_matrix",
|
|
34
|
+
"euler",
|
|
35
|
+
]
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pandas as pd
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def bisection(f, a, b, tol=1e-10, max_iter=1000, p_star=None):
|
|
6
|
+
"""Bisection method for f(x) = 0 on [a, b]; stops when (b - a) / 2 < tol.
|
|
7
|
+
|
|
8
|
+
p_star : optional exact root, adds error
|
|
9
|
+
|
|
10
|
+
Returns a DataFrame, one row per iteration: n, a, b, p, f(p), b-a, bound, f_evals [, error]
|
|
11
|
+
bound = (b0 - a0) / 2^(n+1). The root is the last p.
|
|
12
|
+
"""
|
|
13
|
+
fa = f(a)
|
|
14
|
+
if fa * f(b) >= 0:
|
|
15
|
+
raise ValueError("f(a) and f(b) must have opposite signs")
|
|
16
|
+
|
|
17
|
+
a0, b0 = a, b
|
|
18
|
+
evals = 2
|
|
19
|
+
rows = []
|
|
20
|
+
|
|
21
|
+
for n in range(max_iter):
|
|
22
|
+
p = (a + b) / 2
|
|
23
|
+
fp = f(p)
|
|
24
|
+
evals += 1
|
|
25
|
+
rows.append([n, a, b, p, fp, b - a, (b0 - a0) / 2 ** (n + 1), evals])
|
|
26
|
+
|
|
27
|
+
if fp == 0 or (b - a) / 2 < tol:
|
|
28
|
+
break
|
|
29
|
+
if fa * fp < 0:
|
|
30
|
+
b = p
|
|
31
|
+
else:
|
|
32
|
+
a = p
|
|
33
|
+
fa = fp
|
|
34
|
+
|
|
35
|
+
table = pd.DataFrame(rows, columns=["n", "a", "b", "p", "f(p)", "b-a", "bound", "f_evals"])
|
|
36
|
+
if p_star is not None:
|
|
37
|
+
table["error"] = np.abs(table["p"] - p_star)
|
|
38
|
+
return table
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pandas as pd
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def euler(f, t0, T, y0, h, exact=None):
|
|
6
|
+
"""Explicit Euler method for y' = f(t, y), y(t0) = y0 on [t0, T].
|
|
7
|
+
|
|
8
|
+
h : step size, or a list of step sizes
|
|
9
|
+
exact : optional exact solution y(t), adds exact and error
|
|
10
|
+
|
|
11
|
+
Returns a DataFrame (the step sizes stacked): h, t, y, stable [, exact, error]
|
|
12
|
+
stable is False when |y| exceeds 1e10; y is then nan from that step on.
|
|
13
|
+
"""
|
|
14
|
+
tables = []
|
|
15
|
+
|
|
16
|
+
for step in np.atleast_1d(h):
|
|
17
|
+
N = int(round((T - t0) / step))
|
|
18
|
+
if abs(N * step - (T - t0)) > 1e-9:
|
|
19
|
+
raise ValueError("T - t0 must be a multiple of h")
|
|
20
|
+
|
|
21
|
+
t = t0 + step * np.arange(N + 1)
|
|
22
|
+
y = np.full(N + 1, np.nan)
|
|
23
|
+
y[0] = y0
|
|
24
|
+
stable = True
|
|
25
|
+
|
|
26
|
+
for i in range(N):
|
|
27
|
+
y[i + 1] = y[i] + step * f(t[i], y[i])
|
|
28
|
+
if not abs(y[i + 1]) < 1e10:
|
|
29
|
+
stable = False
|
|
30
|
+
y[i + 1:] = np.nan
|
|
31
|
+
break
|
|
32
|
+
|
|
33
|
+
table = pd.DataFrame({"h": step, "t": t, "y": y, "stable": stable})
|
|
34
|
+
if exact is not None:
|
|
35
|
+
table["exact"] = [exact(ti) for ti in t]
|
|
36
|
+
table["error"] = (table["exact"] - table["y"]).abs()
|
|
37
|
+
tables.append(table)
|
|
38
|
+
|
|
39
|
+
return pd.concat(tables, ignore_index=True)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pandas as pd
|
|
3
|
+
|
|
4
|
+
FORMULAS = {
|
|
5
|
+
"forward": lambda f, x, h: (f(x + h) - f(x)) / h,
|
|
6
|
+
"backward": lambda f, x, h: (f(x) - f(x - h)) / h,
|
|
7
|
+
"central": lambda f, x, h: (f(x + h) - f(x - h)) / (2 * h),
|
|
8
|
+
"second": lambda f, x, h: (f(x + h) - 2 * f(x) + f(x - h)) / h**2,
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def finite_difference(f, x, h, exact=None, methods=("forward", "backward", "central")):
|
|
13
|
+
"""Finite difference approximation of f'(x) ("second": f''(x)).
|
|
14
|
+
|
|
15
|
+
h : a step size or a list of step sizes
|
|
16
|
+
exact : optional true derivative, adds <method>_error and <method>_order
|
|
17
|
+
methods : any of "forward", "backward", "central", "second"
|
|
18
|
+
|
|
19
|
+
Returns a DataFrame: h, <method> [, <method>_error, <method>_order]
|
|
20
|
+
"""
|
|
21
|
+
h = np.atleast_1d(np.asarray(h, dtype=float))
|
|
22
|
+
table = pd.DataFrame({"h": h})
|
|
23
|
+
|
|
24
|
+
for method in methods:
|
|
25
|
+
table[method] = [FORMULAS[method](f, x, step) for step in h]
|
|
26
|
+
if exact is not None:
|
|
27
|
+
error = np.abs(table[method].to_numpy() - exact)
|
|
28
|
+
with np.errstate(divide="ignore", invalid="ignore"):
|
|
29
|
+
order = np.log(error[:-1] / error[1:]) / np.log(h[:-1] / h[1:])
|
|
30
|
+
table[method + "_error"] = error
|
|
31
|
+
table[method + "_order"] = np.append(order, np.nan)
|
|
32
|
+
|
|
33
|
+
return table
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pandas as pd
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def fixed_point(g, p0, tol=1e-10, max_iter=1000, f=None, p_star=None):
|
|
6
|
+
"""Fixed point iteration p_(n+1) = g(p_n); stops when |p_(n+1) - p_n| < tol.
|
|
7
|
+
|
|
8
|
+
f : optional function f(x) = 0, adds the residual f(p)
|
|
9
|
+
p_star : optional exact fixed point, adds error
|
|
10
|
+
|
|
11
|
+
Returns a DataFrame, one row per iterate: n, p, g(p), step, [f(p)], f_evals [, error]
|
|
12
|
+
The last row is the final iterate. The table stops early if g leaves the real numbers or
|
|
13
|
+
the values exceed 1e12.
|
|
14
|
+
"""
|
|
15
|
+
p = p0
|
|
16
|
+
rows = []
|
|
17
|
+
|
|
18
|
+
for n in range(max_iter):
|
|
19
|
+
try:
|
|
20
|
+
p_next = g(p)
|
|
21
|
+
if isinstance(p_next, complex) or abs(p_next) > 1e12:
|
|
22
|
+
break
|
|
23
|
+
except (ValueError, OverflowError, ZeroDivisionError):
|
|
24
|
+
break
|
|
25
|
+
|
|
26
|
+
row = [n, p, p_next, abs(p_next - p)]
|
|
27
|
+
if f is not None:
|
|
28
|
+
row.append(f(p))
|
|
29
|
+
rows.append(row + [n + 1])
|
|
30
|
+
|
|
31
|
+
p = p_next
|
|
32
|
+
if rows[-1][3] < tol:
|
|
33
|
+
break
|
|
34
|
+
|
|
35
|
+
final = [len(rows), p, np.nan, np.nan]
|
|
36
|
+
if f is not None:
|
|
37
|
+
final.append(f(p))
|
|
38
|
+
rows.append(final + [len(rows)])
|
|
39
|
+
|
|
40
|
+
columns = ["n", "p", "g(p)", "step"] + (["f(p)"] if f is not None else []) + ["f_evals"]
|
|
41
|
+
table = pd.DataFrame(rows, columns=columns)
|
|
42
|
+
if p_star is not None:
|
|
43
|
+
table["error"] = np.abs(table["p"] - p_star)
|
|
44
|
+
return table
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pandas as pd
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def gauss_legendre(f, a, b, n=2, exact=None):
|
|
6
|
+
"""Gauss-Legendre quadrature for the integral of f over [a, b].
|
|
7
|
+
|
|
8
|
+
n : number of Gauss points, or a list of them
|
|
9
|
+
exact : optional true value, adds error
|
|
10
|
+
|
|
11
|
+
Returns a DataFrame: n, value [, error]
|
|
12
|
+
"""
|
|
13
|
+
n = np.atleast_1d(n)
|
|
14
|
+
values = []
|
|
15
|
+
|
|
16
|
+
for k in n:
|
|
17
|
+
points, weights = np.polynomial.legendre.leggauss(int(k))
|
|
18
|
+
total = 0
|
|
19
|
+
for i in range(k):
|
|
20
|
+
x = (a + b) / 2 + ((b - a) / 2) * points[i]
|
|
21
|
+
total += weights[i] * f(x)
|
|
22
|
+
values.append(((b - a) / 2) * total)
|
|
23
|
+
|
|
24
|
+
table = pd.DataFrame({"n": n, "value": values})
|
|
25
|
+
if exact is not None:
|
|
26
|
+
table["error"] = np.abs(table["value"].to_numpy() - exact)
|
|
27
|
+
return table
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pandas as pd
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def gauss_seidel(A, b, x0=None, tol=1e-6, max_iter=100):
|
|
6
|
+
"""Gauss-Seidel iteration for A x = b; stops when max|x_k - x_(k-1)| < tol.
|
|
7
|
+
|
|
8
|
+
x0 : starting vector (zeros by default)
|
|
9
|
+
|
|
10
|
+
Returns a DataFrame, one row per iteration (k = 0 is the starting vector):
|
|
11
|
+
k, step, residual, q, x1, x2, ...
|
|
12
|
+
step = max|x_k - x_(k-1)|, residual = max|b - A x_k|, q = residual_k / residual_(k-1).
|
|
13
|
+
The solution is the last row of x1, x2, ...
|
|
14
|
+
"""
|
|
15
|
+
A = np.array(A, dtype=float)
|
|
16
|
+
b = np.array(b, dtype=float)
|
|
17
|
+
n = len(b)
|
|
18
|
+
x = np.zeros(n) if x0 is None else np.array(x0, dtype=float)
|
|
19
|
+
|
|
20
|
+
xs = [x]
|
|
21
|
+
step = [np.nan]
|
|
22
|
+
residual = [np.max(np.abs(b - A @ x))]
|
|
23
|
+
|
|
24
|
+
for k in range(1, max_iter + 1):
|
|
25
|
+
x_new = x.copy()
|
|
26
|
+
for i in range(n):
|
|
27
|
+
sum_off_diag = 0.0
|
|
28
|
+
for j in range(n):
|
|
29
|
+
if i != j:
|
|
30
|
+
sum_off_diag += A[i, j] * x_new[j]
|
|
31
|
+
x_new[i] = (b[i] - sum_off_diag) / A[i, i]
|
|
32
|
+
|
|
33
|
+
step.append(np.max(np.abs(x_new - x)))
|
|
34
|
+
x = x_new
|
|
35
|
+
xs.append(x)
|
|
36
|
+
residual.append(np.max(np.abs(b - A @ x)))
|
|
37
|
+
if step[-1] < tol:
|
|
38
|
+
break
|
|
39
|
+
|
|
40
|
+
table = pd.DataFrame({"k": range(len(xs)), "step": step, "residual": residual})
|
|
41
|
+
table["q"] = table["residual"] / table["residual"].shift(1)
|
|
42
|
+
return pd.concat([table, pd.DataFrame(xs, columns=[f"x{i + 1}" for i in range(n)])], axis=1)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pandas as pd
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def hermite(x, y, dy, alpha, exact=None):
|
|
6
|
+
"""Hermite polynomial matching the values y and slopes dy at the nodes x, evaluated at alpha.
|
|
7
|
+
|
|
8
|
+
alpha : a number or a list of points
|
|
9
|
+
exact : optional function f(x), adds the columns exact and error
|
|
10
|
+
|
|
11
|
+
Returns a DataFrame: alpha, p [, exact, error]
|
|
12
|
+
"""
|
|
13
|
+
alpha = np.atleast_1d(np.asarray(alpha, dtype=float))
|
|
14
|
+
n = len(x)
|
|
15
|
+
|
|
16
|
+
a = np.repeat(x, 2)
|
|
17
|
+
M = np.zeros((2 * n, 2 * n))
|
|
18
|
+
M[:, 0] = np.repeat(y, 2)
|
|
19
|
+
|
|
20
|
+
for i in range(1, 2 * n):
|
|
21
|
+
if a[i] == a[i - 1]:
|
|
22
|
+
M[i, 1] = dy[i // 2]
|
|
23
|
+
else:
|
|
24
|
+
M[i, 1] = (M[i, 0] - M[i - 1, 0]) / (a[i] - a[i - 1])
|
|
25
|
+
|
|
26
|
+
for j in range(2, 2 * n):
|
|
27
|
+
for i in range(j, 2 * n):
|
|
28
|
+
M[i, j] = (M[i, j - 1] - M[i - 1, j - 1]) / (a[i] - a[i - j])
|
|
29
|
+
|
|
30
|
+
c = np.diag(M)
|
|
31
|
+
p = np.zeros(len(alpha))
|
|
32
|
+
for k in range(2 * n - 1, -1, -1):
|
|
33
|
+
p = c[k] + (alpha - a[k]) * p
|
|
34
|
+
|
|
35
|
+
table = pd.DataFrame({"alpha": alpha, "p": p})
|
|
36
|
+
if exact is not None:
|
|
37
|
+
table["exact"] = [exact(a) for a in alpha]
|
|
38
|
+
table["error"] = (table["exact"] - table["p"]).abs()
|
|
39
|
+
return table
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pandas as pd
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def jacobi(A, b, x0=None, tol=1e-6, max_iter=100):
|
|
6
|
+
"""Jacobi iteration for A x = b; stops when max|x_k - x_(k-1)| < tol.
|
|
7
|
+
|
|
8
|
+
x0 : starting vector (zeros by default)
|
|
9
|
+
|
|
10
|
+
Returns a DataFrame, one row per iteration (k = 0 is the starting vector):
|
|
11
|
+
k, step, residual, q, x1, x2, ...
|
|
12
|
+
step = max|x_k - x_(k-1)|, residual = max|b - A x_k|, q = residual_k / residual_(k-1).
|
|
13
|
+
The solution is the last row of x1, x2, ...
|
|
14
|
+
"""
|
|
15
|
+
A = np.array(A, dtype=float)
|
|
16
|
+
b = np.array(b, dtype=float)
|
|
17
|
+
n = len(b)
|
|
18
|
+
x = np.zeros(n) if x0 is None else np.array(x0, dtype=float)
|
|
19
|
+
|
|
20
|
+
xs = [x]
|
|
21
|
+
step = [np.nan]
|
|
22
|
+
residual = [np.max(np.abs(b - A @ x))]
|
|
23
|
+
|
|
24
|
+
for k in range(1, max_iter + 1):
|
|
25
|
+
x_new = np.zeros(n)
|
|
26
|
+
for i in range(n):
|
|
27
|
+
sum_off_diag = 0.0
|
|
28
|
+
for j in range(n):
|
|
29
|
+
if i != j:
|
|
30
|
+
sum_off_diag += A[i, j] * x[j]
|
|
31
|
+
x_new[i] = (b[i] - sum_off_diag) / A[i, i]
|
|
32
|
+
|
|
33
|
+
step.append(np.max(np.abs(x_new - x)))
|
|
34
|
+
x = x_new
|
|
35
|
+
xs.append(x)
|
|
36
|
+
residual.append(np.max(np.abs(b - A @ x)))
|
|
37
|
+
if step[-1] < tol:
|
|
38
|
+
break
|
|
39
|
+
|
|
40
|
+
table = pd.DataFrame({"k": range(len(xs)), "step": step, "residual": residual})
|
|
41
|
+
table["q"] = table["residual"] / table["residual"].shift(1)
|
|
42
|
+
return pd.concat([table, pd.DataFrame(xs, columns=[f"x{i + 1}" for i in range(n)])], axis=1)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pandas as pd
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def lagrange(x, y, alpha, exact=None):
|
|
6
|
+
"""Lagrange polynomial through the points (x, y), evaluated at alpha.
|
|
7
|
+
|
|
8
|
+
alpha : a number or a list of points
|
|
9
|
+
exact : optional function f(x), adds the columns exact and error
|
|
10
|
+
|
|
11
|
+
Returns a DataFrame: alpha, p [, exact, error]
|
|
12
|
+
"""
|
|
13
|
+
alpha = np.atleast_1d(np.asarray(alpha, dtype=float))
|
|
14
|
+
n = len(x)
|
|
15
|
+
p = np.zeros(len(alpha))
|
|
16
|
+
|
|
17
|
+
for i in range(n):
|
|
18
|
+
l_i = np.ones(len(alpha))
|
|
19
|
+
for j in range(n):
|
|
20
|
+
if j != i:
|
|
21
|
+
l_i *= (alpha - x[j]) / (x[i] - x[j])
|
|
22
|
+
p += y[i] * l_i
|
|
23
|
+
|
|
24
|
+
table = pd.DataFrame({"alpha": alpha, "p": p})
|
|
25
|
+
if exact is not None:
|
|
26
|
+
table["exact"] = [exact(a) for a in alpha]
|
|
27
|
+
table["error"] = (table["exact"] - table["p"]).abs()
|
|
28
|
+
return table
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pandas as pd
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def natural_cubic_spline(x, y, alpha, exact=None):
|
|
6
|
+
"""Natural cubic spline through the points (x, y), evaluated at alpha.
|
|
7
|
+
|
|
8
|
+
alpha : a number or a list of points
|
|
9
|
+
exact : optional function f(x), adds the columns exact and error
|
|
10
|
+
|
|
11
|
+
Returns a DataFrame: alpha, p [, exact, error]
|
|
12
|
+
"""
|
|
13
|
+
x = np.array(x, dtype=float)
|
|
14
|
+
y = np.array(y, dtype=float)
|
|
15
|
+
alpha = np.atleast_1d(np.asarray(alpha, dtype=float))
|
|
16
|
+
n = len(x) - 1
|
|
17
|
+
h = np.diff(x)
|
|
18
|
+
|
|
19
|
+
A = np.zeros((n + 1, n + 1))
|
|
20
|
+
b = np.zeros(n + 1)
|
|
21
|
+
A[0, 0] = 1.0
|
|
22
|
+
A[n, n] = 1.0
|
|
23
|
+
|
|
24
|
+
for i in range(1, n):
|
|
25
|
+
A[i, i - 1] = h[i - 1]
|
|
26
|
+
A[i, i] = 2.0 * (h[i - 1] + h[i])
|
|
27
|
+
A[i, i + 1] = h[i]
|
|
28
|
+
b[i] = 6.0 * ((y[i + 1] - y[i]) / h[i] - (y[i] - y[i - 1]) / h[i - 1])
|
|
29
|
+
|
|
30
|
+
M = np.linalg.solve(A, b)
|
|
31
|
+
|
|
32
|
+
p = np.zeros(len(alpha))
|
|
33
|
+
for idx, t in enumerate(alpha):
|
|
34
|
+
i = int(np.clip(np.searchsorted(x, t) - 1, 0, n - 1))
|
|
35
|
+
dx_right = x[i + 1] - t
|
|
36
|
+
dx_left = t - x[i]
|
|
37
|
+
p[idx] = (
|
|
38
|
+
M[i] * dx_right**3 / (6.0 * h[i])
|
|
39
|
+
+ M[i + 1] * dx_left**3 / (6.0 * h[i])
|
|
40
|
+
+ (y[i] - M[i] * h[i]**2 / 6.0) * dx_right / h[i]
|
|
41
|
+
+ (y[i + 1] - M[i + 1] * h[i]**2 / 6.0) * dx_left / h[i]
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
table = pd.DataFrame({"alpha": alpha, "p": p})
|
|
45
|
+
if exact is not None:
|
|
46
|
+
table["exact"] = [exact(a) for a in alpha]
|
|
47
|
+
table["error"] = (table["exact"] - table["p"]).abs()
|
|
48
|
+
return table
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pandas as pd
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def newton(f, df, p0, tol=1e-12, max_iter=100, p_star=None):
|
|
6
|
+
"""Newton's method; stops when |p_(n+1) - p_n| < tol.
|
|
7
|
+
|
|
8
|
+
df : derivative f'(x)
|
|
9
|
+
p_star : optional exact root, adds error and q
|
|
10
|
+
|
|
11
|
+
Returns a DataFrame, one row per iterate: n, p, f(p), f'(p), step, f_evals, df_evals [, error, q]
|
|
12
|
+
The last row is the final iterate. q = log(e_(n+1)/e_n) / log(e_n/e_(n-1)) is the estimated order.
|
|
13
|
+
"""
|
|
14
|
+
p = p0
|
|
15
|
+
rows = []
|
|
16
|
+
|
|
17
|
+
for n in range(max_iter):
|
|
18
|
+
fp, dfp = f(p), df(p)
|
|
19
|
+
p_next = p - fp / dfp
|
|
20
|
+
rows.append([n, p, fp, dfp, abs(p_next - p), n + 1, n + 1])
|
|
21
|
+
p = p_next
|
|
22
|
+
if rows[-1][4] < tol:
|
|
23
|
+
break
|
|
24
|
+
|
|
25
|
+
rows.append([len(rows), p, f(p), np.nan, np.nan, len(rows), len(rows)])
|
|
26
|
+
table = pd.DataFrame(rows, columns=["n", "p", "f(p)", "f'(p)", "step", "f_evals", "df_evals"])
|
|
27
|
+
|
|
28
|
+
if p_star is not None:
|
|
29
|
+
error = np.abs(table["p"].to_numpy() - p_star)
|
|
30
|
+
e = np.where(error < 1e-15, np.nan, error) # round-off sized errors are ignored
|
|
31
|
+
q = np.full(len(e), np.nan)
|
|
32
|
+
with np.errstate(divide="ignore", invalid="ignore"):
|
|
33
|
+
q[1:-1] = np.log(e[2:] / e[1:-1]) / np.log(e[1:-1] / e[:-2])
|
|
34
|
+
table["error"] = error
|
|
35
|
+
table["q"] = q
|
|
36
|
+
return table
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pandas as pd
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def divided_differences(x, y):
|
|
6
|
+
"""Divided difference table of the points (x, y).
|
|
7
|
+
|
|
8
|
+
Returns a DataFrame: x, f[0], f[1], ... (the first row holds the Newton coefficients)
|
|
9
|
+
"""
|
|
10
|
+
n = len(x)
|
|
11
|
+
columns = [list(y)]
|
|
12
|
+
|
|
13
|
+
for j in range(1, n):
|
|
14
|
+
col = []
|
|
15
|
+
for i in range(n - j):
|
|
16
|
+
col.append((columns[j - 1][i + 1] - columns[j - 1][i]) / (x[i + j] - x[i]))
|
|
17
|
+
columns.append(col + [np.nan] * j)
|
|
18
|
+
|
|
19
|
+
table = pd.DataFrame({"x": list(x)})
|
|
20
|
+
for j in range(n):
|
|
21
|
+
table[f"f[{j}]"] = columns[j]
|
|
22
|
+
return table
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def newton_interpolation(x, y, alpha, exact=None):
|
|
26
|
+
"""Newton form of the interpolation polynomial through the points (x, y), evaluated at alpha.
|
|
27
|
+
|
|
28
|
+
alpha : a number or a list of points
|
|
29
|
+
exact : optional function f(x), adds the columns exact and error
|
|
30
|
+
|
|
31
|
+
Returns a DataFrame: alpha, p [, exact, error]
|
|
32
|
+
"""
|
|
33
|
+
alpha = np.atleast_1d(np.asarray(alpha, dtype=float))
|
|
34
|
+
n = len(x)
|
|
35
|
+
|
|
36
|
+
a = list(y)
|
|
37
|
+
for j in range(1, n):
|
|
38
|
+
for i in range(n - 1, j - 1, -1):
|
|
39
|
+
a[i] = (a[i] - a[i - 1]) / (x[i] - x[i - j])
|
|
40
|
+
|
|
41
|
+
p = a[0] * np.ones(len(alpha))
|
|
42
|
+
product = np.ones(len(alpha))
|
|
43
|
+
for i in range(1, n):
|
|
44
|
+
product = product * (alpha - x[i - 1])
|
|
45
|
+
p += a[i] * product
|
|
46
|
+
|
|
47
|
+
table = pd.DataFrame({"alpha": alpha, "p": p})
|
|
48
|
+
if exact is not None:
|
|
49
|
+
table["exact"] = [exact(a) for a in alpha]
|
|
50
|
+
table["error"] = (table["exact"] - table["p"]).abs()
|
|
51
|
+
return table
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pandas as pd
|
|
3
|
+
|
|
4
|
+
from .finite_differences import FORMULAS
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def richardson_triangle(first_column, p=2):
|
|
8
|
+
"""Richardson table as a list of rows: R[k][j] = R[k][j-1] + (R[k][j-1] - R[k-1][j-1]) / (2^(p*j) - 1).
|
|
9
|
+
|
|
10
|
+
first_column[k] is computed with step h / 2^k.
|
|
11
|
+
p = 2 for central differences and the trapezoidal rule, p = 1 for forward / backward differences.
|
|
12
|
+
"""
|
|
13
|
+
R = []
|
|
14
|
+
for k in range(len(first_column)):
|
|
15
|
+
row = [first_column[k]]
|
|
16
|
+
for j in range(1, k + 1):
|
|
17
|
+
row.append(row[j - 1] + (row[j - 1] - R[k - 1][j - 1]) / (2 ** (p * j) - 1))
|
|
18
|
+
R.append(row)
|
|
19
|
+
return R
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def richardson(f, x, h, levels=1, method="central", exact=None, layout="by_h"):
|
|
23
|
+
"""Richardson extrapolation of a finite difference formula at the point x.
|
|
24
|
+
|
|
25
|
+
method : "forward", "backward", "central" or "second"
|
|
26
|
+
exact : optional true derivative, adds R<j>_error and R<j>_order
|
|
27
|
+
layout : "by_h" h is a list of steps; R0 = D(h), R1 = (4 D(h/2) - D(h)) / 3, ...
|
|
28
|
+
"triangle" h is one step h0; row k uses h0 / 2^k and R[k][j] = R[k][j-1] + (R[k][j-1] - R[k-1][j-1]) / (4^j - 1)
|
|
29
|
+
|
|
30
|
+
Returns a DataFrame: h, R0, R1, ... (triangle: k, h, R0, R1, ...)
|
|
31
|
+
"""
|
|
32
|
+
D = lambda step: FORMULAS[method](f, x, step)
|
|
33
|
+
p = 1 if method in ("forward", "backward") else 2
|
|
34
|
+
|
|
35
|
+
if layout == "by_h":
|
|
36
|
+
h = np.atleast_1d(np.asarray(h, dtype=float))
|
|
37
|
+
rows = []
|
|
38
|
+
for step in h:
|
|
39
|
+
values = [D(step / 2**m) for m in range(levels + 1)]
|
|
40
|
+
row = [values[0]]
|
|
41
|
+
for j in range(1, levels + 1):
|
|
42
|
+
factor = 2 ** (p * j)
|
|
43
|
+
values = [(factor * values[m + 1] - values[m]) / (factor - 1) for m in range(len(values) - 1)]
|
|
44
|
+
row.append(values[0])
|
|
45
|
+
rows.append(row)
|
|
46
|
+
table = pd.DataFrame(rows, columns=[f"R{j}" for j in range(levels + 1)])
|
|
47
|
+
table.insert(0, "h", h)
|
|
48
|
+
elif layout == "triangle":
|
|
49
|
+
steps = [h / 2**k for k in range(levels + 1)]
|
|
50
|
+
table = pd.DataFrame(richardson_triangle([D(step) for step in steps], p))
|
|
51
|
+
table.columns = [f"R{j}" for j in range(levels + 1)]
|
|
52
|
+
table.insert(0, "h", steps)
|
|
53
|
+
table.insert(0, "k", range(levels + 1))
|
|
54
|
+
else:
|
|
55
|
+
raise ValueError("layout must be 'by_h' or 'triangle'")
|
|
56
|
+
|
|
57
|
+
if exact is not None:
|
|
58
|
+
for j in range(levels + 1):
|
|
59
|
+
error = np.abs(table[f"R{j}"].to_numpy() - exact)
|
|
60
|
+
with np.errstate(divide="ignore", invalid="ignore"):
|
|
61
|
+
if layout == "by_h":
|
|
62
|
+
order = np.append(np.log(error[:-1] / error[1:]) / np.log(h[:-1] / h[1:]), np.nan)
|
|
63
|
+
else:
|
|
64
|
+
order = np.append(np.nan, np.log2(error[:-1] / error[1:]))
|
|
65
|
+
table[f"R{j}_error"] = error
|
|
66
|
+
table[f"R{j}_order"] = order
|
|
67
|
+
|
|
68
|
+
return table
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
|
|
3
|
+
from .richardson import richardson_triangle
|
|
4
|
+
from .trapezoid import composite_trapezoid
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def romberg(f, a, b, levels=6, tol=None, exact=None):
|
|
8
|
+
"""Romberg integration of f over [a, b].
|
|
9
|
+
|
|
10
|
+
levels : number of rows, row k uses n = 2^k sub-intervals
|
|
11
|
+
tol : if given, rows are added until |R[k][k] - R[k-1][k-1]| < tol
|
|
12
|
+
exact : optional true value, adds the error of the diagonal entries
|
|
13
|
+
|
|
14
|
+
Returns a DataFrame: n, R0, R1, ... [, error]. The last entry of the last row is the answer.
|
|
15
|
+
"""
|
|
16
|
+
T = []
|
|
17
|
+
while True:
|
|
18
|
+
T.append(composite_trapezoid(f, a, b, 2 ** len(T)))
|
|
19
|
+
R = richardson_triangle(T)
|
|
20
|
+
if tol is None and len(T) >= levels:
|
|
21
|
+
break
|
|
22
|
+
if tol is not None and (len(T) >= 25 or (len(T) >= 2 and abs(R[-1][-1] - R[-2][-1]) < tol)):
|
|
23
|
+
break
|
|
24
|
+
|
|
25
|
+
table = pd.DataFrame(R)
|
|
26
|
+
table.columns = [f"R{j}" for j in range(len(R))]
|
|
27
|
+
table.insert(0, "n", [2**k for k in range(len(R))])
|
|
28
|
+
if exact is not None:
|
|
29
|
+
table["error"] = [abs(R[k][k] - exact) for k in range(len(R))]
|
|
30
|
+
return table
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pandas as pd
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def secant(f, p0, p1, tol=1e-12, max_iter=100, p_star=None):
|
|
6
|
+
"""Secant method; stops when |p_(n+1) - p_n| < tol.
|
|
7
|
+
|
|
8
|
+
p_star : optional exact root, adds error and q
|
|
9
|
+
|
|
10
|
+
Returns a DataFrame, one row per iterate: n, p_prev, p, f(p), p_next, step, f_evals [, error, q]
|
|
11
|
+
The last row is the final iterate. q = log(e_(n+1)/e_n) / log(e_n/e_(n-1)) is the estimated order.
|
|
12
|
+
"""
|
|
13
|
+
p_prev, p_curr = p0, p1
|
|
14
|
+
f_prev, f_curr = f(p0), f(p1)
|
|
15
|
+
evals = 2
|
|
16
|
+
rows = []
|
|
17
|
+
|
|
18
|
+
for n in range(1, max_iter + 1):
|
|
19
|
+
if f_curr == f_prev:
|
|
20
|
+
break
|
|
21
|
+
p_next = p_curr - f_curr * (p_curr - p_prev) / (f_curr - f_prev)
|
|
22
|
+
f_next = f(p_next)
|
|
23
|
+
evals += 1
|
|
24
|
+
rows.append([n, p_prev, p_curr, f_curr, p_next, abs(p_next - p_curr), evals])
|
|
25
|
+
|
|
26
|
+
p_prev, f_prev = p_curr, f_curr
|
|
27
|
+
p_curr, f_curr = p_next, f_next
|
|
28
|
+
if rows[-1][5] < tol:
|
|
29
|
+
break
|
|
30
|
+
|
|
31
|
+
rows.append([len(rows) + 1, p_prev, p_curr, f_curr, np.nan, np.nan, evals])
|
|
32
|
+
table = pd.DataFrame(rows, columns=["n", "p_prev", "p", "f(p)", "p_next", "step", "f_evals"])
|
|
33
|
+
|
|
34
|
+
if p_star is not None:
|
|
35
|
+
e_prev, e_curr, e_next = (np.abs(table[c].to_numpy() - p_star) for c in ("p_prev", "p", "p_next"))
|
|
36
|
+
mask = np.minimum(np.minimum(e_prev, e_curr), e_next) < 1e-15 # round-off sized errors are ignored
|
|
37
|
+
with np.errstate(divide="ignore", invalid="ignore"):
|
|
38
|
+
q = np.log(e_next / e_curr) / np.log(e_curr / e_prev)
|
|
39
|
+
q[mask] = np.nan
|
|
40
|
+
table["error"] = e_curr
|
|
41
|
+
table["q"] = q
|
|
42
|
+
return table
|