Marvyn Bailly

PhD Candidate in Applied Mathematics & Scientific Computation

Slime Mold Simulation

Emergent Behavior from Simple Rules

This project simulates the behavior of slime mold (Physarum polycephalum) using simple agent-based rules. Thousands of particles follow pheromone trails, creating beautiful organic patterns that emerge naturally from local interactions.

Inspiration

Physarum polycephalum is a single-celled organism that exhibits remarkable problem-solving abilities. Despite having no brain, it can find the shortest path between food sources and has even been used to model efficient transportation networks.

This simulation recreates its behavior computationally, demonstrating how complex patterns can emerge from simple rules.

A massive thanks to the incredible work of Sebastian Lague. He has provided me with so much inspiration. His original implementation can be found here and was the main inspiration for this project.

The Agent Model

Each "slime" agent is surprisingly simple - it only knows two things:

Agent Class

class Agent {
    PVector position;
    float angle;
    
    Agent(float x, float y, float a) {
        position = new PVector(x, y);
        angle = a;
    }
}

That's it! We just position and direction.

The Simulation Loop

Each frame, every agent performs three steps:

  1. Sense - Check pheromone levels at three sensors
  2. Steer - Turn toward the direction with highest concentration
  3. Move - Take a step forward and deposit pheromone

Main Loop

void draw() {
    background(0);
    
    // Agent loop
    for (Agent a : agents) {
        steer(a);              // Decide direction
        int index = a.update(); // Move forward
        displayArray[index] = 255; // Deposit pheromone
    }
    
    processDisplay();  // Diffuse and evaporate
    updateDisplay();   // Render to screen
}

Sensing: The Three-Sensor Model

Each agent has three "sensors" that detect pheromone concentration:

Sensing Function

float sense(Agent a, float sensorAngleOff) {
    // Calculate sensor position
    float sensorAngle = a.angle + sensorAngleOff;
    PVector sensorDir = new PVector(cos(sensorAngle), sin(sensorAngle));
    PVector sensorCenter = sensorDir.mult(sensorDst).add(a.position.copy());
    
    // Sum pheromone in sensor area
    float sum = 0;
    for (int xOff = -sensorSize; xOff <= sensorSize; xOff++) {
        for (int yOff = -sensorSize; yOff <= sensorSize; yOff++) {
            PVector sample = new PVector(sensorCenter.x + xOff, 
                                         sensorCenter.y + yOff);
            if (inBounds(sample)) {
                sum += displayArray[getIndex(sample)];
            }
        }
    }
    return sum;
}

The sensor samples a small area (determined by sensorSize) at a distance sensorDst away from the agent.

Steering: Following the Trail

The steering logic compares the three sensor readings and decides which way to turn:

Steering Decision Tree

void steer(Agent a) {
    float weightForward = sense(a, 0);
    float weightLeft = sense(a, sensorAngle);
    float weightRight = sense(a, -sensorAngle);
    
    float randomSteer = random(1);
    
    // If forward is strongest, go straight
    if (weightForward > weightLeft && weightForward > weightRight) {
        a.angle += 0;
    }
    // If forward is weakest, turn randomly
    else if (weightForward < weightLeft && weightForward < weightRight) {
        a.angle += 2 * (randomSteer - 0.5) * turnSpeed * dt;
    }
    // If left is stronger, turn left
    else if (weightLeft > weightRight) {
        a.angle -= randomSteer * turnSpeed * dt;
    }
    // If right is stronger, turn right
    else if (weightLeft < weightRight) {
        a.angle += randomSteer * turnSpeed * dt;
    }
}

Notice the randomness! Even when turning, agents add some random variation. This prevents the system from getting stuck in repetitive patterns.

Pheromone Dynamics

The pheromone field is where the magic happens. It's a 2D array where each cell stores a brightness value (0-255). Each frame, two processes occur:

Diffusion

Pheromones spread to neighboring cells using a 3×3 blur:

// Average of 9 neighboring cells
float blur = sum / 9;
// Blend original with blurred value
float diffuse = lerp(originalValue, blur, diffuseSpeed * dt);

Evaporation

Pheromones gradually fade over time:

// Reduce brightness, but don't go negative
float evap = max(0, diffuse - evapSpeed * dt);

The balance between diffusion and evaporation is critical. Too much diffusion creates uniform blur. Too much evaporation erases trails before agents can follow them.

Key Parameters

Parameter Value Effect
colonySize 5000 Number of agents in simulation
sensorAngle 6 Angle offset for left/right sensors (radians)
sensorDst 10 How far ahead sensors look (pixels)
turnSpeed 0.5 Maximum turning rate per frame
diffuseSpeed 0.05 Rate of pheromone spreading
evapSpeed 0.8 Rate of pheromone fading

Emergent Patterns

The simulation produces several characteristic patterns:

Network Formation

Agents naturally organize into branching networks as they reinforce successful paths and abandon dead ends.

Pulsing Behavior

Dense regions can exhibit pulsing as agents cycle between following and dispersing.

Self-Avoiding Paths

With the right parameters, agents spread out to explore while maintaining connectivity.

Why It Works

The simulation demonstrates stigmergy - indirect coordination through environmental modification. Agents don't communicate directly; they leave traces that influence future behavior of themselves and others.

This principle appears throughout nature: ant colonies, termite mounds, and even human systems like footpaths through parks ("desire lines").

Technical Details

Original Code