← Cellular Automata From First Principles

Damage, Robustness and Persistence

A pattern that survives in perfect conditions is only the beginning.

A stronger artificial-life question is:

What happens when the pattern is disturbed?

We can turn that into an experiment.


Define damage explicitly

Start with a rectangular ablation:

def damage_rectangle(state, y0, y1, x0, x1):
    damaged = state.copy()
    damaged[..., y0:y1, x0:x1] = 0.0
    return damaged

For a single-channel state:

damaged = damage_rectangle(state, 55, 70, 55, 70)

For multi-channel state, the ellipsis damages all channels in the same spatial region.


Compare damaged and undamaged controls

Always keep a control run:

control = state.copy()
damaged = damage_rectangle(state, 55, 70, 55, 70)

for _ in range(500):
    control = lenia_step(control, kernel_f, config)
    damaged = lenia_step(damaged, kernel_f, config)

Without the control, we might mistake ordinary evolution for damage response.


Measure recovery of mass

def relative_mass(state, reference):
    return float(state.sum() / max(reference.sum(), 1e-12))

Track:

immediately after damage
100 steps later
500 steps later

Mass recovery alone does not prove morphological recovery, but it is one useful signal.

A worked example on the reference configuration, ablating 26% of mass with a control run alongside: relative mass recovers 0.76 → 1.00 within 50 steps. Mass alone would declare victory — the shape measurements below refuse to. The full protocol as a diagram, with the comparison that matters made explicit:

    flowchart LR
    S[reference state] --> C[control rollout]
    S --> D[damage: ablation/noise/params]
    D --> R[recovery rollout]
    C --> M[compare: mass ratio + aligned IoU]
    R --> M
  

Each robustness metric answers a different question — and each can mislead alone:

MetricCapturesFalsely suggests when alone
relative massmaterial returnmorphology restored
raw IoUpixel overlaprecovery (confounds motion)
aligned IoUshape similarity modulo driftexact state return
recovery timespeed back over thresholdquality of the endpoint

Compare shape

Threshold the field:

def binary_mask(state, threshold=0.05):
    return state > threshold

Then use intersection-over-union:

def iou(a, b):
    a = binary_mask(a)
    b = binary_mask(b)

    intersection = np.logical_and(a, b).sum()
    union = np.logical_or(a, b).sum()

    if union == 0:
        return 1.0

    return float(intersection / union)

For moving organisms, direct pixel alignment is unfair.

We may need translation alignment before comparing shape.

In the same worked example, center-of-mass-aligned IoU moves 0.34 → 0.71 → 0.42 → 0.67 over 400 steps — partial, fluctuating, never restored. Mass recovered; morphology did not. Both trajectories together, from the same runs:

Relative mass returns to control level within 50 steps while aligned shape similarity fluctuates without restoring (26% ablation, 400 steps)

That split is the chapter’s central empirical lesson, and it is why no screenshot here is allowed to claim “regeneration.”


Align by center of mass

def shift_to_center(state):
    centre = center_of_mass(state)
    if centre is None:
        return state.copy()

    target_y = state.shape[-2] // 2
    target_x = state.shape[-1] // 2

    y, x = centre
    dy = int(round(target_y - y))
    dx = int(round(target_x - x))

    return np.roll(np.roll(state, dy, axis=-2), dx, axis=-1)

Then compare aligned states.

This separates:

shape changed

from:

shape merely moved

Noise perturbations

Damage does not have to delete a chunk.

Add noise:

def add_noise(state, amount=0.05, seed=42):
    rng = np.random.default_rng(seed)
    noise = rng.normal(0.0, amount, size=state.shape)
    return np.clip(state + noise, 0.0, 1.0)

Or randomly erase cells:

def dropout_damage(state, probability=0.1, seed=42):
    rng = np.random.default_rng(seed)
    keep = rng.random(state.shape[-2:]) > probability
    return state * keep

Different perturbations test different forms of robustness.


Parameter perturbations

The environment itself can change.

For example:

perturbed = LeniaConfig(
    radius=config.radius,
    ring_center=config.ring_center,
    ring_width=config.ring_width,
    mu=config.mu + 0.005,
    sigma=config.sigma,
    dt=config.dt,
)

Ask whether the organism still persists.

This distinguishes a broad stable basin from an exact brittle parameter point.


A robustness matrix

Run many perturbation levels:

damage fraction:  0%  10%  20%  30%  40%
noise level:       0  .01  .03  .05  .10
parameter shift:   0  .002 .005 .010 .020

For each combination record:

survival
recovery time
final mass ratio
aligned shape similarity
sustained activity

Now robustness becomes a surface, not a yes/no anecdote.


Define recovery time

def recovery_time(simulator, damaged, reference_shape, threshold=0.8, max_steps=1000):
    state = damaged.copy()

    for step_index in range(max_steps):
        state = simulator(state)
        aligned = shift_to_center(state)

        if iou(aligned, reference_shape) >= threshold:
            return step_index

    return None

The exact metric depends on the pattern, but the experimental structure is reusable. (None — never reaching threshold — is a result, distinguishing slow recovery from non-recovery the way tail activity distinguished transient from sustained dynamics.)


Robustness is not regeneration

Be careful with language.

A pattern might:

survive damage while staying deformed

or:

regrow mass but not original morphology

or:

return to a close version of its previous form

Those are different outcomes — and the worked example above landed squarely in the middle one.

Use the evidence to decide which claim is justified:

persistence after damage
  ≠ regeneration

return to similar morphology
  ≠ exact state recovery

robustness
  ≠ adaptation

Collisions are perturbations too

Place two structures near each other.

Possible outcomes:

annihilation
fusion
scattering
capture
stable coexistence
fragmentation

Track both morphology and mass through the event.

Collision experiments can expose dynamics invisible in isolated runs.


Robustness becomes a search objective

Instead of selecting candidates only for survival, evaluate them under a perturbation suite — built only from tools defined above (rectangular ablation, noise injection, mass and IoU tracking). The first scorer measures mass recovery only, and is named accordingly:

def mass_recovery_score(final, kernel_f, config, perturbations, steps=300):
    scores = []

    for perturb in perturbations:
        state = perturb(final.copy())

        for _ in range(steps):
            state = lenia_step(state, kernel_f, config)

        scores.append(relative_mass(state, final))

    return float(np.mean(scores))

with perturbations a list of callables such as damage_rectangle and add_noise partials. No undefined machinery: every piece ran earlier in this chapter.

This is intentionally a screening metric, not a robustness verdict. Mass ratio alone would have scored the worked example as a full recovery. Morphology is a separate criterion, measured with iou(shift_to_center(state), shift_to_center(final)) and reported next to the mass score. The two are not collapsed into one number, because this book has no justified weighting between them.

Now search can explicitly seek systems whose mass survives damage, and report separately whether their shape does.


A deeper limitation remains

Ordinary Lenia can create and remove local state through the growth function:

A(t + dt) = clip(A(t) + dt * G(U))

Total mass is not conserved.

That makes beautiful self-organizing dynamics possible, but it also means growth can appear locally without material being transported from somewhere else.

What happens if we impose a stronger physical-style constraint?

In the next chapter we will examine Flow-Lenia, where the update is reformulated around transport and mass conservation, and see why that changes the possibilities for interacting artificial organisms.


Research

  • Chan, B. W.-C. — Lenia: Biology of Artificial Life (Complex Systems 28(3), 2019). The persistence discussion this chapter operationalizes: what it means for a fuzzy, quasi-periodic pattern to survive, and the plasticity limits of that survival. The mass/shape split measured here is the quantitative form of Chan’s qualitative persistence analysis. https://arxiv.org/html/1812.05433v3

  • Berto, F. & Tagliabue, J. — Cellular Automata (Stanford Encyclopedia of Philosophy). The methodological backing for damage-as-probe: where analysis cannot predict the evolution, controlled perturbation with recorded context is the empirical method — the same justification that carried the discrete sensitivity chapter, now on continuous fields. https://plato.stanford.edu/entries/cellular-automata/