cacatoo
Version:
Building, exploring, and sharing spatially structured models
355 lines (301 loc) • 17.9 kB
HTML
<html>
<script src="https://bramvandijk88.github.io/cacatoo/scripts/cacatoo.js"></script> <!-- Include cacatoo library (compiled with rollup) -->
<script src="https://bramvandijk88.github.io/cacatoo/scripts/all.js"></script> <!-- Include other libraries (concattenated in 1 file) -->
<script src="./PoS_classes.js"></script>
<link rel="stylesheet" href="https://bramvandijk88.github.io/cacatoo/styles/cacatoo.css"> <!-- Set style sheet -->
<script>
/*-----------------------Start user-defined code ---------------------*/
let sim;
// PARAMETERS FOR FITNESS DEFINITION
var init_es = 10 // es = essential --- if even a single one is missing = 0.0 fitness
var init_ne = 0 // ne = non-essential --- for each non-essential genes gain "non_essential_gene_boon" fitness
var init_nc = 30
var init_tra_rate = 0.85
var transposon_fitness_cost = 0.005
var non_essential_gene_boon = 0.1
// PARAMETERS FOR ECOLOGY
var death_rate = 0.02
var non = 100
// PARAMETERS FOR EVOLUTION
var gene_inactivation_rate = 0.001
var gene_deletion_rate = 0.001
var gene_duplication_rate = 0.001
var phi_mutation_rate = 0.01
// PARAMETERS FOR EDNA POOL
var degr_rate_edna = 0.02
var diff_rate_edna = 0.01
// PARAMETERS FOR TRANSPOSON DYNAMICS
var probability_TE_induced_damage = 1.0
var uptake_from_pool = 0.02
var jump_rate = 0.02
var size = 120
var scale = 3
var address_query = {}
location.search.substr(1).split("&").forEach(function (item) { address_query[item.split("=")[0]] = item.split("=")[1] })
if (address_query.size) size = Math.min(address_query.size, 500)
if (size > 300) scale = 1
else if (size > 200) scale = 2
function cacatoo() {
let config = {
title: "TEs and their host",
description: "",
maxtime: 200000,
ncol: size,
seed: 5,
nrow: size, // dimensions of the grid to build
wrap: [true, true], // Wrap boundary [COLS, ROWS]
scale: scale, // scale of the grid (nxn pixels per grid cell)
graph_interval: 10,
statecolours: { alive: { 1: 'blue' } } // The background state '0' is never drawn
}
sim = new Simulation(config)
sim.makeGridmodel("TE_model");
sim.createDisplay_continuous({model:"TE_model", property:"genomesize", label:"Genome size", minval:0, maxval:100, fill:"viridis"})
sim.createDisplay_continuous({model:"TE_model", property:"T_in_genomes", label:"TEs inside genomes", minval:0, maxval:40, fill:"inferno"})
sim.createDisplay_continuous({model:"TE_model", property:"T_in_eDNA", label:"TEs in eDNA pool", minval:0, maxval:40, fill:"inferno"})
sim.TE_model.initialise = function () {
sim.initialGrid(sim.TE_model, "alive", 0, 0, 1.0)
sim.initialGrid(sim.TE_model, "genomesize", 0,undefined, 1.0)
sim.initialGrid(sim.TE_model, "T_in_eDNA", 0,undefined, 1.0)
sim.initialGrid(sim.TE_model, "T_in_genomes",0,undefined, 1.0)
sim.max_g = 100
sim.max_t = 100
sim.TE_model.colourViridis("genomesize", sim.max_g)
sim.TE_model.colourViridis("T_in_genomes", sim.max_t)
sim.TE_model.colourViridis("T_in_eDNA", sim.max_t)
sim.mixDNApool = false
this.resetPlots()
placeCell = function (gp, init_es, init_ne, init_nc, init_tra, init_tra_rate) {
gp.alive = 1
gp.genome = new Genome()
gp.genome.initialise(init_es, init_ne, init_nc, init_tra, init_tra_rate)
gp.genomesize = Math.min(sim.max_g, gp.genome.chromosome.length) // A copy of the genome size is also stored within the grid point itself, so we can visualise it on the grid (capped at 100)
gp.fitness = gp.genome.fitness // A copy of the genomes' fitness is stored within the grid point itself, so we can use it for the "rouletteWheel" function
gp.T_in_genomes = Math.min(sim.max_t, gp.genome.nr_tra) // A copy of the genomes' fitness is stored within the grid point itself, so we can use it for the "rouletteWheel" function
return gp.genome
}
let genome
for (let i = 0; i < sim.TE_model.nc; i++) for (let j = 0; j < sim.TE_model.nr; j++) {
this.grid[i][j].eDNA = [] // Initialise empty eDNA pool
this.grid[i][j].T_in_eDNA = 0
let size = 5
if ((Math.pow((i - sim.TE_model.nc / 2), 2) + Math.pow((j - sim.TE_model.nr / 2), 2)) < size)
genome = placeCell(sim.TE_model.grid[i][j], init_es, init_ne, init_nc, 10, init_tra_rate)
else if ((Math.pow((i - sim.TE_model.nc / 2), 2) + Math.pow((j - sim.TE_model.nr / 2), 2)) < size * 50){
genome = placeCell(sim.TE_model.grid[i][j], init_es, init_ne, init_nc, 0, init_tra_rate)
//genome.check_neutral_genome_dynamics() // Check what happens to the initial genome under non-neutral conditions
}
}
}
sim.TE_model.initialise() // Initialise for the first time (otherwise used for "RESET" button)
// Define the next-state function. This example is stochastic growth in a petri dish
sim.TE_model.nextState = function (i, j) {
if (this.grid[i][j].alive == 0) {
let neighbours = this.getMoore8(this, i, j, 'alive',1)
if (neighbours.length > 0) {
let winner = this.rouletteWheel(neighbours, 'fitness', non)
if (winner != undefined)
this.reproduce(i, j, winner)
}
}
else if (this.rng.genrand_real1() < death_rate || this.grid[i][j].genome.fitness == 0)
this.death_and_lysis(i, j)
else {
//this.TEdynamicsI(i, j)
//this.TEdynamicsII(i, j)
}
// EDNA DYNAMICS
if (this.grid[i][j].eDNA.length > 0) {
for (let k = 0; k < this.grid[i][j].eDNA.length; k++)
if (this.rng.genrand_real1() < degr_rate_edna) // degr
this.grid[i][j].eDNA.splice(k, 1)
this.grid[i][j].T_in_eDNA = Math.min(sim.max_t, this.grid[i][j].eDNA.length) // Track number of TEs for visualisation purposes
}
else
this.grid[i][j].T_in_eDNA = undefined
}
// This function is asynchronously applied to the entire grid every time step. It uses a single random number to determine both IF a DNA fragment will move,
// as well as WHERE it moves.
sim.TE_model.diffuse_eDNA = function (i, j) {
moveDNA = function (k, direction) {
let coords = sim.TE_model.moore[direction]
let target = sim.TE_model.getGridpoint(coords[0] + i, coords[1] + j)
target.eDNA.push(sim.TE_model.grid[i][j].eDNA[k])
sim.TE_model.grid[i][j].eDNA.splice(k, 1)
}
for (let k = 0; k < sim.TE_model.grid[i][j].eDNA.length; k++) {
let randomnr = sim.TE_model.rng.genrand_real1()
if (randomnr < diff_rate_edna / 4) moveDNA(k, 1)
else if (randomnr < 2 * diff_rate_edna / 4) moveDNA(k, 2)
else if (randomnr < 3 * diff_rate_edna / 4) moveDNA(k, 3)
else if (randomnr < 4 * diff_rate_edna / 4) moveDNA(k, 4)
}
}
// A custom function for copying a cell into a gp at position i,j ("reproduction")
sim.TE_model.reproduce = function (i, j, winner) {
this.grid[i][j].alive = winner.alive
this.grid[i][j].genome = winner.genome.copy(mutate = true)
this.grid[i][j].genomesize = Math.min(sim.max_g, this.grid[i][j].genome.chromosome.length)
this.grid[i][j].fitness = this.grid[i][j].genome.fitness
this.grid[i][j].T_in_genomes = Math.min(sim.max_t, this.grid[i][j].genome.nr_tra) // A copy of the genomes' fitness is stored within the grid point itself, so we can use it for the "rouletteWheel" function
}
// A custom function for killing a gp at position i,j
sim.TE_model.death_and_lysis = function (i, j) {
this.grid[i][j].alive = 0
this.grid[i][j].genomesize = undefined
this.grid[i][j].T_in_genomes = undefined
for (let p = 0; p < this.grid[i][j].genome.chromosome.length; p++) {
if (this.grid[i][j].genome.chromosome[p].type == "T")
this.grid[i][j].eDNA.push(this.grid[i][j].genome.chromosome[p])
}
}
// A custom function for TE dynamics during the lifetime of a cell
sim.TE_model.TEdynamicsI = function (i, j) {
jumping = []
for (let p = 0; p < this.grid[i][j].genome.chromosome.length; p++) {
if (this.grid[i][j].genome.chromosome[p].type == "T") {
if (this.rng.genrand_real1() < jump_rate * this.grid[i][j].genome.chromosome[p].transposition_rate)
jumping.push(this.grid[i][j].genome.chromosome[p])
}
}
for (let p = 0; p < jumping.length; p++) {
let random_pos = Math.floor(this.rng.genrand_real1() * this.grid[i][j].genome.chromosome.length)
let new_TE_copy = jumping[p].copy()
if (this.rng.genrand_real1() < probability_TE_induced_damage)
this.grid[i][j].genome.chromosome[random_pos].type = "."
this.grid[i][j].genome.chromosome.splice(random_pos, 0, new_TE_copy)
}
//if(jumping.length > 0)
this.grid[i][j].genome.calculate_fitness()
}
// A custom function for TE dynamics from the eDNA pool
sim.TE_model.TEdynamicsII = function (i, j) {
let nr_jumps = 0
for (let p = 0; p < this.grid[i][j].eDNA.length; p++) {
let randomnr = this.rng.genrand_real1()
let uptake = randomnr < uptake_from_pool
let integration = randomnr < uptake_from_pool * this.grid[i][j].eDNA[p].transposition_rate
if (uptake) {
if (integration) {
let random_pos = Math.floor(this.rng.genrand_real1() * this.grid[i][j].genome.chromosome.length)
let new_TE_copy = this.grid[i][j].eDNA[p].copy()
if (this.rng.genrand_real1() < probability_TE_induced_damage)
this.grid[i][j].genome.chromosome[random_pos].type = "."
this.grid[i][j].genome.chromosome.splice(random_pos, 0, new_TE_copy)
nr_jumps++
}
this.grid[i][j].eDNA.splice(p, 1)
}
}
if (nr_jumps > 0) this.grid[i][j].genome.calculate_fitness()
}
// Custom function to mix only the eDNA
sim.TE_model.mixeDNA = function () {
let all_eDNA_gps = [];
for (let i = 0; i < this.nc; i++)
for (let j = 0; j < this.nr; j++)
all_eDNA_gps.push(this.grid[i][j].eDNA)
all_eDNA_gps = shuffle(all_eDNA_gps, this.rng)
for (let i = 0; i < this.nc; i++)
for (let j = 0; j < this.nr; j++)
this.grid[i][j].eDNA = all_eDNA_gps.pop()
// return "Perfectly mixed the grid"
}
sim.TE_model.update = function () {
if (this.time % sim.config.graph_interval == 0) this.updateGraphs()
this.synchronous() // Applied as many times as it can in 1/60th of a second
this.apply_async(this.diffuse_eDNA)
if (sim.mixDNApool == true) this.mixeDNA()
}
sim.TE_model.updateGraphs = function () {
let num_alive = 0, gsizes = 0, hks = 0, nes = 0, non = 0, tra = 0, fitnesses = 0, sum_tra_rates = 0
for (let i = 0; i < sim.TE_model.nc; i++) for (let j = 0; j < sim.TE_model.nr; j++) {
if (this.grid[i][j].alive == 1) {
num_alive++
gsizes += this.grid[i][j].genome.chromosome.length
fitnesses += this.grid[i][j].genome.fitness
for (let p = 0; p < sim.TE_model.grid[i][j].genome.chromosome.length; p++)
switch (sim.TE_model.grid[i][j].genome.chromosome[p].type) {
case "G": hks++; break;
case "g": nes++; break;
case ".": non++; break;
case "T":
tra++;
sum_tra_rates += sim.TE_model.grid[i][j].genome.chromosome[p].transposition_rate
break;
}
}
}
this.plotArray(["Population size"],
[num_alive],
["blue"],
"Population size (nr. of living cells)")
this.plotArray(["Genome size", "Essential", "Non-essential", "Non-coding", "Transposons"],
[gsizes / num_alive, hks / num_alive, nes / num_alive, non / num_alive, tra / num_alive],
["black", "blue", "green", "grey", "red"],
"Avg genome size and composition")
this.plotArray(["Fitness"],
[fitnesses / num_alive],
["black"],
"Avg fitness")
this.plotArray(["TE jump rate (phi)"],
[sum_tra_rates / tra],
["red"],
"Avg TE jump rate (phi)")
}
// sim.addHTML("canvas_holder", "<img src=\"legend.png\">")
sim.addButton("Pause/continue", function () { sim.toggle_play() })
sim.addButton("Well-mix (all)", function () { sim.toggle_mix() })
sim.addButton("Well-mix (eDNA)", () => { sim.mixDNApool = !sim.mixDNApool })
sim.addButton("Restart", function () { sim.TE_model.initialise() })
sim.addButton("Reset", function () { location.reload(); })
sim.addHTML("form_holder", "<br>Ecological options:")
sim.addSlider("death_rate", 0.0, 1.0, 0.001, "Stochastic death rate (d)")
sim.addSlider("non", 0.0, 200.0, 1.00, "No-reproduction constant (eps)")
sim.addSlider("diff_rate_edna", 0.0, 0.25, 0.001, "eDNA diffusion rate (D)")
sim.addSlider("degr_rate_edna", 0.0, 1.0, 0.001, "eDNA degradation rate (q)")
sim.addHTML("form_holder", "<br>TE options:")
sim.addSlider("uptake_from_pool", 0.0, 1.0, 0.001, "Uptake rate from eDNA pool (u)")
sim.addSlider("jump_rate", 0.0, 1.0, 0.001, "TE jump rate (j)")
sim.addSlider("transposon_fitness_cost", 0.0, 1.0, 0.001, "TE fitness cost (c)")
sim.addSlider("probability_TE_induced_damage", 0.0, 1.0, 0.01, "TE-damage propensity (b)")
sim.addHTML("form_holder", "<br>Mutations:")
sim.addSlider("phi_mutation_rate", 0.0, 1.0, 0.001, "TE (phi) mutation rate")
sim.addSlider("gene_deletion_rate", 0.0, 0.1, 0.0001, "Gene deletion rate")
sim.addSlider("gene_duplication_rate", 0.0, 0.1, 0.0001, "Gene duplication rate")
sim.addSlider("gene_inactivation_rate", 0.0, 0.1, 0.0001, "Gene inactivation rate")
sim.addHTML("form_holder", "<br>Initial conditions (restart required!):")
sim.addSlider("init_es", 0.0, 100.0, 1, "Nr. essential genes")
sim.addSlider("init_nc", 0.0, 300.0, 1, "Nr. non-coding elements")
sim.addSlider("init_ne", 0.0, 100.0, 1, "Nr. non-essential functions")
sim.addSlider("non_essential_gene_boon", 0.0, 1.0, 0.001, "Fitness added per non-essential function")
sim.addSlider("init_tra_rate", 0.0, 1.0, 0.001, "Initial TE jump rate")
sim.start()
}
/*-------------------------End user-defined code ---------------------*/
</script>
<body onload="cacatoo()">
<div class="header" id="header">
<h2>Cacatoo</h2>
</div>
<div class="content" id="canvas_holder">
<center>
<div style="width:40%;">
This is an interactive version of the TE-host coevolution model <br>(derived from van Dijk <i>et
al.</i>, 2021)<br>
<font size=2><br>
Note: default parameters result in genome streamlining, preventing extinction even in small systems
(<a href="?size=120">120x120</a>).
When exploring parameters that (may) cause host extinction (e.g. disabling TE-damage),
you may need to increase the system size to <a href="?size=250">250x250</a> or perhaps even <a
href="?size=350">350x350</a>.
</font>
</center>
</div>
</div>
<div class="content" id="graph_holder"> </div>
<div class="content" id="form_holder"></div>
<div class="output" id="output"></div>
<div class="footer" id="footer"></div>
</body>
</html>