mathematicskit.calculus#
Finite-difference derivatives with Richardson extrapolation (hand-rolled);
numerical quadrature built on scipy.integrate (trapezoidal, Simpson’s,
Gauss-Legendre via fixed_quad, adaptive via quad); forward-mode
automatic differentiation via dual numbers and a reverse-mode
(backpropagation-style) autodiff engine (hand-rolled – the pedagogical
subject); Taylor/Maclaurin series expansion and convergence-radius
estimation.
mathematicskit.calculus: numerical differentiation and integration.
Archimedes’ polygon bounds on pi; finite-difference derivatives
(forward/backward/central, complex step) with Richardson extrapolation
for higher accuracy (hand-rolled – no scipy equivalent); numerical
quadrature built on scipy.integrate (composite trapezoidal,
composite Simpson’s, Gauss-Legendre via fixed_quad, adaptive
quadrature via quad) alongside hand-rolled Riemann sums, Romberg,
Clenshaw-Curtis, tanh-sinh, and Euler-Maclaurin-corrected rules; forward-mode automatic differentiation via dual
numbers and a small reverse-mode (backpropagation-style) autodiff
engine (hand-rolled – autodiff is the pedagogical subject); Taylor/
Maclaurin series expansion and convergence-radius estimation for
standard functions.
- class mathematicskit.calculus.AdaptiveQuadrature(tol=1e-08, max_depth=50)[source]#
Bases:
QuadratureAdaptive quadrature with error estimation, via
scipy.integrate.quad().quadwraps QUADPACK’sQAGS: adaptive subdivision plus Gauss-Kronrod extrapolation, refining subintervals where the integrand is hardest to approximate until the requested absolute tolerance is met. See Burden & Faires, Numerical Analysis, 10th ed., Ch. 4.6 (“Adaptive Quadrature Methods”), for the algorithm this generalizes.- Parameters:
Examples
>>> result = AdaptiveQuadrature(tol=1e-10).integrate(lambda x: x**2, 0.0, 1.0) >>> round(result.value, 8) 0.33333333
- class mathematicskit.calculus.ClenshawCurtisQuadrature(n=16)[source]#
Bases:
QuadratureClenshaw-Curtis quadrature: integrate the Chebyshev interpolant at \(\cos(k\pi/n)\).
For smooth integrands it converges nearly as fast as Gauss-Legendre quadrature with the same number of points, and its nodes nest when
ndoubles. See C. W. Clenshaw and A. R. Curtis, “A Method for Numerical Integration on an Automatic Computer,” Numerische Mathematik 2 (1960), 197-205.- Parameters:
n (
int) – Number of intervals (n + 1function evaluations).
Examples
>>> result = ClenshawCurtisQuadrature(n=16).integrate(np.exp, 0.0, 1.0) >>> abs(result.value - (np.e - 1)) < 1e-14 True
- class mathematicskit.calculus.DerivativeResult(value, step=0.0, method='', error_estimate=0.0)[source]#
Bases:
objectContainer for a finite-difference derivative estimate.
- class mathematicskit.calculus.Dual(real, dual=0.0)[source]#
Bases:
objectA dual number \(\text{real} + \text{dual}\,\varepsilon\).
Examples
>>> x = Dual(3.0, 1.0) # seed dx/dx = 1 >>> y = x * x + 2.0 * x # f(x) = x^2 + 2x, f'(x) = 2x + 2 >>> y.real, y.dual (15.0, 8.0)
- dual#
- real#
- class mathematicskit.calculus.ExhaustionResult(sides, lower, upper)[source]#
Bases:
objectContainer for Archimedes’ polygon bounds on \(\pi\).
- class mathematicskit.calculus.GaussianQuadrature(n=5)[source]#
Bases:
QuadratureGauss-Legendre quadrature: exact for polynomials up to degree
2n - 1.Thin wrapper around
scipy.integrate.fixed_quad(), which maps \([-1, 1]\) Gauss-Legendre nodes/weights onto[a, b]. For smooth integrands this needs far fewer evaluations than trapezoidal/Simpson for the same accuracy. See Burden & Faires, Numerical Analysis, 10th ed., Ch. 4.7.- Parameters:
n (
int) – Number of Gauss-Legendre nodes.
Examples
>>> result = GaussianQuadrature(n=5).integrate(lambda x: x**9, -1.0, 1.0) >>> abs(round(result.value, 10)) 0.0
- class mathematicskit.calculus.Quadrature[source]#
Bases:
ABCCommon base for numerical-integration rules over
[a, b].
- class mathematicskit.calculus.QuadratureResult(value, error_estimate=0.0, n_evaluations=0, method='', extra=<factory>)[source]#
Bases:
objectContainer for the output of a
Quadraturerule.- error_estimate: float = 0.0#
Estimated absolute error (0 when the rule has no built-in estimate).
- Type:
- class mathematicskit.calculus.RiemannSum(n=100, rule='midpoint')[source]#
Bases:
QuadratureA Riemann sum on
nequal subintervals, sampling each at its left end, right end, or midpoint.Bernhard Riemann’s 1854 definition of the integral is the limit of such sums as the subintervals shrink. The left and right rules are \(O(h)\) accurate; the midpoint rule is \(O(h^2)\). Hand-rolled, since the sum is the definition being illustrated.
Examples
>>> RiemannSum(n=4, rule="left").integrate(lambda x: x, 0.0, 1.0).value 0.375 >>> RiemannSum(n=4, rule="midpoint").integrate(lambda x: x, 0.0, 1.0).value 0.5
- class mathematicskit.calculus.RombergQuadrature(levels=6)[source]#
Bases:
QuadratureRomberg integration: Richardson extrapolation applied to repeatedly halved trapezoidal rules.
Builds the triangular table
\[R_{i,0} = T_{2^i}, \qquad R_{i,j} = R_{i,j-1} + \frac{R_{i,j-1} - R_{i-1,j-1}}{4^j - 1},\]where \(T_{2^i}\) is the trapezoidal rule on \(2^i\) subintervals. Each column cancels the next even power of \(h\) in the trapezoidal error expansion. Hand-rolled because
scipy.integrate.rombergwas removed in SciPy 1.15. See W. Romberg, “Vereinfachte numerische Integration,” Det Kongelige Norske Videnskabers Selskabs Forhandlinger 28(7) (1955), 30-36.- Parameters:
levels (
int) – Number of halvings; the finest rule uses \(2^{\text{levels}}\) subintervals.
Examples
>>> result = RombergQuadrature(levels=5).integrate(np.exp, 0.0, 1.0) >>> abs(result.value - (np.e - 1)) < 1e-12 True
- class mathematicskit.calculus.SimpsonsRule(n=100)[source]#
Bases:
QuadratureComposite Simpson’s rule, \(O(h^4)\) global error.
Fits a parabola through each pair of subintervals, via
scipy.integrate.simpson()onn + 1equally spaced samples (requiresneven). See Burden & Faires, Numerical Analysis, 10th ed., Ch. 4.4.- Parameters:
n (
int) – Number of subintervals (must be even).
Examples
>>> result = SimpsonsRule(n=10).integrate(lambda x: x**3, 0.0, 1.0) >>> round(result.value, 10) 0.25
- class mathematicskit.calculus.TanhSinhQuadrature(h=0.1, t_max=4.0)[source]#
Bases:
QuadratureTanh-sinh (double-exponential) quadrature, robust to endpoint singularities.
Substitutes \(x = \tfrac{a+b}{2} + \tfrac{b-a}{2}\tanh(\tfrac{\pi}{2}\sinh t)\) and applies the trapezoidal rule in \(t\). The transformed integrand decays double-exponentially, so the trapezoidal rule converges very fast even when \(f\) blows up at an endpoint. The endpoints themselves are never evaluated. Nodes crowd toward the endpoints, so accuracy is limited by how precisely
fcan be evaluated there: an integrand such as \(1/\sqrt{1-x^2}\) loses digits once \(x\) is rounded near \(\pm1\). Hand-rolled for SciPy versions before 1.15, which lackscipy.integrate.tanhsinh. See H. Takahasi and M. Mori, “Double Exponential Formulas for Numerical Integration,” Publications of the Research Institute for Mathematical Sciences 9(3) (1974), 721-741.- Parameters:
Examples
>>> result = TanhSinhQuadrature(h=0.1).integrate(lambda x: 1 / np.sqrt(x), 0.0, 1.0) >>> abs(result.value - 2.0) < 1e-10 True
- class mathematicskit.calculus.TrapezoidalRule(n=100)[source]#
Bases:
QuadratureComposite trapezoidal rule, \(O(h^2)\) global error.
\(\int_a^b f\,dx \approx h\left[\tfrac12 f(x_0) + f(x_1) + \dots + f(x_{n-1}) + \tfrac12 f(x_n)\right]\), \(h = (b-a)/n\), via
scipy.integrate.trapezoid()onn + 1equally spaced samples. See Burden & Faires, Numerical Analysis, 10th ed., Ch. 4.3.- Parameters:
n (
int) – Number of subintervals.
Examples
>>> result = TrapezoidalRule(n=1000).integrate(lambda x: x**2, 0.0, 1.0) >>> round(result.value, 6) 0.333334
- class mathematicskit.calculus.Variable(value, _parents=None)[source]#
Bases:
objectA scalar node in a reverse-mode autodiff computation graph.
- Parameters:
Examples
>>> x = Variable(3.0) >>> y = x * x + 2.0 * x # f(x) = x^2 + 2x >>> y.backward() >>> round(x.grad, 10) # f'(3) = 2*3 + 2 = 8 8.0
- backward()[source]#
Propagate gradients back through the computation graph.
Sets
self.grad = 1(the seed) and accumulates \(\partial \text{self}/\partial v\) intov.gradfor every nodevreachable as a parent, via reverse topological traversal.
- grad#
- value#
- mathematicskit.calculus.archimedes_pi_bounds(n_doublings=4)[source]#
Lower and upper bounds on \(\pi\) from regular polygons with \(6 \cdot 2^k\) sides.
Four doublings reach Archimedes’ 96-gon, which gives \(3\tfrac{10}{71} < \pi < 3\tfrac{1}{7}\).
- Parameters:
n_doublings (
int) – Number of side doublings after the initial hexagon.- Return type:
- Returns:
ExhaustionResult
Examples
>>> result = archimedes_pi_bounds(4) >>> result.sides[-1] 96 >>> 3 + 10 / 71 < result.lower[-1] < math.pi < result.upper[-1] < 3 + 1 / 7 True
- mathematicskit.calculus.backward_difference(f, x, h=1e-05)[source]#
Backward-difference derivative estimate, \(O(h)\).
\(f'(x) \approx \dfrac{f(x) - f(x-h)}{h}\).
Examples
>>> round(backward_difference(lambda t: t**2, 3.0, h=1e-4), 3) 6.0
- mathematicskit.calculus.central_difference(f, x, h=1e-05)[source]#
Central-difference derivative estimate, \(O(h^2)\).
\(f'(x) \approx \dfrac{f(x+h) - f(x-h)}{2h}\) – one order of accuracy better than forward/backward differences at the same cost (2 evaluations), since the \(O(h)\) error terms in the underlying Taylor expansions cancel. See Burden & Faires, Numerical Analysis, 10th ed., Ch. 4.1, eq. (4.4).
Examples
>>> import numpy as np >>> round(float(central_difference(np.sin, 0.0, h=1e-4)), 8) 1.0
- mathematicskit.calculus.clenshaw_curtis_nodes_and_weights(n)[source]#
Clenshaw-Curtis nodes \(x_k = \cos(k\pi/n)\) and weights on \([-1, 1]\).
The weights integrate exactly the Chebyshev interpolant through the \(n+1\) nodes. Computed with the explicit cosine-sum formula of L. N. Trefethen, Spectral Methods in MATLAB (Philadelphia: SIAM, 2000), program
clencurt. Hand-rolled: SciPy has no Clenshaw-Curtis rule.- Parameters:
n (
int) – Number of intervals; there aren + 1nodes.- Returns:
nodes, weights (ndarray, shape (n + 1,))
Examples
>>> x, w = clenshaw_curtis_nodes_and_weights(4) >>> round(float(w.sum()), 12) # integrates 1 exactly: length of [-1, 1] 2.0
- mathematicskit.calculus.complex_step_derivative(f, x, h=1e-20)[source]#
Complex-step derivative estimate, \(O(h^2)\) with no subtractive cancellation.
\(f'(x) \approx \operatorname{Im} f(x + ih) / h\). Because no two nearly equal numbers are subtracted,
hcan be taken absurdly small and the result is accurate to machine precision. Requiresfto be real-analytic and implemented with complex-capable operations (e.g.numpyufuncs). See J. N. Lyness and C. B. Moler, “Numerical Differentiation of Analytic Functions,” SIAM Journal on Numerical Analysis 4(2) (1967), 202-210, and W. Squire and G. Trapp, “Using Complex Variables to Estimate Derivatives of Real Functions,” SIAM Review 40(1) (1998), 110-112.- Parameters:
- Return type:
- Returns:
float
Examples
>>> import numpy as np >>> abs(complex_step_derivative(np.exp, 1.0) - np.e) < 1e-15 True
- mathematicskit.calculus.derivative(f, x)[source]#
Exact derivative of
fatxvia forward-mode dual numbers.- Parameters:
- Return type:
- Returns:
float – \(f'(x)\), exact up to floating-point roundoff (no truncation error, unlike finite differences).
Examples
>>> f = lambda x: (x * x).sin() >>> round(derivative(f, 1.0), 10) == round(2.0 * 1.0 * math.cos(1.0 * 1.0), 10) True
- mathematicskit.calculus.estimate_radius_of_convergence(coefficients, growth_factor=1.5)[source]#
Estimate a power series’ radius of convergence via the ratio test.
\(R = \lim_{n\to\infty}|a_n/a_{n+1}|\), estimated from the spacing-adjusted ratio \(|a_i/a_j|^{1/(j-i)}\) over consecutive nonzero coefficients (the \(1/(j-i)\) exponent is what makes series like
"sin"/"cos", whose even or odd coefficients vanish identically, come out on the same footing as dense ones).A truncated series can only ever sample that limit, so a series with infinite radius –
"exp","sin","cos"– shows up not as a large number but as a ratio that keeps climbing with every term added. This function detects exactly that: if the estimate from the series’ last two nonzero coefficients exceeds the estimate from its first two by more than growth_factor, the ratio has not settled andinfis returned, rather than a number that is really just an artifact of where the series happened to be cut off (a degree-20"exp"would otherwise reportR = 20, and a degree-30 oneR = 30). See any standard calculus text’s treatment of the ratio test for power series (e.g. Stewart, Calculus, 8th ed., Ch. 11.8).- Parameters:
coefficients (
ndarray) –a[n]is the coefficient of \(x^n\) (as frommaclaurin_coefficients()).growth_factor (
float) – How much larger the last ratio estimate must be than the first before the radius is judged unbounded.> 1.
- Return type:
- Returns:
float – The estimated radius, or
infwhen fewer than two nonzero coefficients are available to compare, or when the ratio is still growing (an unbounded radius of convergence).
Examples
>>> coeffs = maclaurin_coefficients("geometric", 30) >>> round(estimate_radius_of_convergence(coeffs), 6) 1.0 >>> round(estimate_radius_of_convergence(maclaurin_coefficients("log1p", 30)), 2) 1.03 >>> # exp and sin converge everywhere: their ratio never settles. >>> estimate_radius_of_convergence(maclaurin_coefficients("exp", 20)) inf >>> estimate_radius_of_convergence(maclaurin_coefficients("sin", 20)) inf
- mathematicskit.calculus.euler_maclaurin_trapezoid(f, a, b, n, odd_derivatives)[source]#
Trapezoidal rule plus Euler-Maclaurin endpoint corrections.
\[\int_a^b f\,dx \approx T_n - \sum_{k=1}^{m} \frac{B_{2k} h^{2k}}{(2k)!} \left(f^{(2k-1)}(b) - f^{(2k-1)}(a)\right),\]with Bernoulli numbers \(B_{2k}\) from
scipy.special.bernoulli(). Each correction removes one more even power of \(h\) from the trapezoidal error. Leonhard Euler (1735) and Colin Maclaurin (1742) found the formula independently. See Burden & Faires, Numerical Analysis, 10th ed., Ch. 4.5.- Parameters:
- Return type:
- Returns:
QuadratureResult
Examples
>>> exact = np.e - 1 >>> plain = euler_maclaurin_trapezoid(np.exp, 0.0, 1.0, 8, []) >>> corrected = euler_maclaurin_trapezoid(np.exp, 0.0, 1.0, 8, [np.exp, np.exp]) >>> abs(corrected.value - exact) < 1e-3 * abs(plain.value - exact) True
- mathematicskit.calculus.evaluate_series(coefficients, x)[source]#
Evaluate a truncated power series \(\sum_n a_n x^n\) (Horner’s method).
- Parameters:
coefficients (
ndarray) –a[n]is the coefficient of \(x^n\) (as frommaclaurin_coefficients()).
- Returns:
float or ndarray
Examples
>>> import numpy as np >>> coeffs = maclaurin_coefficients("exp", 15) >>> round(float(evaluate_series(coeffs, 1.0)), 10) == round(np.e, 10) True
- mathematicskit.calculus.forward_difference(f, x, h=1e-05)[source]#
Forward-difference derivative estimate, \(O(h)\).
\(f'(x) \approx \dfrac{f(x+h) - f(x)}{h}\). See Burden & Faires, Numerical Analysis, 10th ed., Ch. 4.1.
- Parameters:
- Return type:
- Returns:
float
Examples
>>> round(forward_difference(lambda t: t**2, 3.0, h=1e-4), 3) 6.0
- mathematicskit.calculus.gradient(f, x)[source]#
Gradient of a scalar function of several variables via reverse-mode AD.
- Parameters:
- Return type:
- Returns:
list of float – \(\nabla f(x)\).
Examples
>>> f = lambda x, y: x * x * y + y # df/dx = 2xy, df/dy = x^2 + 1 >>> grad = gradient(f, [3.0, 2.0]) >>> [round(g, 10) for g in grad] [12.0, 10.0]
- mathematicskit.calculus.legendre_nodes_and_weights(n)[source]#
Gauss-Legendre nodes and weights on \([-1, 1]\), via
numpy.polynomial.legendre.leggauss().Nodes are the
nroots of the degree-nLegendre polynomial \(P_n\); weights are \(w_i = \dfrac{2}{(1-x_i^2)[P_n'(x_i)]^2}\).leggausscomputes both from the companion-matrix eigenvalues of \(P_n\), which is more robust than a from-scratch Newton iteration on the asymptotic initial guess. See Press et al., Numerical Recipes, 3rd ed., Sec. 4.6, and Burden & Faires, Numerical Analysis, 10th ed., Ch. 4.7.- Parameters:
n (
int) – Number of nodes (exact for polynomials up to degree2n - 1).- Returns:
nodes, weights (ndarray, shape (n,))
Examples
>>> nodes, weights = legendre_nodes_and_weights(3) >>> import numpy as np >>> round(float(np.sum(weights)), 10) 2.0
- mathematicskit.calculus.maclaurin_coefficients(name, order)[source]#
Maclaurin-series coefficients \(a_0, \dots, a_{\text{order}}\) for a standard function.
Supported
namevalues and their series:"exp": \(e^x = \sum_{n\geq0} x^n/n!\), \(R=\infty\)."sin": \(\sin x = \sum_{n\geq0} (-1)^n x^{2n+1}/(2n+1)!\), \(R=\infty\)."cos": \(\cos x = \sum_{n\geq0} (-1)^n x^{2n}/(2n)!\), \(R=\infty\)."log1p": \(\ln(1+x) = \sum_{n\geq1} (-1)^{n+1} x^n/n\), \(R=1\)."geometric": \(1/(1-x) = \sum_{n\geq0} x^n\), \(R=1\)."arctan": \(\arctan x = \sum_{n\geq0} (-1)^n x^{2n+1}/(2n+1)\), \(R=1\).
- Parameters:
- Return type:
- Returns:
ndarray, shape (order + 1,) –
a[n]is the coefficient of \(x^n\).
Examples
>>> import numpy as np >>> coeffs = maclaurin_coefficients("exp", 4) >>> np.allclose(coeffs, [1.0, 1.0, 0.5, 1.0 / 6.0, 1.0 / 24.0]) True
- mathematicskit.calculus.partial_sums(coefficients, x)[source]#
Partial sums \(S_k(x) = \sum_{n=0}^{k} a_n x^n\) for every
k.- Parameters:
- Return type:
- Returns:
ndarray, shape (order + 1,) –
result[k]is the degree-kpartial sum.
Examples
>>> import numpy as np >>> sums = partial_sums(np.array([1.0, 1.0, 0.5, 1.0 / 6.0]), 1.0) >>> np.round(sums, 6) array([1. , 2. , 2.5 , 2.666667])
- mathematicskit.calculus.richardson_extrapolation(f, x, h=0.01, levels=4)[source]#
Richardson-extrapolate central differences to arbitrarily high order.
Central differences have an error series in even powers of
h: \(D(h) = f'(x) + c_1 h^2 + c_2 h^4 + \dots\). Combining \(D(h)\) and \(D(h/2)\) as \(\dfrac{4 D(h/2) - D(h)}{3}\) cancels the \(h^2\) term, leaving \(O(h^4)\); repeating with successively halved step sizes in a triangular (Neville-style) table cancels one more error order each level. See Burden & Faires, Numerical Analysis, 10th ed., Ch. 4.2, and the closely related idea in Ch. 4.5 (Romberg integration).- Parameters:
- Return type:
- Returns:
DerivativeResult –
error_estimateis the change between the last two extrapolated values, a practical (not rigorous) error indicator.
Examples
>>> import numpy as np >>> result = richardson_extrapolation(np.sin, 0.0, h=0.2, levels=5) >>> round(float(result.value), 12) 1.0
- mathematicskit.calculus.taylor_remainder_bound(max_derivative_bound, order, x, x0=0.0)[source]#
Lagrange remainder bound for a degree-
orderTaylor polynomial.\(|R_n(x)| \leq \dfrac{M}{(n+1)!}|x-x_0|^{n+1}\), where
Mbounds \(|f^{(n+1)}|\) on the interval betweenx0andx. See Burden & Faires, Numerical Analysis, 10th ed., Ch. 1.1, Theorem 1.14 (Taylor’s Theorem).- Parameters:
- Return type:
- Returns:
float
Examples
>>> # sin has |f^(n+1)| <= 1 everywhere, so this bounds the 5th-order >>> # Maclaurin remainder at x=0.5. >>> round(taylor_remainder_bound(1.0, 5, 0.5), 8) 2.17e-05
- mathematicskit.calculus.truncation_error(f, coefficients, x)[source]#
Empirical truncation error
|f(x) - S_k(x)|for every partial sum.- Parameters:
- Return type:
- Returns:
ndarray, shape (order + 1,)
Examples
>>> import numpy as np >>> from mathematicskit.calculus.systems.taylor_series import maclaurin_coefficients >>> coeffs = maclaurin_coefficients("exp", 15) >>> errors = truncation_error(np.exp, coeffs, 1.0) >>> bool(errors[-1] < errors[0]) True