DEFINITION
sense at −θ, 0, +θ, distance o ahead turn by ±α toward the strongest trail move forward, deposit, then the field diffuses and decays
NOTES
A slime mould has no nervous system and solves shortest-path problems anyway, and Jones showed in 2010 that almost nothing is required to reproduce it: a particle with three sensors in front, a rule that turns it toward whichever sensor smells strongest, and a trail it leaves behind that spreads and fades. The network is not computed. It is the leftover of many particles reinforcing each other’s paths faster than the field forgets them, and it goes on reorganising forever — lacunae contract, redundant links are abandoned, and where a path is cut the remaining ones thicken. The sensor angle and the turn angle are the whole parameter space, and Jones’ paper is largely a map of what different corners of it grow.
PROVENANCE
- Origin
- J. Jones, “Characteristics of Pattern Formation and Evolution in Approximations of Physarum Transport Networks”, Artificial Life 16(2), 2010, 127–153, doi:10.1162/artl.2010.16.2.16202
- Standing
- Public domain as published mathematics — no patent found tied to the agent model, which is *nothing found* rather than *nothing exists*: unlike the 1987 work elsewhere in this atlas, a 2010 filing could still be in force, and this was searched rather than assumed
- Not this
- Distinct from the Physarum network results of Tero and colleagues, which model the organism as an adaptive tube network rather than as particles. Same organism, different mathematics
- Constants
- Sensor angle and rotation angle are the map Jones’ paper draws. Equal angles give meshes; a wide sensor with a narrow turn gives long filaments
- Source
- doi:10.1162/artl.2010.16.2.16202
HOUDINI · VEX
The same published mathematics as a Detail Wrangle body. Paste it into a Wrangle with Run Over set to Detail; every constant is the published value plus a tweak channel, so Create Spare Parameters gives a slider that starts where the paper does.
// FORMA — PL. 94 · PHYSARUM TRANSPORT — Jeff Jones, 2010
// sense at −θ, 0, +θ, distance o ahead
// turn by ±α toward the strongest trail
// move forward, deposit, then the field diffuses and decays
// Paste into a Detail Wrangle (Run Over: Detail), no inputs needed.
// Written from the published mathematics, not adapted from any code.
// Constants arrive at their published values. Press the node's Create
// Spare Parameters button and every tweak becomes a slider — starting
// at 0, the published figure, and moving in the constant's own units.
// https://forma-gen.com/#plate=physarum
float p_agents = 2600 + chf('agents_tweak'); // particles · live 800 .. 5000
float p_sense = 22 + chf('sense_tweak'); // θ — sensor angle (°) · live 8 .. 60
float p_turn = 40 + chf('turn_tweak'); // α — rotation angle (°) · live 8 .. 80
float p_reach = 4 + chf('reach_tweak'); // o — sensor distance (cells) · live 2 .. 12
float p_decay = 0.075 + chf('decay_tweak'); // trail decay · live 0.02 .. 0.2
float p_field = 150 + chf('field_tweak'); // lattice — cells across · live 120 .. 240
// The plate's own colour: FORMA's LATTICES accent as a cosine ramp,
// brightest near t = 0 and t = 1, near-black around t = 0.5.
vector forma_ramp(float t){
return set(
0.46 + 0.5 * cos(6.28318530718 * (t + 0)),
0.4185 + 0.4549 * cos(6.28318530718 * (t + 0.05)),
0.1389 + 0.151 * cos(6.28318530718 * (t + 0.1)));
}
// Jones' particles run to a settled network, and the trail field emitted as a
// grid of points carrying its own concentration in Alpha — the network is the
// field, so the field is what leaves. Threshold on Alpha in Houdini to get the
// mesh alone; the full grid is here so nothing is decided for you.
//
// Three sensors in front, a turn by a fixed angle toward the strongest, a step
// forward, a deposit, and a field that spreads a little and forgets a little.
// The one-agent-to-a-site rule is model rather than optimisation: without it
// the chemotaxis is pure positive feedback and every particle piles onto the
// few paths that won early, which cooks three fat trunks and no network at
// all. Deterministic — the scatter is random(counted seed).
int n = int(rint(p_field)); // the lattice became a dial in 0.18
int N = int(rint(p_agents));
int forma_steps = 260; // where the plate reports the network settled
float forma_diff = 0.36; // part of the way to the 3x3 mean, per step
float SA = radians(p_sense), RA = radians(p_turn), SO = p_reach;
float keep = 1.0 - p_decay;
float fld[];
resize(fld, n * n);
int occ[];
resize(occ, n * n);
float ax[], ay[], ah[];
resize(ax, N); resize(ay, N); resize(ah, N);
int seed = 2010;
for (int i = 0; i < N; i++){
int at = -1;
while (at < 0){
int cx = int(random(seed) * float(n)); seed++;
int cy = int(random(seed) * float(n)); seed++;
cx = clamp(cx, 0, n - 1); cy = clamp(cy, 0, n - 1);
int k = cy * n + cx;
if (occ[k] == 0){ occ[k] = 1; at = k; ax[i] = float(cx) + 0.5; ay[i] = float(cy) + 0.5; }
}
ah[i] = random(seed) * 2.0 * PI; seed++;
}
for (int s = 0; s < forma_steps; s++){
for (int i = 0; i < N; i++){
float x = ax[i], y = ay[i], hd = ah[i];
// the three sensors, read straight out of the field with a wrap
float sxc = x + cos(hd) * SO, syc = y + sin(hd) * SO;
float sxl = x + cos(hd - SA) * SO, syl = y + sin(hd - SA) * SO;
float sxr = x + cos(hd + SA) * SO, syr = y + sin(hd + SA) * SO;
int cxi = int(floor(sxc)) % n; if (cxi < 0) cxi += n;
int cyi = int(floor(syc)) % n; if (cyi < 0) cyi += n;
int lxi = int(floor(sxl)) % n; if (lxi < 0) lxi += n;
int lyi = int(floor(syl)) % n; if (lyi < 0) lyi += n;
int rxi = int(floor(sxr)) % n; if (rxi < 0) rxi += n;
int ryi = int(floor(syr)) % n; if (ryi < 0) ryi += n;
float cc = fld[cyi * n + cxi];
float cl = fld[lyi * n + lxi];
float cr = fld[ryi * n + rxi];
// a fixed turn toward the winner, never proportional to the difference:
// that discreteness is the published rule and is what lets a filament
// hold an edge instead of easing round it
if (cc >= cl && cc >= cr){ }
else if (cl > cr) hd -= RA;
else if (cr > cl) hd += RA;
else { hd += (random(seed) < 0.5) ? -RA : RA; seed++; }
float nx = x + cos(hd), ny = y + sin(hd);
if (nx < 0) nx += float(n);
if (nx >= float(n)) nx -= float(n);
if (ny < 0) ny += float(n);
if (ny >= float(n)) ny -= float(n);
int fromk = int(y) * n + int(x);
int tok = int(ny) * n + int(nx);
if (tok != fromk && occ[tok] == 1){
// blocked: stay put and take a new heading, which is what pushes
// the exploration outward and keeps the network a network
ah[i] = random(seed) * 2.0 * PI; seed++;
continue;
}
occ[fromk] = 0; occ[tok] = 1;
ax[i] = nx; ay[i] = ny; ah[i] = hd;
fld[tok] += 1.0;
}
float tmp[];
resize(tmp, n * n);
for (int y = 0; y < n; y++){
int up = ((y - 1 + n) % n) * n, mid = y * n, dn = ((y + 1) % n) * n;
for (int x = 0; x < n; x++){
int xl = (x - 1 + n) % n, xr = (x + 1) % n;
float mean9 = (fld[up + xl] + fld[up + x] + fld[up + xr] +
fld[mid + xl] + fld[mid + x] + fld[mid + xr] +
fld[dn + xl] + fld[dn + x] + fld[dn + xr]) / 9.0;
tmp[mid + x] = (fld[mid + x] + (mean9 - fld[mid + x]) * forma_diff) * keep;
}
}
fld = tmp;
}
// Logarithmic, because the field's spread is an order of magnitude rather than
// a range — a linear map puts most of the network at full brightness and
// throws the structure away.
float span = log(1.0 + 4.0);
for (int y = 0; y < n; y++){
for (int x = 0; x < n; x++){
float m = min(log(1.0 + fld[y * n + x]) / span, 1.0);
// canvas y runs down, so the lattice row is negated
int pt = addpoint(0, set(float(x), -float(y), 0.0));
setpointattrib(0, "Cd", pt, forma_ramp(0.82 + m * 0.26));
setpointattrib(0, "Alpha", pt, m);
}
}