Skip to content
The Lumenary

Math laboratory

Picard iteration: exact successive approximations

Computes Picard iterates exactly with SymPy for x' = x, x' = t + x and x' = x^2 (all with x(0) = 1), compares them with the exact solutions, and shows the iterates for x' = x^2 agreeing with the series of 1/(1 - t) to higher and higher order. Exact symbolic computation of finitely many iterates; convergence is the Picard-Lindelof theorem.

Back to the labSymbolic computationExact computation
Picard iteration: exact successive approximationsPythonSymbolic computationExact computationComputationally tested
"""Picard iteration, computed exactly with SymPy.

The initial value problem x' = f(t, x), x(t0) = x0 is equivalent to the
integral equation x(t) = x0 + integral from t0 to t of f(s, x(s)) ds. Picard's
iteration starts from the constant x0 and repeatedly substitutes into the
right-hand side. For x' = x it produces the Taylor polynomials of e^t; for
x' = t + x the iterates converge to 2e^t - t - 1; for x' = x^2, x(0) = 1 they
reproduce, term by term, the series 1 + t + t^2 + ... of the solution
1/(1 - t), which only converges for |t| < 1 - a shadow of the blow-up at t = 1.

Runs in the browser (Pyodide) with the sympy package loaded.
"""

import sympy

t, s = sympy.symbols("t s", real=True)

def picard_iterates(f, x0, count, t0=0):
    """Return [x_0, x_1, ..., x_count] with x_0 = x0 and
    x_{k+1}(t) = x0 + integral_{t0}^{t} f(s, x_k(s)) ds."""
    iterates = [sympy.Integer(x0) if isinstance(x0, int) else sympy.sympify(x0)]
    for _ in range(count):
        prev = iterates[-1]
        integrand = f(s, prev.subs(t, s))
        nxt = x0 + sympy.integrate(integrand, (s, t0, t))
        iterates.append(sympy.expand(nxt))
    return iterates

def agreement_order(approx, exact, max_order=20):
    """Largest n such that approx and exact agree up to and including t^n."""
    diff = sympy.series(approx - exact, t, 0, max_order + 1).removeO()
    poly = sympy.Poly(sympy.expand(diff), t)
    nonzero = [m[0] for m, c in zip(poly.monoms(), poly.coeffs()) if c != 0]
    return (min(nonzero) - 1) if nonzero else max_order

def main():
    print(f"Picard iteration with SymPy {sympy.__version__}, all with x(0) = 1")
    xs = picard_iterates(lambda u, x: x, 1, 5)
    print("x' = x:")
    for k in [1, 2, 3, 5]:
        print(f"   x_{k}(t) = {xs[k]}")
    print("   these are the Taylor polynomials of the solution e^t")
    ys = picard_iterates(lambda u, x: u + x, 1, 6)
    exact = 2 * sympy.exp(t) - t - 1
    print("x' = t + x (exact solution 2 e^t - t - 1):")
    for k in [2, 4, 6]:
        order = agreement_order(ys[k], exact)
        value = float(ys[k].subs(t, 1))
        print(f"   x_{k}(1) = {value:.6f}, agrees with the solution up to t^{order}")
    print(f"   exact value at t = 1: {float(exact.subs(t, 1)):.6f}")
    zs = picard_iterates(lambda u, x: x**2, 1, 4)
    sol = 1 / (1 - t)
    print("x' = x^2 (exact solution 1/(1 - t), which blows up at t = 1):")
    for k in range(1, 5):
        order = agreement_order(zs[k], sol)
        degree = int(sympy.degree(zs[k], t))
        print(f"   x_{k}: degree {degree:2d}, agrees with 1/(1 - t) up to t^{order}")
    print("   the series 1 + t + t^2 + ... converges only for |t| < 1;")
    print(f"   x_4(0.5) = {float(zs[4].subs(t, 0.5)):.6f} vs 1/(1 - 0.5) = 2,")
    print(f"   x_4(1.5) = {float(zs[4].subs(t, 1.5)):.1f}, but the solution ends at t = 1")
    print()
    print("Epistemic status: exact symbolic computation of a few iterates (as")
    print("reliable as SymPy's integration). That the iterates converge to the")
    print("unique solution on a short interval is the Picard-Lindelof theorem,")
    print("proved in the lesson; the computation only illustrates it.")

if __name__ == "__main__":
    main()
Runs entirely in your browser, in an isolated worker. Nothing is sent to a server.

Used in