cacatoo
Version:
Building, exploring, and sharing spatially structured models
332 lines (282 loc) • 11.9 kB
HTML
<html>
<script src="../../dist/cacatoo.js"></script> <!-- Include cacatoo library (compiled with rollup) -->
<script src="../../lib/all.js"></script> <!-- Load other packages -->
<link rel="shortcut icon" type="image/jpg" href="../../patterns/cacatoo.png"/>
<link rel="stylesheet" href="../../style/cacatoo.css"> <!-- Set style sheet -->
<script>
/*-----------------------Start user-defined code ---------------------*/
let sim;
var draw_cells = 1
// Phage parameters
var mu_phages = 0.01
var diff_rate_phages = 0.1
var burst_size = 20
var phage_decay = 0.005
// Host level parameters
var N = 1000
var mu_host = 0.01
var base_birth = 0.1
var range_helping = 5
var strength_helping = 0.002
var deathrate = 0.01
// Lock-and-key parameters
var bitstring_length = 16
function cacatoo() {
let simconfig = {
title: "Phage-host coevolution with bitstrings", // The name of your cacatoo-simulation
description: "", // And a description if you wish
maxtime: 100000, // How many time steps the model continues to run
// (note, the onscreen FPS may drop below 60 fps when using fast mode, although many more timesteps may be handled per second)
ncol: 100, // Number of columns (width of your grid)
nrow: 100, // Number of rows (height of your grid)
scale: 4, // Scale of the grid (nxn pixels per grid point)
sleep: 0,
wrap: [true,true],
graph_update: 20,
graph_interval: 2,
fpsmeter: false
}
// FLOCKCONFIG EXAMPLE
// This example sets up a boid simulation with specific values for the currently implemented parameters
// Note however, all these parameters have defaults, so not all need to be set by the user.
let flockconfig = {
// Flock parameters
num_boids: 0, // Starting number of boids (flocking individuals)
shape: 'dot', // Shape of the boids drawn (options: bird, arrow, line, rect, dot, ant)
click: 'kill', // Clicking the boids pushes them away from the mouse
max_speed: 1, // Maximum velocity of boids
max_force: 0.2, // Maximum steering force applied to boids (separation/cohesion/alignment rules)
init_velocity:0,
friction: 0.5, // Them ants are darn slippery :)
brownian: 0.1,
// Mouse parameters
mouse_radius: 30, // Radius of boids captured by the mouse overlay
draw_mouse_radius: 'true', // Show a circle where the mouse is
draw_mouse_colour: 'blue',
// Collision behaviour
physics: true,
collision_force: 0.1,
size: 1.5, // Size of the boids (scales drawing and colision detection)
// Optimalisation (speed) parameters
//qt_colour: "white", // Show quadtree (optimalisation by automatically tessalating the space)
qt_capacity: 3, // How many boids can be in one subspace of the quadtree before it is divided further
}
sim = new Simulation(simconfig) // Initialise the Cacatoo simulation
sim.makeGridmodel("environment") // Build a new Gridmodel within the simulation called "model"
sim.initialGrid(sim.environment,'num_phages',0 ,0.0)
for(let x=0; x < sim.ncol; x++){
for(let y=0; y < sim.nrow; y++){
sim.environment.grid[x][y].phages = []
sim.environment.grid[x][y].phages.push(new Phage())
sim.environment.grid[x][y].num_phages = sim.environment.grid[x][y].phages.length
}
}
sim.createDisplay_continuous({model:"environment", property:"num_phages", label:"Phage density (background colour)", // Createa a display for a continuous variable (ODE state for external resources)
minval:0, maxval:30, num_colours: 100, decimals: 2,
fill:"inferno", legend:true, legendlabel: "concentration"})
sim.makeFlockmodel("flock", flockconfig) // Add a flockmodel, which contains invidiuals (boids) in continuous space
sim.randomString = function(len){
let bs = new Int8Array(len).fill(0)
for(let i=0;i<len;i++){
if(sim.rng.random() < 0.0) bs[i] = 1
}
return(bs)
}
sim.reset = function(){
sim.flock.boids = []
sim.flock.populateSpot(N/10,sim.nr/2,sim.nc/2,50)
for(let boid of sim.flock.boids) {
boid.fill="yellow"
boid.bitstring = sim.randomString(bitstring_length)
//console.log(boid.bitstring)
}
}
sim.reset()
sim.createFlockDisplay("flock", {
addToDisplay:sim.canvases[0],
legend: true,
legendlabel: "internal resources",
strokeStyle: "white",
strokeWidth: 1,
minval:0, maxval:100, num_colours: 200, nticks:3, decimals:0})
sim.environment.update = function(){
this.synchronous() // Applied as many times as it can in 1/60th of a second
this.apply_async(this.diffuse_phages)
let sum_phages = 0
for (let x = 0; x < this.nc; x++) // x are columns
for (let y = 0; y < this.nr; y++) // y are rows
{
sum_phages += sim.environment.grid[x][y].phages.length
}
this.plotArray(["Phage population size"],
[sum_phages],
["red"],
"Population size phages")
this.plotArray(["Bacterial population size"],
[sim.flock.boids.length],
["gold"],
"Population size host")
}
sim.environment.nextState = function(x,y) {
for(let p = 0; p < sim.environment.grid[x][y].phages.length; p++){
if(sim.rng.random() < phage_decay){
sim.environment.grid[x][y].phages.splice(p, 1)
}
}
sim.environment.grid[x][y].num_phages = sim.environment.grid[x][y].phages.length
}
sim.environment.diffuse_phages = function (x, y) {
movePhage = function (k, direction) {
let coords = sim.environment.moore[direction]
let target = sim.environment.getGridpoint(coords[0] + x, coords[1] + y)
target.phages.push(sim.environment.grid[x][y].phages[k])
sim.environment.grid[x][y].phages.splice(k, 1)
// target.num_phages++
// sim.environment.grid[x][y].num_phages--
}
for (let k = 0; k < sim.environment.grid[x][y].phages.length; k++) {
let randomnr = sim.environment.rng.genrand_real1()
if (randomnr < diff_rate_phages / 4) movePhage(k, 1)
else if (randomnr < 2 * diff_rate_phages / 4) movePhage(k, 2)
else if (randomnr < 3 * diff_rate_phages / 4) movePhage(k, 3)
else if (randomnr < 4 * diff_rate_phages / 4) movePhage(k, 4)
}
}
sim.flock.update = function(){
for(let boid of this.boids) {
// Bacteria reproduction
let numhelpers = this.getIndividualsInRange(boid.position, range_helping).length
let birthrate = (base_birth+ numhelpers*strength_helping)*(1-this.boids.length/N)
if(sim.rng.random() < birthrate){
let newboid = this.copyBoid(boid)
newboid.bitstring = new Int8Array(bitstring_length).fill(0)
for(let i=0; i< newboid.bitstring.length; i++) {
newboid.bitstring[i] = (sim.rng.random() > mu_host) ? boid.bitstring[i] : !boid.bitstring[i]
}
//console.log(newboid.bitstring.join(''))
let angle = sim.rng.random()*Math.PI*2
newboid.position.x+= 0.5*boid.size*Math.cos(angle)
newboid.position.y+= 0.5*boid.size*Math.sin(angle)
newboid.position.x = (newboid.position.x+sim.ncol)%sim.ncol
newboid.position.y = (newboid.position.y+sim.nrow)%sim.nrow
this.boids.push(newboid)
// if(this.inBounds(newboid))
// this.boids.push(newboid)
}
// Bacteria death
const index = this.boids.indexOf(boid);
if(sim.rng.random() < deathrate){
this.boids.splice(index,1)
}
else{ // Bacteria infection
//console.log(boid.position.x,boid.position.y)
let x = Math.round(boid.position.x)%100
let y = Math.round(boid.position.y)%100
//console.log(x,y)
let new_phages = []
for(let p of sim.environment.grid[x][y].phages){
let hamming_distance = 0
for(let i=0; i < p.bitstring.length; i++){
hamming_distance += p.bitstring[i] != boid.bitstring[i]
}
//console.log(boid.bitstring.join(''))
//console.log(p.bitstring.join(''))
//console.log(hamming_distance)
if(hamming_distance <= 1){
//console.log("KILL!")
this.boids.splice(index,1)
for(let b=0;b<burst_size; b++){
new_phages.push(new Phage(p))
}
}
}
sim.environment.grid[x][y].phages.push(...new_phages)
}
}
if(sim.time %50 == 0) console.log(`Simulation step ${sim.time} has ${sim.flock.boids.length} cells`)
}
sim.start()
sim.addButton("Pause", function () { sim.toggle_play() })
sim.addButton("Restart", function () { sim.reset() })
sim.addToggle("draw_cells", "Show bacteria", function(){ sim.flock.draw = !sim.flock.draw})
sim.addHTML("form_holder","<br>")
//sim.addSlider("range_helping",2,50,1,"Range helping")
sim.addSlider("mu_phages",0.00,0.1,0.001,"Mutation rate (phages)")
sim.addSlider("mu_host",0.001,0.1,0.001,"Mutation rate (host)")
sim.addHTML("form_holder","<br>")
sim.addSlider("diff_rate_phages",0.00,0.2,0.001,"Diffusion phages")
sim.addSlider("burst_size",2,20,1,"Burst size")
sim.addSlider("phage_decay",0.001,0.1,0.001,"Phage decay")
// var mu_phages = 0.01
// var diff_rate_phages = 0.1
// var burst_size = 20
// var phage_decay = 0.005
// // Host level parameters
// var N = 1000
// var mu_host = 0.01
}
class Phage {
// Gene constructor
constructor(parent) {
if (parent instanceof Phage) {
this.uid = phageIDs.next().value
this.generation = parent.generation
this.mutations = parent.mutations
this.bitstring = new Int8Array(bitstring_length).fill(0)
for(let i=0; i< this.bitstring.length; i++) {
this.bitstring[i] = (sim.rng.random() > mu_phages) ? parent.bitstring[i] : !parent.bitstring[i]
}
}
else{
this.generation = 0
this.mutations = 0
this.len = bitstring_length
this.uid = phageIDs.next() // Just so it has a unique identifier, no biological function
this.bitstring = this.newBitstring(bitstring_length)
}
}
newBitstring(len){
let bs = new Int8Array(len).fill(0)
for(let i=0;i<len;i++){
if(sim.rng.random() < 0.5) bs[i] = 1
}
return bs
}
copy() {
return new Phage(this.len)
}
}
/**
* Shuffles array in place.
* @param {Array} a items An array containing the items.
*/
function shuffle(a) {
var j, x, i;
for (i = a.length - 1; i > 0; i--) {
j = Math.floor(sim.rng.genrand_real2() * (i + 1));
x = a[i];
a[i] = a[j];
a[j] = x;
}
return a;
}
function* idGenerator() {
let id = 1;
while (true) {
yield id
id++
}
}
const phageIDs = idGenerator()
const hostIDs = idGenerator()
</script>
<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>