Multi-Kernel and Multi-Channel Lenia
Our Lenia implementation has one scalar field:
A(x, y)
and one kernel:
K
That is enough for rich behavior.
But it also forces every local process to share the same spatial scale and the same state variable.
A richer model can use multiple channels:
A0(x, y)
A1(x, y)
A2(x, y)
and multiple kernels linking them.
This chapter builds a pedagogical multi-channel formulation in Lenia’s spirit — a connection graph with per-edge kernels and growth parameters. Canonical multi-kernel, multi-channel Lenia is Chan’s “Expanded Universe” (ALIFE 2020) direction; the book’s version is simpler and says so, keeping the graph inspectable rather than matching the canonical matrix formulation.
Represent state as channels
import numpy as np
channels = 3
state = np.zeros((channels, 128, 128), dtype=np.float64)
Now each cell has a vector state:
[cell_0, cell_1, cell_2]
The channels do not need literal biological meanings.
They are interacting local fields.
A connection describes influence
We can describe one interaction as:
from dataclasses import dataclass
@dataclass(frozen=True)
class Connection:
source: int
target: int
kernel: np.ndarray
mu: float
sigma: float
weight: float = 1.0
This says:
read source channel
↓
apply this spatial kernel
↓
apply this growth response
↓
add weighted change to target channel
That is a small local network.
Compute several influences
def multi_channel_step(state, connections, dt=0.1):
delta = np.zeros_like(state)
for connection in connections:
kernel_f = kernel_fft(connection.kernel, state.shape[1:])
potential = np.fft.ifft2(
np.fft.fft2(state[connection.source]) * kernel_f
).real
response = gaussian_growth(
potential,
connection.mu,
connection.sigma,
)
delta[connection.target] += connection.weight * response
return np.clip(state + dt * delta, 0.0, 1.0)
The growth call reuses Chapter 30’s owner — no fourth name for the same bump. The kernel transform reuses Chapter 31’s. (Verified: two-channel cross-coupled runs stay finite; inhibition visibly suppresses its target channel while excitation saturates its own — coupling does work, in both directions.)
For repeated simulation we should precompute each kernel FFT rather than rebuilding it every step — each connection costs a forward/inverse FFT pair per step, so cost scales with connections, not just grid size.
Cross-channel interaction
Suppose:
channel 0 encourages channel 1
channel 1 suppresses channel 0
We can express that with two connections.
The result can create feedback loops:
A grows B
B suppresses A
A falls
B loses support
B falls
A can recover
Local feedback creates temporal structure as well as spatial structure.
Multiple spatial scales
Different kernels can operate at different radii:
short-range excitation
long-range inhibition
This is a recurring pattern in self-organizing systems.
For example:
short_kernel = ring_kernel(radius=8, ring_center=0.4, ring_width=0.12)
long_kernel = ring_kernel(radius=20, ring_center=0.6, ring_width=0.18)
Now a cell can respond differently to nearby and distant activity.
Think in terms of a graph
With several channels and connections, the rule can be visualized as a graph — edges, not prose, carry the topology:
flowchart LR
A0[channel 0] -->|K0 self| A0
A0 -->|K1 excite| B1[channel 1]
B1 -->|K2 inhibit| A0
Each edge carries:
kernel
growth parameters
weight
That is much easier to inspect than one giant function containing all interactions. Connection patterns and their dynamical roles:
| Pattern | Construction | Dynamical role |
|---|---|---|
| self-loop (0→0) | kernel on own channel | self-maintenance of one field |
| excitation (0→1, +weight) | cross-kernel, positive | recruit another field |
| inhibition (1→0, −weight) | cross-kernel, negative | suppress, bound growth |
| feedback pair | excite + inhibit loop | oscillation, regulation |
Precompute an execution plan
@dataclass
class PreparedConnection:
source: int
target: int
kernel_f: np.ndarray
mu: float
sigma: float
weight: float
def prepare_connections(connections, shape):
return [
PreparedConnection(
source=c.source,
target=c.target,
kernel_f=kernel_fft(c.kernel, shape),
mu=c.mu,
sigma=c.sigma,
weight=c.weight,
)
for c in connections
]
The simulation loop should execute prepared data, not repeatedly reconstruct model structure.
This is the same distinction between configuration and runtime representation that appears in larger software systems.
Visualize channels separately
import matplotlib.pyplot as plt
for channel in range(state.shape[0]):
plt.figure(figsize=(4, 4))
plt.imshow(state[channel], vmin=0, vmax=1)
plt.title(f"channel {channel}")
plt.axis("off")
plt.show()
Also create composites:
rgb = np.moveaxis(state[:3], 0, -1)
plt.imshow(np.clip(rgb, 0, 1))
plt.axis("off")
plt.show()
A combined image can hide important internal dynamics, so always retain per-channel inspection.
Search becomes structural
Our earlier parameter search varied numbers.
Now we might also vary:
number of channels
number of connections
source/target topology
kernel radii
connection weights
The search space is no longer just numerical.
It includes architecture.
That is a useful preview of neural cellular automata, where the local update rule itself will become learned.
Avoid unnecessary biological claims
Multiple channels can produce behaviors that look tissue-like or organism-like.
That does not mean each channel corresponds to a chemical, cell type or biological pathway.
Keep the interpretation disciplined, with this part’s guardrails stated plainly:
observed:
multiple interacting local fields produce persistent morphology
not automatically established:
biological equivalence
more channels
≠ more intelligence
more parameters
≠ better model
richer morphology
≠ richer behavior
Artificial life becomes more interesting when we are precise about what has actually emerged.
Richer systems create a new question
A pattern may survive indefinitely under perfect conditions.
But is it robust?
What happens if we:
delete part of it
inject noise
change parameters slightly
collide it with another structure
Persistence under no disturbance is a weak test.
In the next chapter we will turn damage and recovery into measurable experiments and separate genuine robustness from lucky stability.
Research
Chan, B. W.-C. — Lenia and Expanded Universe (ALIFE 2020). The canonical reference for this chapter’s direction: higher dimensions, multiple kernels, and multiple channels as the generalization of single-kernel Lenia. Read it to see what the book’s connection-graph simplification stands in for — and what the full formulation adds. https://arxiv.org/abs/2005.03742
Chan, B. W.-C. — Lenia: Biology of Artificial Life (Complex Systems 28(3), 2019). The base system being generalized: single-kernel update, growth mapping, and the species catalog that multi-channel work extends. The fidelity baseline from Chapter 31, still the standard here. https://arxiv.org/html/1812.05433v3