mathematicskit.numerical_analysis#
Root finding (bisection, Newton-Raphson, secant, fixed-point iteration;
hand-rolled for their per-iterate convergence history, cross-checked
against scipy.optimize) with convergence-order verification;
Lagrange and Newton divided-difference polynomial interpolation
(hand-rolled, cross-checked against scipy.interpolate); cubic spline
interpolation via scipy.interpolate.CubicSpline; Chebyshev
interpolation nodes (numpy.polynomial.chebyshev.chebpts2) and the
Runge phenomenon; least-squares polynomial regression via
numpy.linalg.lstsq; and shared error/stability-analysis utilities
(numpy.linalg.cond-based condition number, the hand-rolled Lebesgue
constant and empirical convergence order).
mathematicskit.numerical_analysis: root finding and polynomial approximation.
Root finding (bisection, Newton-Raphson, secant, fixed-point iteration,
hand-rolled to expose per-iterate convergence history, cross-checked
against scipy.optimize in tests) with convergence-order verification;
Lagrange and Newton divided-difference polynomial interpolation
(hand-rolled, cross-checked against scipy.interpolate in tests);
cubic spline interpolation (natural and clamped) built on
scipy.interpolate.CubicSpline; Chebyshev interpolation nodes
(numpy.polynomial.chebyshev.chebpts2) and the Runge phenomenon;
least-squares polynomial regression via numpy.linalg.lstsq;
Halley’s and Steffensen’s methods and Aitken’s delta-squared acceleration;
Hermite interpolation (scipy.interpolate.KroghInterpolator);
Bernstein polynomials, Padé approximants, and Remez minimax
approximation; Horner’s scheme and Wilkinson’s polynomial with root
condition numbers; Kahan’s compensated summation; and shared error/stability-analysis utilities (empirical convergence order,
the Lebesgue constant as an interpolation problem’s condition number,
and a general condition-number wrapper around numpy.linalg.cond).
- class mathematicskit.numerical_analysis.Bisection(f, a, b, tol=1e-08, max_iter=1000)[source]#
Bases:
IterativeRootFinderBisection method on a bracket
[a, b]withf(a) * f(b) < 0.Halves the bracket each iteration, keeping the half where the sign change persists; converges linearly (order 1) with error bound \(|x_n - \text{root}| \leq (b - a) / 2^{n+1}\). See Burden & Faires, Numerical Analysis, 10th ed., Ch. 2.1.
- Parameters:
f (
Callable[[float],float]) – Continuous functionf(x) -> float.a (
float) – Bracket endpoints withf(a)andf(b)of opposite sign.b (
float) – Bracket endpoints withf(a)andf(b)of opposite sign.tol (
float) – Stop once the bracket width is below tol.max_iter (
int) – Maximum number of bisections.
Examples
>>> result = Bisection(lambda x: x**2 - 2.0, 0.0, 2.0, tol=1e-10).solve() >>> round(result.root, 6) 1.414214 >>> result.converged True >>> # ``history`` holds exactly one midpoint per iteration performed. >>> len(result.history) == result.iterations True
- class mathematicskit.numerical_analysis.ChebyshevInterpolant(f, n, a=-1.0, b=1.0)[source]#
Bases:
InterpolantPolynomial interpolant of a function sampled at Chebyshev nodes.
Evaluated with the barycentric interpolation formula specialized to Chebyshev-of-the-second-kind nodes (weights \(w_k = (-1)^k \delta_k\), \(\delta_k = 1/2\) at the endpoints and 1 otherwise), which is both faster (\(O(n)\) per point after setup) and far better conditioned than the raw Lagrange form. See Trefethen, Approximation Theory and Approximation Practice, 2013, Ch. 5, eq. (5.3)-(5.4).
- Parameters:
Examples
>>> import numpy as np >>> p = ChebyshevInterpolant(np.sin, n=20, a=-3.0, b=3.0) >>> bool(abs(p.evaluate(1.0) - np.sin(1.0)) < 1e-10) True
- class mathematicskit.numerical_analysis.CubicSpline(x, y, boundary='natural', fpa=None, fpb=None)[source]#
Bases:
InterpolantPiecewise cubic spline through
(x_i, y_i), natural or clamped.On each subinterval \([x_i, x_{i+1}]\),
\[S_i(x) = a_i + b_i (x - x_i) + c_i (x - x_i)^2 + d_i (x - x_i)^3\]with \(S, S', S''\) continuous across nodes. A natural spline fixes \(S''(x_0) = S''(x_n) = 0\); a clamped spline instead matches prescribed end-slopes \(S'(x_0) = f'_a\), \(S'(x_n) = f'_b\). Delegates to
scipy.interpolate.CubicSplinewithbc_type="natural"orbc_type=((1, fpa), (1, fpb))respectively. See Burden & Faires, Numerical Analysis, 10th ed., Ch. 3.5, Algorithms 3.4-3.5.- Parameters:
x (array-like, shape (n + 1,)) – Interpolation nodes (strictly increasing) and values.
y (array-like, shape (n + 1,)) – Interpolation nodes (strictly increasing) and values.
boundary (
str) – Boundary-condition type.fpa (
float|None) – End-slopes \(S'(x_0)\), \(S'(x_n)\); required whenboundary="clamped".fpb (
float|None) – End-slopes \(S'(x_0)\), \(S'(x_n)\); required whenboundary="clamped".
Examples
>>> import numpy as np >>> x = np.array([0.0, 1.0, 2.0, 3.0]) >>> y = np.array([0.0, 1.0, 0.0, 1.0]) >>> spline = CubicSpline(x, y, boundary="natural") >>> # Interpolates exactly at the nodes themselves. >>> np.allclose([spline.evaluate(xi) for xi in x], y) True >>> # A straight line is reproduced exactly by a clamped spline whose >>> # end-slopes match the line's slope. >>> line = CubicSpline(x, 2.0 * x + 1.0, boundary="clamped", fpa=2.0, fpb=2.0) >>> round(float(line.evaluate(1.7)), 10) 4.4
- class mathematicskit.numerical_analysis.FixedPointIteration(g, x0, tol=1e-08, max_iter=1000)[source]#
Bases:
IterativeRootFinderFixed-point iteration: \(x_{n+1} = g(x_n)\), converging to a fixed point
x* = g(x*)(equivalently a root off(x) = g(x) - x).Converges linearly if
|g'(x*)| < 1in a neighborhood of the fixed point (the contraction condition); see Burden & Faires, Numerical Analysis, 10th ed., Ch. 2.2, Theorem 2.4 (Fixed-Point Theorem) and Corollary 2.5.- Parameters:
Examples
>>> # x* solves x = cos(x); g(x) = cos(x) is a contraction near x*. >>> import numpy as np >>> result = FixedPointIteration(np.cos, x0=0.5, tol=1e-10).solve() >>> bool(abs(result.root - np.cos(result.root)) < 1e-9) True
- class mathematicskit.numerical_analysis.Halley(f, fprime, fprime2, x0, tol=1e-08, max_iter=1000)[source]#
Bases:
IterativeRootFinderHalley’s method: a cubically convergent refinement of Newton’s method.
\[x_{n+1} = x_n - \frac{2 f(x_n) f'(x_n)}{2 f'(x_n)^2 - f(x_n) f''(x_n)}\]Equivalent to applying Newton’s method to \(f/\sqrt{|f'|}\), or to stepping to the root of the osculating hyperbola at \(x_n\). Converges with order 3 near a simple root. See E. Halley, “Methodus nova accurata & facilis inveniendi radices aequationum quarumcumque generaliter, sine praevia reductione,” Philosophical Transactions 18 (1694), 136-148; and T. R. Scavo and J. B. Thoo, “On the Geometry of Halley’s Method,” American Mathematical Monthly 102 (1995), 417-426. The same iteration is what
scipy.optimize.newton()runs when givenfprime2; it is hand-rolled here to expose the iterates.- Parameters:
f (
Callable[[float],float]) – Function and its first and second derivatives, allfloat -> float.fprime (
Callable[[float],float]) – Function and its first and second derivatives, allfloat -> float.fprime2 (
Callable[[float],float]) – Function and its first and second derivatives, allfloat -> float.x0 (
float) – Initial guess.tol (
float) – Stop once|x_{n+1} - x_n| < tol.max_iter (
int) – Maximum number of iterations.
Examples
>>> result = Halley(lambda x: x**2 - 2.0, lambda x: 2.0 * x, lambda x: 2.0, x0=1.0).solve() >>> round(result.root, 12) 1.414213562373 >>> result.iterations <= 4 True
- class mathematicskit.numerical_analysis.HermiteInterpolant(x, y, dydx)[source]#
Bases:
InterpolantHermite interpolating polynomial matching values and slopes.
The unique polynomial \(H\) of degree \(\le 2n + 1\) with \(H(x_i) = y_i\) and \(H'(x_i) = y'_i\) at \(n + 1\) distinct nodes. In Newton form it is the divided-difference interpolant on the doubled node list \(x_0, x_0, x_1, x_1, \ldots\), with each repeated first difference \(f[x_i, x_i]\) replaced by \(y'_i\). The error is
\[f(x) - H(x) = \frac{f^{(2n+2)}(\xi)}{(2n+2)!} \prod_{i=0}^{n} (x - x_i)^2 .\]See C. Hermite, “Sur la formule d’interpolation de Lagrange,” Journal für die reine und angewandte Mathematik 84 (1878), 70-79; Burden & Faires, Numerical Analysis, 10th ed., Ch. 3.4. Built on
scipy.interpolate.KroghInterpolator, which accepts repeated nodes as derivative conditions (F. T. Krogh, Mathematics of Computation 24 (1970), 185-190).- Parameters:
x (array-like, shape (n + 1,)) – Distinct interpolation nodes and values.
y (array-like, shape (n + 1,)) – Distinct interpolation nodes and values.
dydx (array-like, shape (n + 1,)) – First derivatives at the nodes.
Examples
>>> # Two nodes with slopes determine a cubic: recover x^3 exactly. >>> H = HermiteInterpolant([0.0, 1.0], [0.0, 1.0], dydx=[0.0, 3.0]) >>> round(H(0.5), 12) 0.125 >>> round(float(H.derivative(1.0)), 12) 3.0
- class mathematicskit.numerical_analysis.HornerResult(value, derivative, quotient)[source]#
Bases:
objectContainer for one step of Horner’s scheme: \(p(x_0)\) and the deflated quotient \(q(x)\) with \(p(x) = (x - x_0)\,q(x) + p(x_0)\).
- class mathematicskit.numerical_analysis.Interpolant(x, y)[source]#
Bases:
ABCCommon base for algorithms that build an approximation to tabulated data
(x_i, y_i)and can be evaluated at new points.- Parameters:
- class mathematicskit.numerical_analysis.IterativeRootFinder(tol=1e-08, max_iter=1000)[source]#
Bases:
ABCCommon base for scalar root-finding algorithms.
Subclasses store whatever state they need in
__init__(a function, a bracket or initial guess(es)) and implementsolve().- Parameters:
- class mathematicskit.numerical_analysis.LagrangeInterpolant(x, y)[source]#
Bases:
InterpolantLagrange-form interpolating polynomial through
(x_i, y_i).\[p(x) = \sum_{i=0}^{n} y_i L_i(x), \qquad L_i(x) = \prod_{j \neq i} \frac{x - x_j}{x_i - x_j}\]Evaluated directly from this basis-function sum (\(O(n^2)\) per evaluation point); see Burden & Faires, Numerical Analysis, 10th ed., Ch. 3.1, Theorem 3.2.
- Parameters:
x (array-like, shape (n + 1,)) – Interpolation nodes (distinct) and values.
y (array-like, shape (n + 1,)) – Interpolation nodes (distinct) and values.
Examples
>>> import numpy as np >>> # Exact for polynomials up to the interpolation degree. >>> x = np.array([0.0, 1.0, 2.0, 3.0]) >>> y = x**3 - 2.0 * x + 1.0 >>> p = LagrangeInterpolant(x, y) >>> round(float(p.evaluate(1.5)), 10) 1.375 >>> round(1.5**3 - 2.0 * 1.5 + 1.0, 10) 1.375
- class mathematicskit.numerical_analysis.MinimaxResult(coefficients, max_error, reference, iterations, converged)[source]#
Bases:
objectContainer for the output of the Remez minimax approximation.
- Parameters:
- class mathematicskit.numerical_analysis.NewtonDividedDifference(x, y)[source]#
Bases:
InterpolantNewton divided-difference form of the interpolating polynomial.
\[p(x) = f[x_0] + f[x_0,x_1](x-x_0) + f[x_0,x_1,x_2](x-x_0)(x-x_1) + \dots\]built from the divided-difference table \(f[x_i,\dots,x_{i+k}] = \dfrac{f[x_{i+1},\dots,x_{i+k}] - f[x_i,\dots,x_{i+k-1}]}{x_{i+k}-x_i}\) and evaluated with nested (Horner-like) multiplication, \(O(n)\) per point after an \(O(n^2)\) table build – cheaper to extend with a new node than rebuilding a Lagrange form from scratch. See Burden & Faires, Numerical Analysis, 10th ed., Ch. 3.3.
- Parameters:
x (array-like, shape (n + 1,)) – Interpolation nodes (distinct) and values.
y (array-like, shape (n + 1,)) – Interpolation nodes (distinct) and values.
Examples
>>> import numpy as np >>> x = np.array([0.0, 1.0, 2.0, 3.0]) >>> y = x**3 - 2.0 * x + 1.0 >>> p = NewtonDividedDifference(x, y) >>> round(float(p.evaluate(1.5)), 10) 1.375
- class mathematicskit.numerical_analysis.NewtonRaphson(f, fprime, x0, tol=1e-08, max_iter=1000)[source]#
Bases:
IterativeRootFinderNewton-Raphson method: \(x_{n+1} = x_n - f(x_n) / f'(x_n)\).
Converges quadratically (order 2) near a simple root, provided \(f'\) doesn’t vanish there. See Burden & Faires, Numerical Analysis, 10th ed., Ch. 2.3, Theorem 2.9.
- Parameters:
Examples
>>> result = NewtonRaphson(lambda x: x**2 - 2.0, lambda x: 2.0 * x, x0=1.0).solve() >>> round(result.root, 10) 1.4142135624
- class mathematicskit.numerical_analysis.PadeApproximant(taylor_coefficients, m, n)[source]#
Bases:
objectPadé approximant \([m/n]\) of a power series: the rational function \(p(x)/q(x)\), \(\deg p \le m\), \(\deg q \le n\), \(q(0) = 1\), whose Taylor series agrees with \(f\) through \(x^{m+n}\).
With Taylor coefficients \(c_i\) (and \(c_i = 0\) for \(i < 0\)), the denominator solves the \(n \times n\) linear system \(\sum_{j=0}^{n} q_j c_{m+k-j} = 0\), \(k = 1, \ldots, n\), and then \(p_i = \sum_{j=0}^{\min(i, n)} q_j c_{i-j}\). See H. Padé, “Sur la représentation approchée d’une fonction par des fractions rationnelles,” Annales scientifiques de l’École Normale Supérieure (3) 9 (1892), supplement, 3-93; G. A. Baker and P. Graves-Morris, Padé Approximants, 2nd ed. (Cambridge University Press, 1996), Ch. 1.
- Parameters:
- numerator, denominator
Coefficients of \(p\) and \(q\), lowest order first.
- Type:
ndarray
Examples
>>> import math >>> c = [1.0 / math.factorial(k) for k in range(3)] >>> r = PadeApproximant(c, 1, 1) # exp(x) ~ (1 + x/2) / (1 - x/2) >>> r.numerator.tolist(), r.denominator.tolist() ([1.0, 0.5], [1.0, -0.5]) >>> round(r(1.0), 10) 3.0
- class mathematicskit.numerical_analysis.PolynomialRegression(x, y, degree)[source]#
Bases:
objectLeast-squares polynomial fit of degree degree to
(x_i, y_i).- Parameters:
Examples
>>> import numpy as np >>> rng = np.random.default_rng(0) >>> x = np.linspace(-1.0, 1.0, 50) >>> y = 3.0 * x**2 - 2.0 * x + 1.0 # noiseless: fit should be essentially exact >>> model = PolynomialRegression(x, y, degree=2) >>> result = model.fit() >>> np.allclose(result.coefficients, [3.0, -2.0, 1.0], atol=1e-8) True >>> round(result.r_squared, 6) 1.0
- predict(x_new)[source]#
Evaluate the fitted polynomial at new points.
Fits the model on first use and caches the coefficients, so calling
fit()beforehand is optional; the cache is never invalidated, since aPolynomialRegressioninstance’s data and degree are fixed at construction.
- class mathematicskit.numerical_analysis.RegressionResult(coefficients, fitted_values, residuals, r_squared, adjusted_r_squared, condition_number=None)[source]#
Bases:
objectContainer for the output of a least-squares regression fit.
- Parameters:
- coefficients: ndarray#
Fitted coefficients, highest power first (
numpy.polyvalconvention).- Type:
ndarray, shape (degree + 1,)
- class mathematicskit.numerical_analysis.RootResult(root, converged, iterations, history, method='', extra=<factory>)[source]#
Bases:
objectContainer for the output of an
IterativeRootFinder.- Parameters:
- extra: dict#
Free-form slot for method-specific diagnostics (e.g. the final bracket for bisection).
- Type:
- class mathematicskit.numerical_analysis.Secant(f, x0, x1, tol=1e-08, max_iter=1000)[source]#
Bases:
IterativeRootFinderSecant method: Newton’s method with the derivative replaced by a finite-difference slope through the two most recent iterates.
\(x_{n+1} = x_n - f(x_n) \dfrac{x_n - x_{n-1}}{f(x_n) - f(x_{n-1})}\). Converges superlinearly with order \((1+\sqrt5)/2 \approx 1.618\) (the golden ratio) near a simple root – see Burden & Faires, Numerical Analysis, 10th ed., Ch. 2.3, Theorem 2.10 – without requiring an analytic derivative.
- Parameters:
Examples
>>> result = Secant(lambda x: x**2 - 2.0, x0=1.0, x1=2.0).solve() >>> round(result.root, 8) 1.41421356
- class mathematicskit.numerical_analysis.Steffensen(g, x0, tol=1e-08, max_iter=1000)[source]#
Bases:
IterativeRootFinderSteffensen’s method: fixed-point iteration accelerated by Aitken’s \(\Delta^2\) process at every step.
From \(x_n\), compute \(g(x_n)\) and \(g(g(x_n))\), then jump to their Aitken extrapolation
\[x_{n+1} = x_n - \frac{\bigl(g(x_n) - x_n\bigr)^2}{g(g(x_n)) - 2 g(x_n) + x_n}.\]Converges quadratically to a fixed point with \(g'(x^*) \neq 1\), even where plain iteration converges only linearly (or diverges), and without any derivative. See J. F. Steffensen, “Remarks on iteration,” Skandinavisk Aktuarietidskrift 16 (1933), 64-72; Burden & Faires, Numerical Analysis, 10th ed., Ch. 2.5, Algorithm 2.6. The same iteration is
scipy.optimize.fixed_point()withmethod="del2"; it is hand-rolled here to expose the iterates.- Parameters:
Examples
>>> import numpy as np >>> result = Steffensen(np.cos, x0=0.5, tol=1e-12).solve() >>> round(result.root, 10) 0.7390851332 >>> result.iterations < 6 True
- mathematicskit.numerical_analysis.aitken_delta_squared(sequence)[source]#
Apply Aitken’s \(\Delta^2\) process to a sequence.
\[\hat a_n = a_n - \frac{(\Delta a_n)^2}{\Delta^2 a_n} = a_n - \frac{(a_{n+1} - a_n)^2}{a_{n+2} - 2 a_{n+1} + a_n}\]The transform is exact for sequences of the form \(a_n = L + c\,r^n\) (\(r \neq 1\)), and for any linearly convergent sequence it converges to the limit faster than the original. Where \(\Delta^2 a_n = 0\), \(a_{n+2}\) is returned.
- Parameters:
sequence (array-like, shape (n,)) – Terms \(a_0, \ldots, a_{n-1}\), with
n >= 3.- Return type:
- Returns:
ndarray, shape (n - 2,) – The accelerated sequence \(\hat a_0, \ldots, \hat a_{n-3}\).
Examples
>>> import numpy as np >>> # a_n = 2 + 3 * 0.5**n: one Aitken step recovers the limit exactly. >>> a = 2.0 + 3.0 * 0.5 ** np.arange(6) >>> np.allclose(aitken_delta_squared(a), 2.0) True
- mathematicskit.numerical_analysis.bernstein_polynomial(f, n, x)[source]#
Evaluate the degree-
nBernstein polynomial offon \([0, 1]\).\[(B_n f)(x) = \sum_{k=0}^{n} f\!\left(\tfrac{k}{n}\right) \binom{n}{k} x^k (1 - x)^{n-k}\]The weights are the binomial probabilities \(P(K = k)\) for \(K \sim \mathrm{Bin}(n, x)\), so \(B_n f(x) = \mathbb{E}[f(K/n)]\), and the law of large numbers forces \(B_n f \to f\) uniformly for every continuous
f: a constructive proof of the Weierstrass approximation theorem (S. Bernstein, Comm. Soc. Math. Kharkov (2) 13 (1912), 1-2). Convergence is slow: \(B_n(x^2) = x^2 + x(1 - x)/n\).- Parameters:
- Returns:
float or ndarray
Examples
>>> round(bernstein_polynomial(lambda t: t**2, 4, 0.5), 10) # 0.25 + 0.25/4 0.3125
- mathematicskit.numerical_analysis.chebyshev_nodes(n, a=-1.0, b=1.0)[source]#
Return the
nChebyshev points of the second kind on[a, b].\[x_k = \frac{a+b}{2} + \frac{b-a}{2} \cos\!\left(\frac{k\pi}{n-1}\right), \qquad k=0,\dots,n-1\]the extrema of the Chebyshev polynomial \(T_{n-1}\), returned in increasing order.
numpy.polynomial.chebyshev.chebpts2()computes these on[-1, 1]; this function affinely maps them onto[a, b]. See Trefethen, Approximation Theory and Approximation Practice, 2013, Ch. 12.- Parameters:
- Return type:
- Returns:
ndarray, shape (n,)
Examples
>>> nodes = chebyshev_nodes(5) >>> float(nodes[0]), float(nodes[-1]) (-1.0, 1.0) >>> import numpy as np >>> bool(np.all(np.diff(nodes) > 0)) True
- mathematicskit.numerical_analysis.condition_number(matrix)[source]#
2-norm condition number \(\kappa_2(A) = \sigma_{\max}/\sigma_{\min}\), via
numpy.linalg.cond().Used to flag numerically unstable fits/interpolation problems, e.g.
PolynomialRegression()’s Vandermonde design matrix, whose condition number grows quickly with polynomial degree. See Burden & Faires, Numerical Analysis, 10th ed., Ch. 7.5, and Trefethen & Bau, Numerical Linear Algebra, 1997, Lecture 12.Examples
>>> import numpy as np >>> A = np.diag([1.0, 2.0, 100.0]) >>> round(condition_number(A), 4) 100.0
- mathematicskit.numerical_analysis.estimate_convergence_order(history, root)[source]#
Estimate an iterative sequence’s empirical order of convergence.
Given the last four iterates \(x_{n-2}, x_{n-1}, x_n, x_{n+1}\) and errors \(e_k = |x_k - \text{root}|\), the order \(p\) satisfying \(e_{k+1} \approx C e_k^p\) is estimated from
\[p \approx \frac{\ln(e_{n+1} / e_n)}{\ln(e_n / e_{n-1})}\]using the two most recent error ratios. See Burden & Faires, Numerical Analysis, 10th ed., Ch. 2.4 (“Error Analysis for Iterative Methods”), Definition 2.8 and the surrounding discussion.
- Parameters:
- Return type:
- Returns:
float – The estimated order \(p\). The textbook values it should reproduce are
1.0for linearly convergent methods (bisection, fixed-point iteration under a contraction),~1.618– the golden ratio – for the secant method’s superlinear convergence, and2.0for Newton-Raphson’s quadratic convergence at a simple root.
Examples
>>> import numpy as np >>> # A synthetic quadratically-convergent sequence: e_{k+1} = e_k^2. >>> errors = [0.1, 0.01, 0.0001, 1e-8] >>> history = np.array([1.0 - e for e in errors]) >>> round(estimate_convergence_order(history, 1.0), 2) 2.0
- mathematicskit.numerical_analysis.horner(coefficients, x0)[source]#
Evaluate a polynomial and its derivative at
x0by Horner’s scheme.Writing \(p(x) = a_n x^n + \dots + a_1 x + a_0\) in nested form \((\cdots((a_n x + a_{n-1}) x + a_{n-2}) \cdots) x + a_0\) needs only \(n\) multiplications and \(n\) additions, which is optimal (Ostrowski 1954, Pan 1966). The intermediate values \(b_k = b_{k+1} x_0 + a_k\) are the coefficients of the quotient \(q\) in \(p(x) = (x - x_0) q(x) + p(x_0)\) (synthetic division), and a second pass gives \(p'(x_0) = q(x_0)\).
- Parameters:
coefficients (array-like, shape (n + 1,)) – Coefficients, highest power first (
numpy.polyvalconvention).x0 (
float) – Evaluation point.
- Return type:
- Returns:
HornerResult
Examples
>>> # p(x) = 2x^3 - 6x^2 + 2x - 1 at x = 3 >>> result = horner([2.0, -6.0, 2.0, -1.0], 3.0) >>> result.value, result.derivative (5.0, 20.0) >>> result.quotient.tolist() # 2x^2 + 0x + 2 [2.0, 0.0, 2.0]
- mathematicskit.numerical_analysis.kahan_sum(values)[source]#
Sum floating-point numbers with Kahan’s compensated summation.
A running correction
ccaptures the low-order bits lost when each term is added to the (much larger) partial sum, and feeds them back into the next term:y = x_i - c t = s + y c = (t - s) - y s = t
The computed sum satisfies \(|\hat s - s| \le \bigl(2u + O(nu^2)\bigr) \sum |x_i|\), with \(u\) the unit roundoff, independent of \(n\) to first order, where naive left-to-right summation only guarantees \((n - 1)\,u \sum |x_i|\) (Higham 1993).
Examples
>>> values = [1.0] + [1e-16] * 10_000 >>> naive = 0.0 >>> for v in values: # left to right: every tiny term is rounded away ... naive += v >>> naive 1.0 >>> round(kahan_sum(values), 15) 1.000000000001
- mathematicskit.numerical_analysis.lebesgue_constant(nodes, n_eval=2000)[source]#
Estimate the Lebesgue constant of a set of interpolation nodes.
The Lebesgue constant \(\Lambda_n = \max_x \sum_i |L_i(x)|\) (where \(L_i\) are the Lagrange basis polynomials for nodes) bounds how much interpolation error can be amplified relative to the best possible polynomial approximation, \(\|f - p_n\|_\infty \leq (1 + \Lambda_n) \|f - p_n^*\|_\infty\), and so serves as the interpolation problem’s condition number: large \(\Lambda_n\) (as for equally spaced nodes, where it grows exponentially in n) signals the Runge-phenomenon-prone regime, while Chebyshev nodes keep it growing only like \(O(\log n)\). See Trefethen, Approximation Theory and Approximation Practice, 2013, Ch. 15.
- Parameters:
- Return type:
- Returns:
float – The (approximate) Lebesgue constant.
Examples
>>> import numpy as np >>> equally_spaced = np.linspace(-1, 1, 15) >>> chebyshev = np.cos(np.pi * (2 * np.arange(15) + 1) / (2 * 15)) >>> lebesgue_constant(equally_spaced) > lebesgue_constant(chebyshev) True
- mathematicskit.numerical_analysis.remez_minimax(f, degree, a=-1.0, b=1.0, tol=1e-10, max_iter=1000, n_grid=20001)[source]#
Best uniform (minimax) polynomial approximation by the Remez exchange algorithm.
By Chebyshev’s equioscillation theorem, \(p^*\) of degree \(\le n\) minimizes \(\max_{[a,b]} |f - p|\) if and only if the error attains its maximum magnitude with alternating sign at \(n + 2\) points. Starting from the Chebyshev extrema, each step solves the linear system
\[p(x_i) + (-1)^i E = f(x_i), \qquad i = 0, \ldots, n + 1,\]for \(p\) and the levelled error \(E\), then moves the reference to the alternating extrema of the new error curve (located on a fine grid, then polished with
scipy.optimize.minimize_scalar()), until they are equal to within tol. See E. Ya. Remez, “Sur la détermination des polynômes d’approximation de degré donnée,” Comm. Soc. Math. Kharkov (4) 10 (1934), 41-63; L. N. Trefethen, Approximation Theory and Approximation Practice (SIAM, 2013), Ch. 10.- Parameters:
f (
Callable) – Vectorized continuous function on \([a, b]\).degree (
int) – Polynomial degree \(n\).a (
float) – Interval endpoints.b (
float) – Interval endpoints.tol (
float) – Stop when \((\max|e| - \min_i |e(x_i)|) / \max|e| < ` `tol\).max_iter (
int) – Maximum number of exchange steps.n_grid (
int) – Size of the grid used to locate error extrema.
- Return type:
- Returns:
MinimaxResult
Examples
>>> # Best cubic to x^4 on [-1, 1] is x^2 - 1/8, error 1/8 (= 2^{-3}). >>> res = remez_minimax(lambda x: x**4, 3) >>> bool(np.allclose(res.coefficients, [0.0, 1.0, 0.0, -0.125])) True >>> round(res.max_error, 8) 0.125
- mathematicskit.numerical_analysis.root_condition_numbers(coefficients, root)[source]#
Relative condition number of a simple root with respect to each coefficient.
Perturbing \(a_k\) to \(a_k(1 + \varepsilon)\) moves a simple root \(r\) by \(\delta r \approx -\varepsilon\,a_k r^k / p'(r)\), so the relative condition number is
\[\kappa_k = \frac{|a_k|\,|r|^k}{|r|\,|p'(r)|}.\]See Wilkinson, Rounding Errors in Algebraic Processes (1963), Ch. 2; Higham, Accuracy and Stability of Numerical Algorithms, 2nd ed. (SIAM, 2002), Ch. 26.
- Parameters:
coefficients (array-like, shape (n + 1,)) – Coefficients, highest power first.
root (
float) – A simple, nonzero root of the polynomial.
- Return type:
- Returns:
ndarray, shape (n + 1,) – \(\kappa_k\), in the same order as coefficients.
Examples
>>> # (x - 1)(x - 2) = x^2 - 3x + 2, root 2, p'(2) = 1 >>> root_condition_numbers([1.0, -3.0, 2.0], 2.0).tolist() [2.0, 3.0, 1.0]
- mathematicskit.numerical_analysis.runge_function(x)[source]#
Runge’s classic example, \(f(x) = 1/(1+25x^2)\) on \([-1,1]\).
- mathematicskit.numerical_analysis.runge_phenomenon_errors(degrees, n_eval=1000)[source]#
Compare equally spaced vs. Chebyshev-node interpolation error for
runge_function(), across a range of polynomial degrees.Demonstrates the Runge phenomenon: max error for equally spaced nodes grows (eventually diverging) with degree, while Chebyshev-node error shrinks.
- Parameters:
- Returns:
equal_errors, chebyshev_errors (ndarray, shape (len(degrees),)) – Max absolute interpolation error over
[-1, 1]for each degree.
Examples
>>> equal_err, cheb_err = runge_phenomenon_errors([5, 10, 15, 20]) >>> bool(equal_err[-1] > equal_err[0]) True >>> bool(cheb_err[-1] < cheb_err[0]) True
- mathematicskit.numerical_analysis.wilkinson_polynomial(n=20)[source]#
Coefficients of Wilkinson’s polynomial \(w(x) = \prod_{k=1}^{n} (x - k)\).
Built with
numpy.poly(). Forn = 20several coefficients exceed \(2^{53}\) and are already rounded in double precision, and the roots are so sensitive that changing the \(x^{19}\) coefficient \(-210\) by \(2^{-23}\) sends ten of them into the complex plane (Wilkinson 1959, 1963).- Parameters:
n (
int) – Degree (number of roots \(1, \ldots, n\)).- Return type:
- Returns:
ndarray, shape (n + 1,) – Coefficients, highest power first.
Examples
>>> wilkinson_polynomial(3).tolist() # (x-1)(x-2)(x-3) [1.0, -6.0, 11.0, -6.0] >>> float(wilkinson_polynomial(20)[1]) -210.0