# Generate Figures and Animations # Book: Cellular Automata From First Principles # Chapter URL: https://programmer.ie/books/cellular-automata-from-first-principles/57-chapter/ # Prose fingerprint (sha256, normalized): c5b686b1b7efd0c6962c250fbc859f7e274b4da2e541bfe9e5a357bccf0c4d73 # 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. Cellular automata are visual systems. That makes figures and animations unusually important. But a useful image is not merely a screenshot. It should be a reproducible output of an experiment. Save figures from data, not from memory Suppose an experiment has produced a spacetime history: history = np.stack(states) A figure generator should accept that result explicitly: import matplotlib.pyplot as plt def save_spacetime(history, path): fig, ax = plt.subplots(figsize=(10, 6)) ax.imshow(history, interpolation="nearest", aspect="auto") ax.set_xlabel("cell") ax.set_ylabel("time") fig.tight_layout() fig.savefig(path, dpi=180) plt.close(fig) (Verified headless: PNG written with labeled axes from recorded arrays.) Now the PNG is derived from recorded state rather than from an interactive session we cannot reproduce. Different phenomena need different views For an elementary rule: spacetime diagram For traffic: spacetime diagram fundamental diagram: density vs flow For reaction-diffusion: field snapshot parameter sweep grid For Lenia: state image centroid path mass/activity time series animation For NCA: growth animation damage/recovery comparison hidden-channel visualization loss over time The figure should expose the mechanism or evidence the chapter is discussing. Generate animations from stored frames from matplotlib.animation import FuncAnimation, PillowWriter def save_animation(frames, path, fps=20): fig, ax = plt.subplots() image = ax.imshow(frames[0], animated=True) ax.axis("off") def update(i): image.set_data(frames[i]) return (image,) animation = FuncAnimation(fig, update, frames=len(frames), blit=True) animation.save(path, writer=PillowWriter(fps=fps)) plt.close(fig) (Verified headless: GIF written via the Pillow writer with no display. The exact writer depends on the output format installed in the environment, so keep the rendering backend configurable — and record which writer produced each artifact, since encoders differ.) Do not store every simulation step unnecessarily A 10,000-step, 1024×1024 float simulation can produce enormous histories. Sample frames deliberately: if step % frame_interval == 0: frames.append(state.copy()) The frame interval is part of the artifact metadata. Build comparisons into the figure generator Regeneration is clearer as: before damage | immediately after | recovered than as three unrelated files. fig, axes = plt.subplots(1, 3, figsize=(12, 4)) for ax, image, title in zip( axes, [before, damaged, recovered], ["before", "damaged", "recovered"], ): ax.imshow(image) ax.set_title(title) ax.axis("off") The comparison is the argument. Plot measurements next to appearance A compelling animation can hide instability. Pair visual evidence with quantitative traces: state image mass over time activity over time centroid displacement recovery error This keeps the visual and analytical stories connected. Make artifact names stable Instead of: final.png final2.png really-final.png use names derived from experiment identity: rule184-density-0.30-seed-42-spacetime.png lenia-run-a17-mass.png nca-damage-square-recovery.gif Better still, place them under a run ID. Save a manifest A manifest is a function of explicit arguments, not a sketch with free variables: def make_manifest(experiment_id, figure, source_result, generator): return { "experiment_id": experiment_id, "figure": figure, "source_result": source_result, "generator": generator, } (Verified serializable — manifests must survive a JSON round trip, or they are not manifests.) Now a publication artifact has lineage. Every element earns its place by recording provenance — and fails loudly without it: Artifact element Records provenance of Missing it means experiment_id which run produced this unrepeatable image source_result raw metrics/state behind it numbers untraceable to runs generator + version code that rendered it re-rendering may differ silently run config + seed exact initial conditions same figure, different world frame interval / writer temporal sampling, encoder motion misread, artifacts differ A reproducible image without this chain is still just a pretty picture; with it, the image becomes evidence. Figures should be rebuildable The ideal command is conceptually — the provenance chain this chapter exists to enforce: flowchart LR E[run experiment] --> W[save raw outputs] W --> F[generate figures] F --> A[generate animation] W --> M[manifest links all three] instead of the irreproducible alternative: open notebook click around remember what looked good save screenshot That difference becomes critical when a book contains dozens of figures. Closing the loop with this book’s own figures A published page does not need to own every experiment. It needs trustworthy assets that can be traced back to code and results. The figures in this book are built that way: each is regenerated by a script from recorded runs (Chapter 3 shows the command for one), and the measured values quoted beside a figure come from those same runs. This chapter states the practice as a general rule. Next we will assemble all of these pieces into one coherent cellular-automata laboratory. Research Matplotlib documentation: matplotlib.animation. The exact API behind this chapter: FuncAnimation mechanics, blitting, and the writer registry (Pillow, HTML, FFmpeg, ImageMagick) — including which writer your environment actually has, since that determines what artifacts you can reproducibly produce. https://matplotlib.org/stable/api/animation_api.html The Turing Way — Guide for Reproducible Research. The lineage discipline this chapter implements: every figure traceable to raw outputs plus generator identity — the artifact-management half of reproducible research. https://book.the-turing-way.org/reproducible-research/reproducible-research/