Skip to content
The Lumenary

Math laboratory

Return probabilities of random walks: Z^2 versus the free group

Computes the exact probability that simple random walk returns to its start after 2n steps on Z^2 (closed formula, checked against a direct count) and on the 4-regular tree, the Cayley graph of the free group F_2 (exact birth-death recursion). The n-th roots approach 1 and sqrt(3)/2, illustrating Kesten's amenability criterion; exact for finitely many n, the limits themselves are theorems.

Back to the labExact computationNumerical approximation
Return probabilities of random walks: Z^2 versus the free groupPythonExact computationNumerical approximationComputationally tested
"""Random walks see amenability: Kesten's criterion, computed exactly.

Simple random walk on a Cayley graph steps to a uniformly random neighbour.
Let p(2n) be the probability of being back at the start after 2n steps.

* On Z^2, p(2n) = (C(2n, n) / 4^n)^2, which decays like 1/(pi n): slower
  than any exponential, so p(2n)^(1/2n) -> 1.
* On the free group F_2 (the 4-regular tree), the distance from the start is
  a birth-death chain: from distance k >= 1 it goes up with probability 3/4
  and down with probability 1/4. We compute p(2n) exactly with fractions;
  p(2n)^(1/2n) tends to sqrt(3)/2 = 0.866..., the spectral radius.

Kesten's theorem (1959): a finitely generated group is amenable exactly when
the spectral radius is 1. Exact rational arithmetic for finitely many n; the
limits are theorems, approached here numerically.
"""

import math
from fractions import Fraction

def z2_return(n):
    """Exact return probability after 2n steps of simple random walk on Z^2."""
    return Fraction(math.comb(2 * n, n), 4 ** n) ** 2

def z2_return_by_counting(n):
    """The same probability by counting all walks of length 2n (small n)."""
    counts = {(0, 0): 1}
    for _ in range(2 * n):
        new = {}
        for (x, y), c in counts.items():
            for p in ((x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1)):
                new[p] = new.get(p, 0) + c
        counts = new
    return Fraction(counts.get((0, 0), 0), 4 ** (2 * n))

def tree_return(n, degree=4):
    """Exact return probability after 2n steps on the degree-regular tree."""
    up = Fraction(degree - 1, degree)
    down = Fraction(1, degree)
    dist = {0: Fraction(1)}
    for _ in range(2 * n):
        new = {}
        for k, pr in dist.items():
            if k == 0:
                new[1] = new.get(1, 0) + pr
            else:
                new[k + 1] = new.get(k + 1, 0) + pr * up
                new[k - 1] = new.get(k - 1, 0) + pr * down
        dist = new
    return dist.get(0, Fraction(0))

def nth_root(p, n):
    """p^(1/2n) as a float, computed through logarithms."""
    return math.exp(math.log(p) / (2 * n))

def main():
    print("Probability p(2n) of being back at the start after 2n steps")
    print("  2n    Z^2: p(2n)  p^(1/2n)  pi*n*p     F_2: p(2n)  p^(1/2n)")
    for n in (1, 2, 5, 10, 20, 40, 80):
        pz, pt = z2_return(n), tree_return(n)
        print(f"{2 * n:4d}  {float(pz):11.3e}  {nth_root(pz, n):8.5f}"
              f"  {math.pi * n * float(pz):6.4f}  {float(pt):11.3e}"
              f"  {nth_root(pt, n):8.5f}")
    print(f"Spectral radius of F_2 (Kesten): sqrt(3)/2 = {math.sqrt(3) / 2:.5f}")
    print(f"Formula for Z^2 checked by counting walks up to 2n = 12: "
          f"{all(z2_return(n) == z2_return_by_counting(n) for n in range(7))}")
    print(f"p(2) on the tree is exactly {tree_return(1)} (go out, come back).")
    print()
    print("Epistemic status: every p(2n) is an exact rational number. The")
    print("columns show Z^2 returning polynomially often (pi*n*p -> 1) and the")
    print("tree exponentially rarely, with n-th roots creeping towards 1 and")
    print("sqrt(3)/2. Finitely many n prove no limit: the limits are Kesten's")
    print("theorems; the roots converge slowly (corrections like n^(-c/n)).")

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

Used in