← Cellular Automata From First Principles

Search Lenia Parameter Space

A Lenia pattern is not defined by its initial state alone.

It also lives inside a particular dynamical world.

Change the kernel or growth function slightly and the same seed may:

die
explode
freeze
oscillate
move
split
stabilize into a different morphology

So discovery has two coupled search spaces:

initial condition
      +
world parameters

Define parameter ranges

PARAM_RANGES = {
    "radius": (8, 24),
    "ring_center": (0.25, 0.75),
    "ring_width": (0.05, 0.25),
    "mu": (0.05, 0.30),
    "sigma": (0.01, 0.08),
    "dt": (0.03, 0.20),
}

These ranges are experimental choices.

They define which region of Lenia space we are willing to explore. (Chan’s maps span the same μ–σ plane among others — including the β-cube this single-ring implementation does not reach, a stated scope limit from Chapter 31.)

Each parameter’s local role — and how its failure looks, so the heatmap below reads as diagnosis rather than decoration:

ParameterRoleToo lowToo high
mupreferred neighborhood densitysparse contexts starvecrowding saturates
sigmatolerance widthselective to the point of deathindiscriminate flooding
radiuskernel scalefine detail, small structurescoarse, slow, expensive
dtintegration stepsluggish convergenceinstability

Sample a configuration

def sample_config(rng):
    return LeniaConfig(
        radius=int(rng.integers(8, 25)),
        ring_center=float(rng.uniform(0.25, 0.75)),
        ring_width=float(rng.uniform(0.05, 0.25)),
        mu=float(rng.uniform(0.05, 0.30)),
        sigma=float(rng.uniform(0.01, 0.08)),
        dt=float(rng.uniform(0.03, 0.20)),
    )

A random search is not sophisticated.

But it gives us a baseline.

Never build an elaborate optimizer before measuring what random search can already find.


Evaluate configurations and seeds together

def evaluate_trial(config, seed_value, shape=(128, 128), steps=500):
    initial = random_seed(shape=shape, seed=seed_value)
    result = evaluate_seed(initial, config, steps=steps)

    return {
        "config": config,
        "seed": seed_value,
        "score": candidate_score(result),
        "result": result,
    }

Then:

rng = np.random.default_rng(1234)
trials = []

for trial_id in range(200):
    config = sample_config(rng)
    seed_value = int(rng.integers(0, 1_000_000))
    trials.append(evaluate_trial(config, seed_value))

Separate failure categories

A single low score hides useful information.

Record explicit outcomes:

def classify_outcome(result):
    final = result["final"]
    mass = final.sum()
    fraction = result["active_fraction"]
    activity = result["activity"]

    if mass < 1e-3:
        return "dead"
    if fraction > 0.8:
        return "world-filling"
    if activity < 1e-5:
        return "static"
    return "persistent-dynamic"

Now we can ask:

Which parameters tend to die?
Which explode?
Where do persistent structures cluster?

Failure becomes data.


Plot the search landscape

import matplotlib.pyplot as plt

x = [t["config"].mu for t in trials]
y = [t["config"].sigma for t in trials]
c = [t["score"] for t in trials]

plt.scatter(x, y, c=c)
plt.xlabel("mu")
plt.ylabel("sigma")
plt.colorbar(label="candidate score")
plt.show()

This is the book’s version of Chan’s μ–σ maps, which chart species distributions across growth-parameter space: a two-dimensional projection of a larger parameter space, where persistent regions — not individual champions — are the finding.

But projections can reveal regions worth exploring more densely. Measured here over a 6×5 μ–σ grid (2 seeds per cell, 150 steps, 96×96, full candidate score with real activity):

Candidate-score heatmap over mu × sigma: persistent interior, dead border at narrow sigma (X marks extinction)

Scores run 0.40 (dead) to ~0.67 with a persistent interior around μ ≈ 0.13–0.17, σ ≥ 0.02 and extinction along the narrow-sigma border. Read it as a map of where to search, not a phase theorem — 150 steps and 2 seeds per cell resolve regimes, not boundaries.


Local refinement

Once a promising configuration appears, mutate around it:

def mutate_config(config, rng):
    return LeniaConfig(
        radius=max(3, config.radius + int(rng.integers(-2, 3))),
        ring_center=float(np.clip(config.ring_center + rng.normal(0, 0.03), 0.05, 0.95)),
        ring_width=float(np.clip(config.ring_width + rng.normal(0, 0.02), 0.01, 0.40)),
        mu=float(np.clip(config.mu + rng.normal(0, 0.015), 0.01, 0.50)),
        sigma=float(np.clip(config.sigma + rng.normal(0, 0.008), 0.005, 0.20)),
        dt=float(np.clip(config.dt + rng.normal(0, 0.01), 0.01, 0.30)),
    )

The strategy becomes:

explore broadly
      ↓
find promising region
      ↓
search locally

Robustness matters more than one lucky run

A configuration that works for one exact seed may be extremely fragile.

Evaluate multiple nearby seeds:

def robustness_score(config, seeds, steps=500):
    scores = []

    for seed_value in seeds:
        initial = random_seed(seed=seed_value)
        result = evaluate_seed(initial, config, steps=steps)
        scores.append(candidate_score(result))

    return {
        "mean": float(np.mean(scores)),
        "minimum": float(np.min(scores)),
        "std": float(np.std(scores)),
    }

A robust region should not collapse under tiny changes in initialization.

The regime test, executed on the reference configuration: shifting μ by ±0.02, widening σ by +0.02, or doubling dt all preserve persistent dynamics (masses ≈ 1785–2237) — quantitative change within qualitative persistence. But narrowing σ by 0.015 kills the pattern outright. That is the honest shape of a parameter region: robust interior, fragile borders, verified rather than asserted.


Search for niches, not just champions

The highest score may not be the most interesting result.

Imagine three candidates:

A: stationary pulsing blob
B: fast translating crescent
C: branching structure that repeatedly repairs itself

A single score may rank one highest and discard the others.

Instead maintain an archive indexed by behavioral descriptors:

speed
mass
size
activity
symmetry
oscillation period

Keep the best candidate in each region of behavior space.

This is the quality-diversity idea in practical form.


Search produces maps of possibility

A good search system does more than return one organism.

It can reveal:

stable regions
fragile borders
extinction zones
world-filling zones
mobile-pattern niches
oscillatory niches

That changes the scientific question from:

What creature did we find?

into:

What kinds of behavior are possible in this family of worlds?


Store the complete experiment table

Save every trial, not just winners.

A tabular record might contain:

trial_id
seed
radius
ring_center
ring_width
mu
sigma
dt
survival
mass
activity
active_fraction
classification
score
artifact_path

Then later analysis does not require rerunning every simulation.


The rule itself can become richer

So far one kernel produces one perception field and one growth response.

But many biological systems operate at more than one spatial scale.

We can have:

several kernels
several channels
several growth responses
cross-channel influence

That is the next major expansion — and the canonical direction: Chan’s full formulation already carries multi-shell β kernels, and multi-kernel/channel systems generalize further.

In the next chapter we will build multi-kernel and multi-channel Lenia, where a cell’s state becomes a vector again and different local fields can interact.


Research

  • Chan, B. W.-C. — Lenia: Biology of Artificial Life (Complex Systems 28(3), 2019). The mapping precedent for this chapter: μ–σ maps and the β-cube charting where species live in parameter space, niches and landscapes as the unit of discovery, and the stability–motility hypothesis linking persistence to movement. Run the regime test first; read the maps second. https://arxiv.org/html/1812.05433v3

  • Lehman, J. & Stanley, K. O. — Abandoning Objectives (Evolutionary Computation 19(2), 2011). The reason the archive is indexed by behavior rather than score: novelty in descriptor space finds what champion-chasing discards. The niches section is quality diversity argued from the book’s own candidates. https://dl.acm.org/doi/10.1162/EVCO_a_00025