← Cellular Automata From First Principles

Discover Your First Lenia Organisms

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:

MethodWhat it findsWhat it missesEvidence status
random soup samplingspontaneous persisters (Orbium class)rare or parameter-sensitive formsobserved, cataloged
survival filteringanything that lasts N stepsinteresting-but-fragile transientsmeasured, threshold-dependent
parameter tweakingneighbors of known formsdistant regions of spaceexploratory, human-guided
human inspectionsymmetry, locomotion, noveltyanything unrecognizable to eyesjudgment, 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