cacatoo
Version:
Building, exploring, and sharing spatially structured models
397 lines (337 loc) • 16.8 kB
HTML
<!--
Cooperation example for students
DO NOT CHANGE THE FIRST 5 LINES OF CODE BELOW, THEY ARE NECESSARY FOR THE PAGE TO WORK
-->
<html>
<script src="../../dist/cacatoo.js"></script> <!-- Include cacatoo library (compiled with rollup) -->
<script src="../../lib/all.js"></script> <!-- Load other packages -->
<link rel="stylesheet" href="../../style/cacatoo.css"> <!-- Set style sheet -->
<!--
CODE FOR SIMULATION BEGINS HERE
-->
<script>
// Initialisation
var sim; // This 'global' variable will hold the entire simulation
var init_nr_of_clones = 150
// Toxin parameters
var nr_of_toxins = 10
var toxin_shown = 1
var toxin_range = 3 // How far toxins can kill by diffusion
var base_death = 0.05 // Determines how often an individual spontaneously dies, making a new spot available
var base_fitness = 1.0
// Mutations
var gene_cost = 0.05
var hgt_range = 2 // 1 means cell-cell contact is required
var hgt_rate_resistance = 0.0001
var hgt_rate_production = 0.001
var gene_loss = 0.005
/**
* function cacatoo() contains all the user-defined parts of a cacatoo-model. Configuration, update rules, what is displayed or plotted, etc. It's all here.
*/
function cacatoo() {
/*
1. SETUP. First, set up a configuration-object. Here we define how large the grid is, how long will it run, what colours will the critters be, etc.
*/
let config = { // Configuration of your model. How large is the grid, how long will it run, what colours will the critters be, etc.
title: "Microbial warfare with HGT",
description: "",
maxtime: 100000,
ncol: 140,
nrow: 140, // dimensions of the grid to build
seed: 58,
fps: 60, // Note: FPS can only be set in fastmode
fpsmeter: false,
wrap: [true, true], // Wrap boundary [COLS, ROWS]
scale: 2, // scale of the grid (nxn pixels per grid point)
graph_interval: 10,
graph_update: 50,
num_colours: 15,
statecolours: {'clone': 'random', 'alive': { 1: 'black' }}
}
/*
1. SETUP. (continued) Now, let's use that configuration-object to generate a new Cacatoo simulation
*/
sim = new Simulation(config) // Initialise a new Simulation instance with configuration given above
sim.makeGridmodel("warfare") // Make a new gridmodel named cheater
//sim.createDisplay_discrete({drawdots:false,radius:2,model:"warfare", property:"clone", label:"Clone types"})
sim.createDisplay_continuous({model:"warfare", fill: "viridis", property:"numP", legend: true, label:"Num producing", minval:0, maxval:nr_of_toxins, num_colours:40})
sim.createDisplay_continuous({model:"warfare", fill: "viridis", property:"numR", legend: true, label:"Num resistant", minval:0, maxval:nr_of_toxins, num_colours:40})
sim.createDisplay_continuous({model:"warfare", fill: "viridis", property:"toxin_conc", legend: true, label:"Concentration toxins", minval:0, maxval:10, num_colours:40})
// Create a display for the toxin production genotype
sim.warfare.colourGradient("genotypeP", 200, [156, 79, 150], [255, 99, 85], // pangenome colours setup
[251, 169, 73], [250, 228, 66], [139, 212, 72], [42, 168, 242],
[50,100,255])
sim.createDisplay_continuous({model:"warfare", property:"genotypeP", legend: true, label:"Production profile", decimals: 0, nticks:5, maxval:200})
// Create a display for the toxin resistance genotype
sim.warfare.colourGradient("genotypeR", 200, [156, 79, 150], [255, 99, 85], // pangenome colours setup
[251, 169, 73], [250, 228, 66], [139, 212, 72], [42, 168, 242],
[50,100,255])
sim.createDisplay_continuous({model:"warfare", property:"genotypeR", legend: true, label:"Resistance profile", decimals: 0, nticks:5, maxval:200})
sim.initialise = function()
{
sim.warfare.clearGrid()
for (let x = 0; x < sim.warfare.nc; x++) // x are columns
for (let y = 0; y < sim.warfare.nr; y++)
{
sim.warfare.grid[x][y].toxins = Array(nr_of_toxins).fill(0)
}
for(let c=0;c<init_nr_of_clones;c++)
{
let resistance = Array(nr_of_toxins).fill(0)
let production = Array(nr_of_toxins).fill(0)
for(let i in resistance)
{
if(sim.rng.random() < 0.1)
{
resistance[i] = 1
production[i] = 1
}
}
pos_x = sim.rng.genrand_int(10,config.ncol-10)
pos_y = sim.rng.genrand_int(10,config.nrow-10)
let init_individuals = [{alive:1,clone:(c%15)+1, deathrate:base_death, production: production, resistance: resistance}]
sim.populateSpot(sim.warfare, init_individuals, [1.0], 10, pos_x, pos_y) // Place the three 'species' in grid points (33% A, 33% B, 33% C)
sim.display()
}
}
// Slightly modified version of "populateSpot", copied from source here: https://bramvandijk88.github.io/cacatoo/scripts/cacatoo.js
sim.populateSpot = function(gridmodel,individuals, freqs,size, putx, puty)
{
let sumfreqs =0;
if(individuals.length != freqs.length) throw new Error("populateGrid should have as many individuals as frequencies")
for(let i=0; i<freqs.length; i++) sumfreqs += freqs[i];
// Draw a circle
for (let x = 0; x < gridmodel.nc; x++) // x are columns
for (let y = 0; y < gridmodel.nr; y++) // y are rows
{
if ((Math.pow((x - putx), 2) + Math.pow((y - puty), 2)) < size)
{
let cumsumfreq = 0;
for(let n=0; n<individuals.length; n++)
{
cumsumfreq += freqs[n];
if(this.rng.random() < cumsumfreq) {
Object.assign(gridmodel.grid[x % gridmodel.nc][y % gridmodel.nr],individuals[n]);
getGenotypeColour(x%gridmodel.nc,y%gridmodel.nr)
break
}
}
}
}
}
sim.warfare.nextState = function (x, y) // Define the next-state function. This example is two mutualists and a cheater
{
// let pA, pB, pC, psum
let me = this.grid[x][y]
me.toxin_conc = me.toxins[toxin_shown-1]
if (!me.alive) // If there is no living cell here
{
let neighbours = this.getMoore8(this, x, y,'alive',1)
let winner = this.rouletteWheel(neighbours, 'fitness', 5.0)
if (winner != undefined)
{
me.alive = winner.alive
me.genotypeP = winner.genotypeP
me.genotypeR = winner.genotypeR
me.numR = winner.numR
me.numP = winner.numP
me.clone = winner.clone
me.deathrate = winner.deathrate
me.fitness = winner.fitness
me.resistance = [...winner.resistance]
let mut = false
for(let res in me.resistance) if(sim.rng.random() < gene_loss) me.resistance[res] = 0, mut = true
me.production = [...winner.production]
for(let tox in me.production) if(sim.rng.random() < gene_loss) me.production[tox] = 0, mut = true
mut = doHGT(x,y)
if(mut) getGenotypeColour(x,y)
}
}
if (this.rng.random() < this.grid[x][y].deathrate){ // Stochastic death (species become 0, which is an empty space for the next step to compete over)
this.grid[x][y].alive = 0
this.grid[x][y].fitness = 0
this.grid[x][y].clone = 0
this.grid[x][y].genotypeP = 0
this.grid[x][y].genotypeR = 0
this.grid[x][y].numP = 0
this.grid[x][y].numR = 0
}
}
getGenotypeColour = function (x, y){
me = sim.warfare.grid[x][y]
if(!me.alive) throw new Error("Only call this function on living points")
me.genotypeP = 0
me.numP = 0
me.genotypeR = 0
me.numR = 0
for(let t=0; t<nr_of_toxins; t++){
if(me.production[t] == 1) me.genotypeP += Math.pow(2,t+2), me.numP++
if(me.resistance[t] == 1) me.genotypeR += Math.pow(2,t+2), me.numR++
}
me.genotypeP = me.genotypeP % 200 + 1
me.genotypeR = me.genotypeR % 200 + 1
}
toxinDynamics = function(x,y)
{
let me = sim.warfare.grid[x][y]
if(me.alive)
{
for(let tox in me.production)
{
if(me.production[tox] == 1)
for(let ii=-toxin_range; ii<=toxin_range; ii++)
for(let jj=-toxin_range; jj<=toxin_range; jj++)
{
let spot = sim.warfare.getGridpoint(x+ii,y+jj)
spot.toxins[tox] += 0.1/(Math.abs(ii)+Math.abs(jj)+1)
}
}
}
for(let tox in me.toxins){
me.toxins[tox] *= 0.9
if(me.toxins[tox]<0.001) me.toxins[tox] = 0.0
}
}
calculateFitness = function(x,y)
{
let me = sim.warfare.grid[x][y]
if(me.alive)
{
me.fitness = base_fitness
for(let g in me.resistance)
{
if(me.resistance[g] == 1)
{
me.fitness -= gene_cost
} else {
if(me.toxins[g] > 0.5)
me.deathrate += 0.05
}
if(me.production[g] == 1)
{
me.fitness -= gene_cost
}
}
let resistance_profile = me.resistance
}
//sim.coop.sumMoore8(sim.coop, x, y, "helping_rate")-0.1*sim.coop.grid[x][y].helping_rate
}
doHGT = function(x,y){
me = sim.warfare.grid[x][y]
if(me.alive){
let hgt_happened = false
for(let ii=-hgt_range; ii<=hgt_range; ii++)
for(let jj=-hgt_range; jj<=hgt_range; jj++)
{
let donor = sim.warfare.getNeighbour(sim.warfare,x,y,sim.rng.genrand_int(1,8))
if(donor.alive){
for(let t=0; t<nr_of_toxins; t++){
if(donor.production[t] >0 && sim.rng.random() < hgt_rate_production) sim.warfare.grid[x][y].production[t] = 1, hgt_happened=true
if(donor.resistance[t] >0 && sim.rng.random() < hgt_rate_resistance) sim.warfare.grid[x][y].resistance[t] = 1, hgt_happened=true
}
}
}
if(hgt_happened) getGenotypeColour(x,y)
}
}
redistributeBarcodes = function(x,y){
me = sim.warfare.grid[x][y]
if(me.alive){
me.clone = Math.floor(sim.rng.random()*15)+1
}
}
/**
* Define your update-function here: stuff that is applied to the entire grid every timestep. E.g. apply the next-state, diffuse stuff, mix individuals, show graphs, etc.
*/
sim.warfare.update = function () {
if(this.time%sim.config.graph_interval==0) this.updateGraphs()
//this.apply_async(doHGT)
this.apply_async(toxinDynamics)
this.apply_sync(calculateFitness)
//this.diffuseStateVector('toxins',0.2)
this.asynchronous() // Update all grid points based on the next-state function (defined above)
// for(let i=0; i<movement;i++)this.MargolusDiffusion() // Every so often mix individuals a bit
if(this.time%10000==0) this.apply_sync(redistributeBarcodes)
}
/**
* OPTIONAL: add some graphs to show how your model progresses. Cacatoo currently supports three graph types, all of which are illustrated in this example
*/
sim.warfare.updateGraphs = function () {
// Update the plots. If the plot do not yet exist, a new plot will be automatically added by cacatoo
this.plotPopsizes('clone',[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20],{})
let g_profiles = {}
let p_profiles = {}
let r_profiles = {}
let pan_p = []
let pan_r = []
for (let x = 0; x < this.nc; x++) // x are columns
for (let y = 0; y < this.nr; y++) // y are rows
{
me = sim.warfare.grid[x][y]
if(me.alive){
for(let g in me.production) if(me.production[g]>0) pan_p[g]=1
for(let g in me.resistance) if(me.resistance[g]>0) pan_r[g]=1
let p = me.production.toString()
let r = me.resistance.toString()
g_profiles[p+r] = g_profiles[p+r] + 1 || 1
p_profiles[p] = p_profiles[p] + 1 || 1
r_profiles[r] = r_profiles[r] + 1 || 1
}
}
let num_pan_p = pan_p.reduce((sum,a) => sum+a,0)
let num_pan_r = pan_r.reduce((sum,a) => sum+a,0)
let num_g_profiles = 0
let filtered = Object.entries(g_profiles).filter(([k,v]) => v>10);
for(const [key,value] of Object.entries(g_profiles)){
if (value > 10) num_g_profiles++
}
let num_abu_p_profiles = 0
filtered = Object.entries(p_profiles).filter(([k,v]) => v>10);
for(const [key,value] of Object.entries(p_profiles)){
if (value > 10) num_abu_p_profiles++
}
let num_abu_r_profiles = 0
filtered = Object.entries(r_profiles).filter(([k,v]) => v>10);
for(const [key,value] of Object.entries(r_profiles)){
if (value > 20) num_abu_r_profiles++
}
this.plotArray(["Genotype richness", "Production profile diversity", "Resistance profile diversity "],
[num_g_profiles, num_abu_p_profiles,num_abu_r_profiles],
["grey", "black", "red"],
"Genotype richness and production/resistance profiles",{})
this.plotArray([ "Production (pan)",
"Resistance (pan)"],
[num_pan_p, num_pan_r],
["black","#FF0000"],
"Number of toxin/resistance genes in pangenome",{})
}
/**
* OPTIONAL: add some buttons and sliders so you can play with your model easily
*/
sim.addButton("Play / Pause", function () { sim.toggle_play() }) // Add a button that calls function "display" in "model"
//sim.addButton("Well mix", function () { sim.toggle_mix() }) // Add a button that calls function "perfectMix" in "model.cheater"
sim.addButton("Restart", function () {sim.initialise() })
sim.addHTML("form_holder","<br>")
sim.addSlider("toxin_shown", 1, nr_of_toxins, 1.00, "Show toxin")
sim.addSlider("hgt_rate_resistance", 0.00, 0.2, 0.001, "HGT rate for resistance genes")
sim.addSlider("hgt_rate_production", 0.00, 0.2, 0.001, "HGT rate for production genes")
sim.addSlider("gene_loss", 0.00, 0.2, 0.001, "Gene loss")
sim.addMovieButton(sim.warfare, "Clone types")
sim.initialise()
sim.start()
}
</script>
<!--
CODE FOR SIMULATION ENDS HERE, DO NOT CHANGE BEYOND THIS POINT
-->
<body onload="cacatoo()">
<div class="header" id="header">
<h2>Cacatoo </h2>
</div>
<div class="content" id="canvas_holder"></div>
<div class="content" id="form_holder"></div>
<div class="content" id="graph_holder"></div>
<div class="footer" id="footer">
</div>
</body>
</html>