# Discover Your First Lenia Organisms # Book: Cellular Automata From First Principles # Chapter URL: https://programmer.ie/books/cellular-automata-from-first-principles/32-chapter/ # Prose fingerprint (sha256, normalized): 8ac8844652d0be5810466947601b71ddb1f4db23218ef67733a082fa624950c0 # 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. Running Lenia is easy. Finding persistent, localized, non-trivial structures is the hard part. This chapter turns that problem into a repeatable search pipeline. It follows Chan’s discovery playbook structurally — random soup generation, survival filtering, parameter tweaking, and, crucially, human judgment for what counts as interesting — while automating the parts that do not need eyes. Define what counts as a candidate We should not start with the vague objective: Find something that looks alive. Instead define measurable properties. A useful first candidate might be: survives for 1,000 steps stays localized keeps non-zero mass continues changing avoids filling the whole board Each condition removes a different failure mode. (“Organism,” “creature,” and “species” below are descriptive community terms in Chan’s own taxonomy — Orbium, Scutium, and some 400 cataloged siblings — not biological claims. The paper is explicit that self-replication, for one, is yet to be discovered in Lenia.) Evaluate persistence def survives(state, threshold=1e-3): return state.sum() > threshold Track it over a run: def survival_time(state, kernel_f, config, max_steps=1000): for step_index in range(max_steps): state = lenia_step(state, kernel_f, config) if not survives(state): return step_index return max_steps Evaluate localization def localization_score(state, threshold=0.05): active = state > threshold fraction = active.mean() return float(1.0 - fraction) A completely active board scores poorly. A very small localized pattern scores highly. But a single dying pixel would also score highly. That is why objectives must be combined. Measure sustained activity def sustained_activity(history, tail=100): values = [row["activity"] for row in history[-tail:]] return float(np.mean(values)) This reads the metric log from run_lenia: Chapter 19’s tail-activity idea, adapted to continuous state (mean absolute change instead of fraction changed). A static blob may be persistent but dynamically uninteresting. A pattern that remains active without exploding is a stronger candidate. Evaluate a seed def evaluate_seed(initial, config, steps=1000): kernel = build_kernel(config) kernel_f = kernel_fft(kernel, initial.shape) final, history = run_lenia( initial.copy(), kernel_f, config, steps=steps, ) return { "final_mass": float(final.sum()), "active_fraction": active_fraction(final), "activity": sustained_activity(history), "final": final, "history": history, } Now discovery can be automated. Generate many initial conditions def seed_bank(count, shape=(128, 128), patch=24, base_seed=1000): for index in range(count): yield random_seed( shape=shape, patch=patch, seed=base_seed + index, ) Evaluate them: results = [] for index, initial in enumerate(seed_bank(100)): result = evaluate_seed(initial, config) result["seed_index"] = index results.append(result) (Verified on a 6-seed pilot at 200 steps: all persist with masses ≈ 3382–3392 and active fractions ≈ 0.22 — every seed persists on the reference configuration, which is why the next chapter varies parameters instead of seeds alone.) Reject obvious failures first Simulation is expensive. Use staged evaluation: 100 steps ↓ reject dead / exploded ↓ 500 steps ↓ reject unstable ↓ 2,000 steps ↓ inspect survivors This is the same principle used in many search systems: cheap filter before expensive evaluation Build a candidate score For ranking only, we can combine several normalized properties: def candidate_score(result): mass_ok = min(result["final_mass"] / 100.0, 1.0) localized = 1.0 - result["active_fraction"] active = min(result["activity"] / 0.02, 1.0) return 0.3 * mass_ok + 0.4 * localized + 0.3 * active Do not confuse this with a scientific definition of life. It is an engineering ranking function for one search task — carrying all of Part III’s score-vs-phenomenon warnings. Keep diversity If we simply keep the top twenty candidates, they may all be near-duplicates. Compute simple descriptors: def descriptors(result): final = result["final"] return np.array([ final.sum(), final.mean(), final.var(), result["active_fraction"], result["activity"], ]) Then prefer candidates far apart in descriptor space. This is the beginning of novelty search and quality-diversity methods. Save everything required to replay For every promising candidate, save: parameter config initial seed random seed simulation length metrics final state optional frames A screenshot without lineage is not a scientific result. Human judgment still matters Automated metrics can remove obvious failures. They cannot fully capture properties such as: interesting symmetry coherent locomotion repeated appendages collision behavior regeneration morphological novelty This matches Chan’s experience directly: most Lenia species were found through interactive evolutionary computation — human eyes doing mutation and selection — with fully automatic exploration judged ineffective without pattern-recognition AI. The pipeline filters out the obviously dull; humans still name the interesting. A productive workflow is: automated search ↓ rank + diversify ↓ human inspection ↓ label interesting behaviors ↓ improve search objectives That creates a feedback loop between computation and observation. Make discovery visual Save thumbnails for the best candidates: def save_candidate_image(state, filename): import matplotlib.pyplot as plt plt.figure(figsize=(4, 4)) plt.imshow(state, cmap="viridis", vmin=0, vmax=1) plt.axis("off") plt.tight_layout() plt.savefig(filename, dpi=150) plt.close() An atlas of discovered forms is often more useful than a terminal full of scores. Discovery is now an engineering problem We have converted: Maybe tweak mu until something cool happens. into: candidate generator ↓ simulator ↓ measurements ↓ filters ↓ ranking ↓ diversity preservation ↓ replayable archive As a discovery flow with its two gates made explicit — survival first, interest second: flowchart LR G[generate seeds] --> R[roll out] R --> S{survives + localized?} S -->|no| X[reject] S -->|yes| I[inspect: human judgment] I -->|interesting| K[retain + record lineage] I -->|dull| X That architecture scales far beyond Lenia. Discovery methods differ in what they can find and what they cost: Method What it finds What it misses Evidence status random soup sampling spontaneous persisters (Orbium class) rare or parameter-sensitive forms observed, cataloged survival filtering anything that lasts N steps interesting-but-fragile transients measured, threshold-dependent parameter tweaking neighbors of known forms distant regions of space exploratory, human-guided human inspection symmetry, locomotion, novelty anything unrecognizable to eyes judgment, not measurement Next: search the parameter space too So far we held the Lenia rule fixed and varied initial conditions. But interesting structures also depend strongly on: mu sigma kernel radius ring geometry time step In the next chapter we will search both the organism seed and the world parameters, and use the experimental machinery from Part III to keep that search reproducible instead of turning it into random parameter roulette. Research Chan, B. W.-C. — Lenia: Biology of Artificial Life (Complex Systems 28(3), 2019). The discovery playbook this chapter automates: random-soup generation (Orbium and kin emerged unprompted), survival/stability/novelty selection criteria, parameter tweaking and manual mutation operators — and the honest verdict that automatic exploration without pattern-recognition AI stays ineffective. Calibrate any “automated discovery” claim against it. https://arxiv.org/html/1812.05433v3 Lehman, J. & Stanley, K. O. — Abandoning Objectives (Evolutionary Computation 19(2), 2011). The foundation under the diversity section: fixed scores deceive, and distance-in-descriptor-space is the principled alternative to top-twenty cloning. Already used in Chapter 25; applied here to organisms. https://dl.acm.org/doi/10.1162/EVCO_a_00025