# Activity, Density and Change # Book: Cellular Automata From First Principles # Chapter URL: https://programmer.ie/books/cellular-automata-from-first-principles/19-chapter/ # Prose fingerprint (sha256, normalized): 5ff6813b4c3191c136a84cf78773f482a0de7e82c269f11d6f24c20e1f40d38c # Snapshot generated at build time by Hugo. This is the complete rendered prose # of the chapter; it is not a transcript of the source file. A cellular automaton can look busy while doing very little that persists. It can also look visually quiet while preserving a small moving structure for hundreds of generations. So we need to separate several properties that are easy to confuse: occupancy temporal change spatial variation persistence The first three are owned by the previous chapter — density, change_rate/change_curve (our activity observable), and spatial_variation — and are reused here without redefinition. This chapter adds what they cannot see: whether change persists, where it happens, and how runs end. Together they form our first useful behavioral fingerprint. Density measures occupancy — reused density(state) from the previous chapter answers: What fraction of the world is active? Nothing about the definition changes here. The reuse is the point: one owner per observable, referenced rather than restated, so later chapters inherit a single meaning. What this chapter adds is the discipline of pairing density with change before interpreting either — because density tells us how much state is active, never how it is arranged or whether it is changing. Activity measures temporal change — reused Activity is what change_rate/change_curve measure: the fraction of cells that changed since the previous generation. Same function, now used systematically: activity = 0 no cell changed activity ≈ 1 almost every cell changed 0 < activity < 1 only part of the world changed The curve matters more than one final value. A rule may be highly active early and completely frozen later — which is exactly the gap the next section closes. Transient activity is not persistent activity Consider two runs. Run A generations 0-30: high activity generations 31-500: activity = 0 Run B generations 0-500: moderate activity Their mean activity could be surprisingly similar over a short experiment. But dynamically they are very different. (Verified: Rule 30 sustains tail activity ≈ 0.50 while the identity rule holds exactly 0.0 — same metric, opposite dynamics.) So measure late-run activity separately, reusing the previous chapter’s curve: import numpy as np def tail_activity( history, tail=50, ): curve = change_curve(history) if len(curve) == 0: return 0.0 return float( np.mean(curve[-tail:]) ) This gives us a simple distinction: early activity -> transient dynamics tail activity -> sustained dynamics That distinction will become increasingly important when we search rule spaces. It is also window-dependent by construction: the tail length is an observation choice, recorded with the experiment like any other. Count how often each cell changes Global activity tells us how much of the world changes. It does not tell us where the change occurs. Count transitions at each position: def cell_change_counts(history): return np.sum( history[1:] != history[:-1], axis=0, ) Now each cell gets a value: 0 never changed 5 changed five times 100 changed repeatedly For an elementary automaton started from one active cell, these counts often reveal the expanding causal region directly. (Verified on a Rule 150 seed: change concentrates in the causal diamond, peaking at 5 changes per cell.) The ordinary spacetime diagram shows the state. The change-count view shows where dynamics actually occurred. Detect extinction and saturation explicitly Binary automata have two particularly simple global states: all 0 all 1 Detect them directly: def terminal_state(state): if np.all(state == 0): return "empty" if np.all(state == 1): return "full" return "mixed" (Verified: all-zero → “empty”, all-one → “full”, Rule 0 collapses to “empty”.) This gives search pipelines a cheap first filter. Rules that immediately become completely empty or full may still be worth understanding, but we do not need expensive measurements to discover that they reached a trivial homogeneous state. Spatial variation measures local disagreement — reused and extended spatial_variation(state) from the previous chapter — mean neighbor disagreement — is reused as-is. (Verified: alternating rows score 1.0 while their temporal self-change is 0.0.) The extension is dimensionality. For a 2D grid: def spatial_variation_2d(grid): horizontal = np.mean( grid != np.roll( grid, -1, axis=1, ) ) vertical = np.mean( grid != np.roll( grid, -1, axis=0, ) ) return float( (horizontal + vertical) / 2 ) Again, the metric is simple. It is not a universal measure of structure. It answers one specific question: How often do neighboring cells disagree? That specificity is a strength. Build a behavioral fingerprint We now combine the previous chapter’s summary with this chapter’s persistence and terminal observables — extension by composition, not redefinition: def fingerprint(history): return { **summarize(history), "tail_activity": tail_activity( history ), "terminal": terminal_state( history[-1] ), } Now a rule does not receive one vague label such as: complex It receives a vector of observable properties. (Verified keys: mean/final density, mean change, mean/final spatial variation, tail activity, terminal state.) The assembly, with each key’s owner: flowchart LR H[history] --> S[summarize: density, change, spatial] H --> T[tail_activity: persistence] H --> E[terminal_state: end state] S --> F[fingerprint vector] T --> F E --> F Component, source, and what can still go wrong: Component Owned helper Interpretation Failure mode mean/final density summarize (Ch18) occupancy level blind to arrangement mean change summarize (Ch18) typical turnover transient-dominated tail activity tail_activity (here) sustained dynamics window-dependent spatial variation summarize (Ch18) fragmentation blind to time terminal state terminal_state (here) end regime says nothing about the route Conceptually: rule ↓ trajectory ↓ [ density, activity, persistence, spatial variation, terminal behavior ] That vector can later become input to: clustering classification search ranking visualization Measured on three rules (201 cells, 200 generations, single-cell starts), the vectors are visibly distinct profiles rather than interchangeable scores: Rule 0 is flat zero everywhere; Rule 30 leads on every axis; Rule 110 sits between, closest on tail activity. The point is not the ranking but the shape: two rules can match on one component and differ sharply on another, which is exactly what a scalar label would hide. A fingerprint is a measurement vector, not a classification theorem — grouping and labeling rules from these vectors belongs to the classification chapters, which will add their own caveats. Compare fingerprints, not screenshots Suppose two rules both look irregular. Their fingerprints might reveal: Rule A mean activity: high tail activity: near zero spatial variation: high Rule B mean activity: moderate tail activity: moderate spatial variation: moderate Now we know something important. Rule A creates a violent transient and then settles. Rule B maintains ongoing dynamics. A screenshot taken at generation 20 might make them look similar. A trajectory-level measurement separates them. Evaluate several initial conditions A rule is not fully characterized by one initial state. Run the same rule from several random initial conditions, reusing the previous chapter’s runner — now with the seed and random-initial arguments it was designed for: def evaluate_rule( rule_number, seeds, width=201, generations=200, ): records = [] for seed in seeds: history = run_rule( rule_number, width=width, generations=generations, seed=seed, initial="random", ) records.append( fingerprint(history) ) return records Now calculate: mean metric value variance across runs minimum maximum (Verified on Rule 30: five seeds give final densities 0.49–0.54 — stable fingerprint with genuine run-to-run variance.) A rule whose measurements vary dramatically across initial conditions behaves differently from one whose fingerprint is extremely stable. Chapter 22 studies that sensitivity directly. Preserve the experimental context A fingerprint without context can be misleading. Record: rule initial-condition type seed width generations boundary condition measurement version For example: record = { "rule": 30, "initial": "random", "seed": 42, "width": 201, "generations": 200, "boundary": "periodic", "features": fingerprint(history), } Now our feature vector remains tied to the experiment that produced it. Metrics are features, not truth A high-activity rule is not automatically interesting. A low-activity rule is not automatically simple. A checkerboard has high spatial variation while remaining highly regular. A transient explosion can produce high mean activity without persistent dynamics. So the correct pipeline is: observation ↓ measurement ↓ comparison ↓ hypothesis ↓ another experiment not: single metric ↓ final interpretation One idea to keep Density tells us how much state is active. (Owned: Chapter 18.) Activity tells us how much state is changing. (Owned: Chapter 18; persistence added here.) Spatial variation tells us how locally fragmented the state is. (Owned: Chapter 18; 2D extension here.) Persistence tells us whether the dynamics survive. (Owned: this chapter.) Together they already distinguish systems that a single screenshot or scalar measurement would collapse together. In the next chapter we will add an information-theoretic observable: Shannon entropy. It will give us another useful measurement — and another opportunity to learn why a high score does not automatically mean high complexity. Research Zenil, H. & Martinez, G. J. — Cellular Automata (Scholarpedia). Supports this chapter’s two methodological moves: finite observations can miss long transients (the reason tail activity exists as a separate observable), and sensitivity/perturbation analysis — Hamming-distance damage spreading and its metric dependence — is the formal sequel to the multi-seed variance measured here. http://www.scholarpedia.org/article/Cellular_automata Berto, F. & Tagliabue, J. — Cellular Automata (Stanford Encyclopedia of Philosophy). Frames the fingerprint project as an answer to the emergence-detection problem (Miller & Page: is there an objective basis for recognizing emergence?): domains, particles, and intrinsic computation identified through systematic observation rather than visual impression — the tradition this chapter’s vectors join. https://plato.stanford.edu/entries/cellular-automata/