Information Measures

Describe a spacetime diagram cell by cell and you have described it completely — and learned almost nothing. The complete description is mostly data with very little information. The measures in pyCA.measures compress the data into the few numbers that matter: how unpredictable is a cell, how far do correlations reach, how many new bits does each cell carry once its neighbors are known. All entropies are in bits, and every function accepts either a single state or a full spacetime history (time along axis 0).

Block entropy

Slide a window of length \(k\) along each row, histogram the \(2^k\) possible words, and take the Shannon entropy[1] of the result — that is \(H_k\), the block entropy. For a frozen lattice it vanishes; for fair coin flips it equals \(k\); everything interesting lies between, and the gap below \(k\) measures the structure the automaton has built.

from pyCA import ECA, measures

ca = ECA(30, N=512)
ca.run(500)
st = ca.spacetime()

for k in (1, 2, 3, 4, 5):
    print(k, measures.block_entropy(st, k))

\(H_k\) never decreases with \(k\) — prove this to yourself from the definition before trusting the code, which is tested for it.

Entropy rate

The increments \(h_k = H_k - H_{k-1}\) converge from above to the entropy rate: the number of genuinely new bits per cell once a cell’s context is known. The entropy rate is the honest measure of randomness — rule 90’s spacetime diagram looks busy, but knowing two cells pins the third, and the rate reveals the redundancy.

print(measures.entropy_rate(st, k=5))

One practical warning: estimating \(H_k\) needs enough samples to populate \(2^k\) bins. Keep \(2^k\) comfortably below the number of cells in your history or the estimate biases low.

Mutual information

pyCA.measures.mutual_information() asks how much knowing one cell tells you about a cell distance sites away — \(I(X;Y) = H(X) + H(Y) - H(X,Y)\), pooled over every pair at that separation. Zero means independence; the maximum, \(H(X)\), means determination. Plotted against distance it is a correlation function with information-theoretic units:

import matplotlib.pyplot as plt

ds = range(1, 30)
plt.plot(ds, [measures.mutual_information(st, d) for d in ds])
plt.xlabel('distance')
plt.ylabel('I (bits)')
plt.show()

Lempel–Ziv complexity

The block measures see nothing longer than \(k\) cells. pyCA.measures.lz_complexity() has no such horizon: it parses each row into the phrases of Lempel and Ziv’s 1976 exhaustive history[2] — each phrase the shortest word that cannot be produced by copying from what came before — and counts them with the scanning algorithm of Kaspar and Schuster[3]. A row with structure at any length reuses its past and parses into few phrases; only genuine novelty forces new ones. The count is normalized by \(n/\log_2 n\), its asymptotic value for fair coin flips, so a random row reads near 1 and a frozen one near 0:

import numpy as np
from pyCA import ECA, measures

for rule in (0, 90, 30, 110):
    ca = ECA(rule, N=1024, rng=np.random.default_rng(1))
    ca.run(200)
    print(rule, measures.lz_complexity(ca.state))

Two cautions. The parse is sequential, not circular, so rows are read left to right. And the normalized count converges to the entropy rate from above slowly — at \(n \sim 1000\) a fair coin still reads about 1.05 — so compare rows of equal length rather than values across lengths.

A classification experiment

Wolfram’s four classes were assigned by eye. Assign them by number instead: for each rule in a sample, run a random initial condition, discard the transient, and place the rule on the (entropy rate, mutual information) plane. Class I collapses to the origin, Class III crowds the high-rate edge, and Class IV — the computing class — lives where the entropy rate is moderate but the mutual information refuses to die. There is a lot of unexplored territory in that plane; keep a journal of what you find in it.

References