colon.png

Colon cancer

This is a simple model of colon cancer growth in niches (colonic crypts). I was inspired by Mathematical modeling of cell population dynamics in the colonic crypt and in colorectal cancer by Matthew Johnston and colleagues.

Rates

Matthew's continuous model has 3 types of cells. Simplifying this to Stem and "Leaf" cells, I can write:

dStem/dt = (growth-change).Stem
dLeaf/dt = change.Stem - death.Leaf
initial: Stem = 1, Leaf = 0

Without spatial considerations, the populations grow rapidly. To balance this, we have to modify the rate term to:

(growth-change-high.Stem/(1+low.Stem)).Stem

This is difficult to justify biologically, and makes analysis complicated. 

Rate models (d/dt) are useful as a first step in describing a system. They can be manipulated, sometimes solved, and fairly well understood. But when we push a model too hard, the results aren't useful. The big simplification here is that individual cells form a continuous medium. Now we have computers, we can simulate individuals.

Cells

My simple model only has two healthy populations: stem and "leaf" cells — differentiated cell, or common population. The cells are spatially arranged, covering a grid. Stem cells divide regularly, about once every 50 cycles. Since there is no free space, for a cell to divide, a neighbour must be destroyed. When a stem cell divides, it mostly produces one stem cell and one leaf cell. Occasionally — 1 time in 20 —  it produces 2 stem cells. Leaf cells are totally inert, unable to divide. (To compare to the continuous model, this is roughly birth=20, change=19.)

I initiate the model with all stem cells (blue). The tissue settles down quickly to a homeostatic balance of stem cell niches surrounded by leaf cells (white). The simulation is below. To see the initial behaviour, reload the page to restart.

cancer.png

Cancer

I also have a second population: cancer stem cells and cancer leaf cells. They behave similarly to the healthy tissue — but the cancer propagates more aggressively. A cancer stem stem (red) is 10x more likely to divide than a healthy stem cell. This is the only difference. The cancerous stem cell only produces another stem cancer cell 1 in 20 divisions. Mostly it produces cancer leaf cells (pink).

I seed a blob of cancer in the middle of the field. The simple rules combined with the spatial restrictions produce a slow-growing cancer.

Slow growth

How does the cancer grow so slowly ? The leaf cells, both healthy and cancerous, are inert; only the stem cells are dynamic, so invasive. Cancerous stem cells can invade healthy tissue easily — but only when they have healthy tissue neighbouring them. There is also a slight push-back from the healthy tissue: 1/10 as often a healthy stem cell will invade the cancer.

Reality

This model is too simple to be much use. It deliberately ignores the folded nature of colonic crypts, and doesn't allow for the distortions of cancer — I imagined the crypts flattened-out. (If you want, you can also imagine the cancer growing into  the third dimension.) I made it to show that an incredibly simple cell model can produce robust, plausible behaviour.

 

Code in Processing

javascript variant p5

var blockSize = 10;
var grid = [];

var STEM = 0;
var LEAF = 1;
var STEMCANCER = 2;
var LEAFCANCER = 3;
var typeColor = [];

var fitness = [
  2,  // stem cells
  0,  // leaf cells
  20, // cancerous stem cells 
  0   // cancerous leaf cells
];

function setup() {
  var xGrid = round(windowWidth / blockSize);
  var yGrid = round(windowHeight / blockSize);
  var canvas = createCanvas(xGrid * blockSize, yGrid * blockSize);
  canvas.parent("container");
  noStroke();
  scale(blockSize);

  typeColor[STEM] = color(128, 213, 255);
  typeColor[LEAF] = color(255);
  typeColor[STEMCANCER] = color(176, 47, 28);
  typeColor[LEAFCANCER] = color(255, 192, 179);

  makeGrid(xGrid, yGrid);
  setNeighbors();
  drawAll();
}

function makeGrid(xGrid, yGrid) {
  for (var x = 0; x < xGrid; x++) {
    grid[x] = [];
    for (var y = 0; y < yGrid; y++) {
      // most of the tissue is healthy
      grid[x][y] = new Element(x, y, STEM);
      // blob of cancer cells in the middle
      if (dist(x, y, xGrid / 2, yGrid / 2) < 10) {
        grid[x][y].change(STEMCANCER);
      }
    }
  }
}

function setNeighbors() {
  for (var x = 0; x < grid.length; x++) {
    for (var y = 0; y < grid[0].length; y++) {
      grid[x][y].setNeighbors();
    }
  }
}

function drawAll() {
  for (var x = 0; x < grid.length; x++) {
    for (var y = 0; y < grid[0].length; y++) {
      grid[x][y].draw();
    }
  }
}

function draw() {
  scale(blockSize);
  for (var i = 0; i < grid.length * grid[0].length; i++) update();
}

function update() {
  var element = random(random(grid));

  // if sufficiently fit, assimilate a neighbor
  if (fitness[element.type] > random(100)) {
    if (element.type === STEM) {
      if (random(20) > 1) {
        // only 1 in 20 new stem cells don't differentiate
        random(element.neighbors).change(LEAF);
        return;
      }
    }
    if (element.type === STEMCANCER) {
      if (random(20) > 1) {
        random(element.neighbors).change(LEAFCANCER);
        return;
      }
    }
    random(element.neighbors).change(element.type);
  }
}

function Element(X, Y, t) {
  this.x = X;
  this.y = Y;
  this.neighbors = [];

  this.setNeighbors = function() {
    if (this.x > 0) // left
      this.neighbors.push(grid[this.x - 1][this.y]);
    if (this.y > 0) // down
      this.neighbors.push(grid[this.x][this.y - 1]);
    if (this.y < grid[0].length - 1) // up
      this.neighbors.push(grid[this.x][this.y + 1]);
    if (this.x < grid.length - 1) // right
      this.neighbors.push(grid[this.x + 1][this.y]);
  };

  this.draw = function() {
    fill(typeColor[this.type]);
    rect(this.x, this.y, 1, 1);
  };

  this.change = function(t) {
    this.type = t;
    this.draw();
  };

  this.change(t);
}