Abelian Sandpile Model

A simulartor for Abelian Sandpile Model

Posted by Krystian Wojcicki on Wednesday, September 30, 2020 Tags: Tutorial   10 minute read

Recently while reading an article I was introduced to the Abelian sandpile model. The article and wikipedia page do a great job introducing the model, The Abelian sandpile; a mathematical introduction serves as a good tool for understanding the deeper mathematics behind the model. For fun I coded out an Abelian sandpile model simulator that lets you play around some of the sandpiles properties.

Black indicates there are 0 grains, red indicates 1 grain, blue indicates 2 grains, green indicates 3 grains.






The code for the above simulation is as follows:

var width = 350;
var height = 350;
var stop = false;
var drawingTime = null;
var sand;
var oldSand;
var redraw_rate = 500;

const colors = {
  0: "black",
  1: "red",
  2: "blue",
  3: "green",
};

var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");

document.getElementById("height").value = height + "";
document.getElementById("width").value = width + "";

function getRandomInt(max) {
  return Math.floor(Math.random() * Math.floor(max));
}

function height_change() {
  console.log("new height:", document.getElementById("height").value);
  height = document.getElementById("height").value;
  restart();
}

function redraw_change() {
  redraw_rate = document.getElementById("redraw").value;
}

function width_change() {
  console.log("new width:", document.getElementById("width").value);
  width = document.getElementById("width").value;
  restart();
}

function stop_sim() {
  stop = true;
  clearTimeout(drawingTime);
}

function restart() {
  clearTimeout(drawingTime);
  ctx.fillStyle = "black";

  start_round = new Function(document.getElementById("add").value)();

  sand = new Array(height);
  oldSand = new Array(height);

  for (var i = 0; i < height; i++) {
    sand[i] = new Array(width);
    oldSand[i] = new Array(width);
    for (var j = 0; j < width; j++) {
      sand[i][j] = 0;
      oldSand[i][j] = 0;
    }
  }

  for (var i = 0; i < height; i++) {
    for (var j = 0; j < width; j++) {
      ctx.fillRect(i * 2, j * 2, 2, 2);
    }
  }

  var i = 0;

  function run() {
    if (stop) return;
    start_round(height, width, sand);
    i++;
    if (i % redraw_rate == 0) {
      console.log("done step", i);
      drawingTime = setTimeout(function () {
        draw();
        run();
      }, 2000);
    } else {
      run();
    }
  }

  run();
}

restart();

function draw() {
  for (var i = 0; i < height; i++) {
    for (var j = 0; j < width; j++) {
      if (sand[i][j] != oldSand[i][j]) {
        const toColor = colors[sand[i][j]] || "yellow";
        ctx.fillStyle = toColor;
        ctx.fillRect(i * 2, j * 2, 2, 2);
      }
    }
  }

  oldSand = sand.map(function (arr) {
    return arr.slice();
  });
}

function add_sand(x, y, inc) {
  if (x < 0 || y < 0 || x >= height || y >= width) return;

  sand[x][y] += inc;

  while (sand[x][y] >= 4) {
    //console.log(x, y, sand[x][y])
    sand[x][y] -= 4;
    add_sand(x + 1, y, 1);
    add_sand(x - 1, y, 1);
    add_sand(x, y + 1, 1);
    add_sand(x, y - 1, 1);
  }
}