Note
Go to the end to download the full example code.
Poincare Recurrence and Kac’s Lemma#
The Poincare recurrence theorem guarantees that, for a measure-preserving system, almost every trajectory returns arbitrarily close to its starting point infinitely often. A trajectory’s recurrence structure is captured by the pairwise recurrence matrix
Kac’s lemma sharpens the recurrence theorem into a quantitative statement: the mean time \(\langle \tau \rangle\) between returns to a region \(A\) equals the reciprocal of that region’s invariant measure \(\mu(A)\), \(\langle \tau \rangle \approx 1/\mu(A)\). This example visualizes recurrence structure with the classic “recurrence plot” (an image of \(R_{ij}\)) for the Lorenz attractor, \(\dot{x}=\sigma(y-x)\), \(\dot{y}=x(\rho-z)-y\), \(\dot{z}=xy-\beta z\), then numerically validates Kac’s lemma on the Henon map’s attractor, \(x_{n+1}=1-ax_n^2+y_n\), \(y_{n+1}=bx_n\).
import matplotlib.pyplot as plt
import numpy as np
from physicskit.chaos.systems.continuous import Lorenz
from physicskit.chaos.systems.maps import HenonMap
from physicskit.chaos.utils.recurrence import kac_lemma_estimate
from physicskit.chaos.visualizers.recurrence import plot_recurrence_matrix, plot_recurrence_times
Recurrence plot#
A recurrence plot marks (i, j) black whenever states i and j are
within epsilon of each other. The diagonal lines and textured blocks
visible here reflect the Lorenz attractor’s structure: near-diagonal bands
are the smooth short-time dynamics, while the broader checkerboard pattern
reflects the trajectory’s switching between the attractor’s two lobes.

Recurrence-time distribution#
For a chaotic map, the distribution of times between successive returns to a small neighborhood is itself informative – and its mean is exactly what Kac’s lemma predicts. This example uses the Henon map, whose returns are quick to sample well (its attractor is chaotic but not fractally stretched across wildly different length scales the way Lorenz’s is).

Validating Kac’s lemma#
mean_recurrence_time * measure(epsilon-ball) should be close to 1.
mean_time, measure, kac_product = kac_lemma_estimate(traj, epsilon=0.05, reference_idx=0)
print(f"mean recurrence time: {mean_time:.2f} iterations")
print(f"measure of the epsilon-ball: {measure:.5f}")
print(f"Kac's lemma product (should be close to 1): {kac_product:.4f}")
plt.show()
mean recurrence time: 43.06 iterations
measure of the epsilon-ball: 0.02321
Kac's lemma product (should be close to 1): 0.9993
Total running time of the script: (0 minutes 0.142 seconds)