Skip to content
The Lumenary

Math laboratory

Logistic map: period doubling, Feigenbaum ratios and Lyapunov exponents

Iterates the logistic map to find attractor periods (1, 2, 4, 8, chaos, a period-3 window), computes superstable parameters by Newton's method and the ratios of their gaps, which approach Feigenbaum's constant 4.669, and estimates Lyapunov exponents, showing exponential divergence of nearby orbits at r = 4. Floating-point evidence about finitely many iterates.

Back to the labNumerical approximation
Logistic map: period doubling, Feigenbaum ratios and Lyapunov exponentsPythonNumerical approximationComputationally tested
"""The logistic map x -> r x (1 - x): period doubling, Feigenbaum's constant and chaos.

For each growth rate r we iterate the map past its transient and detect the
period of the attractor. Then we locate the "superstable" parameters R_n at
which the critical point 1/2 lies on a cycle of period 2^n, by Newton's
method, and form the ratios (R_n - R_(n-1)) / (R_(n+1) - R_n), which approach
Feigenbaum's constant 4.669... Finally the Lyapunov exponent - the average of
log|f'(x_k)| along the orbit - is negative on stable cycles and equals log 2
at r = 4, where nearby orbits separate exponentially.
"""

import math

def logistic(r, x):
    """One step of the logistic map."""
    return r * x * (1 - x)

def orbit(r, x0, n):
    """[x_0, ..., x_n] for the logistic map."""
    xs = [x0]
    for _ in range(n):
        xs.append(logistic(r, xs[-1]))
    return xs

def attractor_period(r, x0=0.2, transient=3000, max_period=64, tol=1e-9):
    """Smallest p <= max_period with x_(n+p) = x_n after the transient, else None."""
    x = x0
    for _ in range(transient):
        x = logistic(r, x)
    start = x
    for p in range(1, max_period + 1):
        x = logistic(r, x)
        if abs(x - start) < tol:
            return p
    return None

def superstable_gap(r, n):
    """f_r^(2^n)(1/2) - 1/2: zero exactly when 1/2 lies on a 2^n-cycle."""
    x = 0.5
    for _ in range(2 ** n):
        x = logistic(r, x)
    return x - 0.5

def superstable_parameters(count):
    """R_0 = 2, R_1 = 1 + sqrt 5, ... found by Newton's method with a numerical
    derivative, each started from an extrapolation of the previous gaps."""
    rs = [2.0, 1 + math.sqrt(5)]
    delta = 4.0
    while len(rs) < count:
        n = len(rs)
        r = rs[-1] + (rs[-1] - rs[-2]) / delta
        for _ in range(60):
            g = superstable_gap(r, n)
            h = 1e-10
            dg = (superstable_gap(r + h, n) - superstable_gap(r - h, n)) / (2 * h)
            step = g / dg
            r -= step
            if abs(step) < 1e-14:
                break
        rs.append(r)
        delta = (rs[-2] - rs[-3]) / (rs[-1] - rs[-2])
    return rs

def lyapunov(r, x0=0.2, transient=1000, n=20000):
    """Average of log|f_r'(x_k)| = log|r (1 - 2 x_k)| along the orbit."""
    x = x0
    for _ in range(transient):
        x = logistic(r, x)
    total = 0.0
    for _ in range(n):
        total += math.log(abs(r * (1 - 2 * x)) + 1e-300)
        x = logistic(r, x)
    return total / n

def main():
    print("Attractor period after 3000 iterations from x0 = 0.2:")
    for r in [2.8, 3.2, 3.5, 3.56, 3.7, 3.83]:
        p = attractor_period(r)
        print(f"  r = {r:<5} period {p if p else 'none found (chaotic?)'}")
    rs = superstable_parameters(8)
    print()
    print("Superstable parameters R_n and Feigenbaum ratios:")
    for n, r in enumerate(rs):
        line = f"  R_{n} = {r:.10f}"
        if n >= 2:
            ratio = (rs[n - 1] - rs[n - 2]) / (rs[n] - rs[n - 1])
            line += f"   ratio {ratio:.5f}"
        print(line)
    print("  (Feigenbaum's constant: 4.66920...)")
    print()
    print("Lyapunov exponents (negative: stable cycle; positive: chaos):")
    for r in [2.8, 3.2, 3.5, 3.7, 3.83, 4.0]:
        print(f"  r = {r:<5} {lyapunov(r):+.4f}")
    print(f"  log 2 = {math.log(2):.4f}")
    a, b = orbit(4.0, 0.3, 40), orbit(4.0, 0.3 + 1e-10, 40)
    gaps = [abs(x - y) for x, y in zip(a, b)]
    first = next(i for i, g in enumerate(gaps) if g > 0.1)
    print(f"At r = 4, starts 1e-10 apart differ by > 0.1 after {first} steps.")
    print()
    print("Epistemic status: floating-point iteration of finitely many steps.")
    print("Periods, ratios and exponents are numerical estimates; the")
    print("universality of Feigenbaum's constant was proved by Lanford (1982)")
    print("with computer assistance, and chaos at r = 4 is a theorem.")

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

Used in