cacatoo
Version:
Building, exploring, and sharing spatially structured models
285 lines (238 loc) • 13 kB
HTML
<!--
EXAMPLE FILE PoS: Pearls-on-a-string genomes with mutations
If your models get increasingly complex, you may want to define your own classes. You can
either do this in a seperate file, or (as shown here) define the classes at the bottom of
your code. In this example, two classes (genomes and the genes within) are defined.
Genomes are ordered lists of genes. Genes have a type (essential, nonessential) and
a function (function 1, function 2, etc.). The fitness of a genome is 1 by default,
but if an essential gene is missing, fitness is 0. For each non-essential gene function
the genome can perform, 0.05 is added to the fitness, making the maximum fitness 2.0.
-->
<html>
<script src="../../dist/cacatoo.js"></script> <!-- Include cacatoo library (compiled with rollup) -->
<script src="../../lib/all.js"></script> <!-- Include other libraries (concattenated in 1 file) -->
<link rel="stylesheet" href="../../style/cacatoo.css"> <!-- Set style sheet -->
<script>
/*-----------------------Start user-defined code ---------------------*/
let sim;
let init_es = 5 // es = essential --- if even a single one is missing = 0.0 fitness
let init_ne = 20 // ne = non-essential --- for each non-essential genes gain 0.05 fitness
let death_rate = 0.02
let gene_inactivation_rate = 0.001
let gene_deletion_rate = 0.001
let gene_duplication_rate = 0.001
let gene_tandem_deletion = 0.001
let gene_tandem_duplication = 0.001
function cacatoo() {
let config = {
title: "Pearls-on-a-string genomes",
description: "Carrying essential and non-essential (but useful) genes",
maxtime: 10000,
fastmode: false, // If possible, fast-mode will update the sim with more than 60 FPS.
// Note: fastmode is only useful/recommended if the sim can readily run
// at 60FPS without fast-mode, and may actually reduce the reported FPS
// Nevertheless, the final state of the grid is reaches sooner in real time.
ncol: 100,
nrow: 100, // dimensions of the grid to build
wrap: [true, true], // Wrap boundary [COLS, ROWS]
scale: 2, // scale of the grid (nxn pixels per grid cell)
graph_interval: 10,
graph_update: 20,
statecolours: { alive: { 1: 'blue' } } // The background state '0' is never drawn
}
sim = new Simulation(config)
sim.makeGridmodel("cells");
sim.cells.colourViridis("genomesize", 100)
sim.createDisplay_continuous({model:"cells", property:"genomesize", label:"Genome size", minval:0, maxval:100})
sim.cells.initialise = function () {
sim.initialGrid(sim.cells, "alive", 0, 1.0)
sim.initialGrid(sim.cells, "genomesize", 0, undefined, 1.0)
for (let x = 0; x < sim.cells.nc; x++) for (let y = 0; y < sim.cells.nr; y++) {
// if(sim.cells.rng.genrand_real1() < 0.999)
if (x == sim.cells.nc / 2 && y == sim.cells.nr / 2) {
sim.cells.grid[x][y].alive = 1
sim.cells.grid[x][y].genome = new Genome()
sim.cells.grid[x][y].genome.initialise(init_es, init_ne, 2) // Initialise genomes with a number of essential, non-essential, and non-coding genes
sim.cells.grid[x][y].genomesize = Math.min(100, sim.cells.grid[x][y].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)
sim.cells.grid[x][y].fitness = sim.cells.grid[x][y].genome.fitness // A copy of the genomes' fitness is stored within the grid point itself, so we can use it for the "rouletteWheel" function
}
}
}
sim.cells.initialise() // Initialise for the first time (otherwise used for "RESET" button)
let birth_rate = 0.85
sim.cells.nextState = function (x, y) // Define the next-state function. This example is stochastic growth in a petri dish
{
if (this.grid[x][y].alive == 0) {
let neighbours = this.getMoore8(this, x, y,'alive',1)
if (neighbours.length > 0) {
let winner = this.rouletteWheel(neighbours, 'fitness', 5.0)
if (winner != undefined) {
this.grid[x][y].alive = winner.alive
this.grid[x][y].genome = winner.genome.copy(mutate = true)
this.grid[x][y].genomesize = Math.min(100, this.grid[x][y].genome.chromosome.length)
this.grid[x][y].fitness = this.grid[x][y].genome.fitness
}
}
}
else if (sim.rng.genrand_real1() < death_rate) {
this.grid[x][y].alive = 0
this.grid[x][y].genomesize = undefined
}
}
sim.cells.update = function () {
this.synchronous() // Applied as many times as it can in 1/60th of a second
this.plotPopsizes('alive', [1])
let num_alive = 0, gsizes = 0, hks = 0, nes = 0, non = 0, fitnesses = 0
for (let x = 0; x < sim.cells.nc; x++) for (let y = 0; y < sim.cells.nr; y++) {
if (this.grid[x][y].alive == 1) {
num_alive++
gsizes += this.grid[x][y].genomesize
fitnesses += this.grid[x][y].genome.fitness
for (let p = 0; p < sim.cells.grid[x][y].genome.chromosome.length; p++)
switch (sim.cells.grid[x][y].genome.chromosome[p].type) {
case "G": hks++; break;
case "g": nes++; break;
case ".": non++; break;
}
}
}
this.plotArray(["Genome size", "Essential", "Non-essential", "Non-coding"],
[gsizes / num_alive, hks / num_alive, nes / num_alive, non / num_alive],
["black", "blue", "green", "grey", "red"],
"Avg genome size and composition")
this.plotArray(["Fitness"],
[fitnesses / num_alive],
["black"],
"Avg fitness")
}
sim.addButton("pause/continue", 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("reset", function () { sim.cells.initialise() }) // Add a button that calls function "perfectMix" in "model.cheater"
sim.addHTML("form_holder", "<br>")
sim.addSlider("mutationrate", 0.0, 0.01, 0.0001)
sim.start()
}
class Genome {
// Genome constructor
constructor() {
this.uid = genomeIds.next()
this.total_num_hk = init_es
this.total_num_ne = init_ne
}
initialise(init_hk, init_ne, init_nc) {
this.generation = 1
this.chromosome = []
for (let i = 0; i < init_hk; i++) this.chromosome.push(new Gene("G", i))
for (let i = 0; i < init_ne; i++) this.chromosome.push(new Gene("g", i))
for (let i = 0; i < init_nc; i++) this.chromosome.push(new Gene(".", i))
shuffle(this.chromosome)
this.calculate_fitness()
}
copy(mutate) {
let child = new Genome()
child.chromosome = []
for (let i = 0; i < this.chromosome.length; i++) child.chromosome.push(this.chromosome[i].copy())
child.generation = this.generation + 1
if (mutate) child.mutate()
return child
}
calculate_fitness() {
this.fitness = 1.0
let hks = [], nes = []
hks.length = this.total_num_hk
nes.length = this.total_num_ne
for (let i = 0; i < this.chromosome.length; i++) {
let gene = this.chromosome[i]
switch (gene.type) {
case "G":
hks[gene.func] = 1
case "g":
nes[gene.func] = 1
}
}
let hks_present = 0
for (let i = 0; i < hks.length; i++) if (hks[i] == 1) hks_present++
for (let i = 0; i < nes.length; i++) if (nes[i] == 1) this.fitness += 0.05
if (hks_present < this.total_num_hk) this.fitness = 0.0
}
mutate() {
let mutation = false
for (let i = 0; i < this.chromosome.length; i++) {
// Mutations with the javascript "splice" function: splice(pos,num_remove,append_this)
let randomnr = sim.rng.genrand_real1()
if (randomnr < gene_deletion_rate) {
this.chromosome.splice(i, 1) // Single gene deletion (splice 1 off, starting from i, append nothing)
mutation = true
}
else if (randomnr < gene_deletion_rate + gene_duplication_rate) {
let newgene = this.chromosome[i].copy()
this.chromosome.splice(i, 0, newgene) // Single gene duplication (splice 0 off, but append the current pos to the array)
mutation = true
}
else if (randomnr < gene_deletion_rate + gene_duplication_rate + gene_tandem_deletion) {
let size = sim.rng.genrand_real1() * this.chromosome.length / 4
this.chromosome.splice(i, size)
mutation = true
}
else if (randomnr < gene_deletion_rate + gene_duplication_rate + gene_tandem_deletion + gene_tandem_duplication) {
if (this.chromosome.length > 1000) break
let size = Math.floor(1 + sim.rng.genrand_real1() * this.chromosome.length / 4)
if (size > this.chromosome.length) size = this.chromosome.length
const strand = [...this.chromosome.slice(i, i + size)]
this.chromosome.splice(i, 0, ...strand)
i += size
mutation = true
}
else if (randomnr < gene_deletion_rate + gene_duplication_rate + gene_tandem_deletion + gene_tandem_duplication + gene_inactivation_rate) {
this.chromosome[i].type = '.'
}
}
this.calculate_fitness()
}
}
class Gene {
// Gene constructor
constructor(type, func) {
this.uid = geneIds.next() // Just so it has a unique identifier, no biological function
this.type = type // Here assigned a string to a gene, being either "essential", "non-essential", "non-coding", or "transposon"
this.func = func // A number indicating the function of that gene (e.g. 1 type of each function is necessary for absolutely essential, while non-essential yields incremental benefits)
}
copy() {
return new Gene(this.type, this.func, this.transposition_rate)
}
}
/**
* 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 genomeIds = idGenerator()
const geneIds = idGenerator()
/*-------------------------End user-defined code ---------------------*/
</script>
<body onload="cacatoo()">
<div class="header" id="header">
<h2>ModelJS - </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>