UNPKG

cacatoo

Version:

Building, exploring, and sharing spatially structured models

332 lines (282 loc) 11.9 kB
<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>