Physarum network

Implementing Physarum Simulations: Code Tutorial

Physarum, or slime mold, simulations illustrate how simple agents, driven by chemotaxis, can create complex emergent networks through purely local interactions and they offer a vast space for exploration. By adjusting model parameters an almost limitless variety of network structures can be generated. These simulations are built around multi-agent chemotaxis a technique that is useful in many artificial life and optimization applications.

In these simulations groups of agents collectively discover efficient network structures, offering practical insights for solving optimization problems in routing, logistics, and network design. Both Physarum simulations, and living slime molds, have been used for optimizing train networks and solving mazes.

The simulation is built using two key programming components: a large group of agents employing chemotaxis to self organize and a 2D data structure that manages the chemical ‘scent’ the agents track.

Figure 1: One agent using chemotaxis to follow the highest ‘scent’ gradient.

Figure 1 illustrates the basic concept of chemotaxis: an agent equipped with multiple forward-facing sensors detects the concentration of a chemical scent at various points ahead of itself. The agent turns toward the strongest scent. This strategy is commonly used by both real bacteria and agents in artificial life simulations to locate food or mates, avoid dangers, and achieve other objectives. In the Physarum simulation, agents also deposit scent as they move. With thousands or even tens of thousands of agents acting simultaneously, they converge on shared paths, and complex, optimized, networks emerge from their collective behavior.

Below is the code for the Physarum project featured in the YouTube video linked above. It is written in p5.js (JavaScript) mode within Processing. You can download Processing for free at Processing.org. The first section of the code defines the Agent class.

class Agent{  
  constructor(){
    this.pos = createVector(random(width),random(height));
    this.v = p5.Vector.fromAngle(random(2*PI));
    this.v.setMag(cellSize*0.5);
    this.senseA = 0.2*PI;
    this.senseL = 8*cellSize;
    this.turnA = 0.2*PI;
  }
  
  update(){
    let scentL = this.getScent(this.senseA);
    let scentR = this.getScent(-this.senseA);
    let scentC = this.getScent(0);
    if(scentL > scentR && scentL > scentC){
      this.v.rotate(this.turnA);
    }
    if(scentR > scentL && scentR > scentC){
      this.v.rotate(-this.turnA);
    }
    this.pos.add(this.v);
    this.pos.x = ((this.pos.x+width)%width);
    this.pos.y = ((this.pos.y+height)%height);  
    this.placeScent();
  }
  
  placeScent(){
    let x = Math.floor(this.pos.x/cellSize);
    x = x %gridWidth;
    let y = Math.floor(this.pos.y/cellSize);
    y = y % gridHeight;
    scent[y][x] += scentAmount;
  }
  
  getScent(a){
    let x = this.pos.x+this.senseL*cos(this.v.heading()+a);
    let y = this.pos.y+this.senseL*sin(this.v.heading()+a);
    x = (x+width)%width;
    y = (y+height)%height;
    x = Math.floor(x/cellSize);
    x = x % gridWidth;
    y = Math.floor(y/cellSize);
    y = y % gridHeight;
    return(scent[y][x]);
  }
  
  display(){
    //stroke(0);
    noStroke();
    fill(300,100,100);
    push();
    translate(this.pos.x,this.pos.y);
    rotate(this.v.heading());
    rect(0,0,cellSize*2,cellSize*2);
    line(0,0,cellSize*4,0);
    pop();
  }
}

Below is the main body of the code, including the code for setting up the program and it’s parameters and updating and displaying the scent.


let agents = [];
let scent = [];
let gridWidth;
let gridHeight;
let cellSize = 8;
let scentAmount = 10;
let scentFade = 0.94;
let kernel = [[1,1,1],
              [1,4,1],
              [1,1,1]];
let kernelC = 12;
let showAgent = true;

function setup() {
  createCanvas(800,900);
  rectMode(CENTER);
  colorMode(HSB,360,100,100,100);
  gridWidth = Math.floor(width/cellSize)+1;
  gridHeight = Math.floor(height/cellSize)+1;
  for(let r = 0; r < gridHeight; r++){
    scent[r] = [];
    for(let c = 0; c < gridWidth; c++){
      scent[r][c] = random(100);
    }
  }
  for(let i = 0; i < gridWidth*gridHeight*0.6; i++){
    agents.push(new Agent());
  }
}

function draw() {
  //background(200);
  //fill(120,100,100,10);
  //rect(width*0.5,height*0.5,width,height);
  for(let a of agents){
    a.update();
  }
  fadeScent();
  diffuseScent();
  displayScent();
  if(showAgent){
    for(let a of agents){
      a.display();
    }
  }
}

function keyPressed(){
  if(key == ' '){
    showAgent = !showAgent;
  }
}

function diffuseScent(){
  let temp=[];
  for(let r = 0; r < gridHeight; r++){
    temp[r] = [];
    for(let c = 0; c < gridWidth; c++){
      temp[r][c] = 0;
    }
  }
  for(let r = 0; r < gridHeight; r++){
    for(let c = 0; c < gridWidth; c++){
      temp[r][c] = avg(r,c);
    }
  }
  for(let r = 0; r < gridHeight; r++){
    for(let c = 0; c < gridWidth; c++){
      scent[r][c] = temp[r][c];
    }
  }
}

function avg(r,c){
  let a = 0;
  for(let dr = -1; dr <=1; dr++){
    for(let dc = -1; dc <=1; dc++){
      let tempR = ((r+dr) + gridHeight) % gridHeight;
      let tempC = ((c+dc) + gridWidth) % gridWidth;
      a += scent[tempR][tempC] * kernel[dr+1][dc+1];
    }
  }
  a /= kernelC;
  return a;
}

function fadeScent(){
  for(let r = 0; r < gridHeight; r++){
    for(let c = 0; c < gridWidth; c++){
      scent[r][c] *= scentFade;
    }
  }
}

function displayScent(){
  noStroke();
  for(let r = 0; r < gridHeight; r++){
    for(let c = 0; c < gridWidth; c++){
      fill(120,scent[r][c],scent[r][c]);
      rect(c*cellSize,r*cellSize,cellSize,cellSize);
    }
  }
}

The Physarum simulation features several key parameters that greatly influence the formation and type of network that develops. These include the angle and length of the agents’ sensors, the agents’ turning angle, the amount of scent each agent deposits, the rate at which the scent fades, the speed of scent diffusion, and the total number of agents involved.

Beyond these built-in parameters, numerous other features of the simulation can be explored. Agents can be equipped with more than three sensors. Multiple types of agents can coexist, each depositing its own scent that may attract or repel other agents. Parameters can vary across the environment; for instance, the turning angle can depend on the agent’s x or y position, resulting in networks that differ throughout the space. Agents’ starting locations can be modified. Additionally, ‘food’ sources that emit scent and attract agents can be introduced, better simulating how slime molds use these models to form feeding networks.

And on and on. If you create your own novel variations please comment below.

Happy Coding!

Leave a Reply

Your email address will not be published. Required fields are marked *

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *