cacatoo
Version:
Building, exploring, and sharing spatially structured models
865 lines (752 loc) • 28.9 kB
JavaScript
/*--------------------------------------------------------------------*/
/*--------------------------------OPTIONS-----------------------------*/
/*--------------------------------------------------------------------*/
let sim;
// PARAMETERS FOR FITNESS DEFINITION
var init_es = 16 // ES = essential --- if even a single one is missing = 0.0 fitness
var init_nc = 16
var motifs_in_es = 3
var motifs_in_nc = 3
var transposon_fitness_cost = 0.005
var transposon_benefit_one_copy = 0.00 // TEs carry a beneficial gene, meaning that being infected will give you a small advantage, as long as the TEs are not out of control!
// PARAMETERS FOR ECOLOGY (death rate and non-reproduction constant)
var death_rate = 0.01
var non = 50
// PARAMETERS FOR EVOLUTION
// Bacterial genome evolution
var gene_inactivation_rate = 0.0
var gene_deletion_rate = 0.00
var is_deletion_rate = 0.000
var gene_duplication_rate = 0.00
var influx_motifs = 0.00
let coding_motifs = []
let noncoding_motifs = []
// TE evolution
var target_mutation_rate = 0.001
var insert_mutation_rate = 0.00
var specificity_mutation_rate = 0.001
var mut_step_size = 0.2
// PARAMETERS FOR EDNA POOL
var degr_rate_edna = 0.03 //0.02
var diff_rate_edna = 0.03 // 0.01
// PARAMETERS FOR TRANSPOSON DYNAMICS
var uptake_from_pool = 0.02 //0.01
var jump_attempt_rate = 0.02 // 0.02
var probability_TE_induced_damage = 1.0
// Displaying stuff
var size = 100
var scale = 2
var mix = false
// PARAMETERS CURRENTLY NOT IN USE
let successful_replications = 0
let attempted_replications = 0
/*--------------------------------------------------------------------*/
/*--------------------------------CACATOO-----------------------------*/
/*--------------------------------------------------------------------*/
function cacatoo() {
let config = {
title: "IS-elements and their host",
description: "",
maxtime: 100,
fpsmeter: false,
ncol: size,
seed: 12,
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,
graph_update: 50,
statecolours: {
alive: {
1: 'blue'
}
} // The background state '0' is never drawn
}
sim = new Simulation(config)
for(let i =0;i<motifs_in_es;i++) coding_motifs.push(sim.rng.random())
for(let i =0;i<motifs_in_nc; i++) noncoding_motifs.push(sim.rng.random())
sim.makeGridmodel("TE_model");
sim.init_insertion_sites = []
for (let i = 0; i < init_es + init_nc; i++)
sim.init_insertion_sites.push(sim.rng.random())
sim.createDisplay_continuous({
model: "TE_model",
property: "fitness",
label: "Fitness",
minval: 0.3,
maxval: 1.5,
nticks: 3,
decimals: 1,
fill: "viridis"
})
sim.createDisplay_continuous({
model: "TE_model",
property: "T_in_genomes",
label: "TE cpn",
minval: 0.0,
maxval: 1.5,
nticks: 3,
decimals: 1,
fill: "inferno"
})
sim.TE_model.colourGradient("specificity", 50, [255, 0, 0], [255, 255, 0], [0,255,0], [0,255,255], [0,0,255])
sim.createDisplay_continuous({
model: "TE_model",
property: "specificity",
label: "Specificity",
num_colours: 50,
nticks: 7,
decimals: 1,
maxval: 1
})
sim.TE_model.statecolours.specificity[0] = 'black'
sim.TE_model.colourGradient("target_site", 50, [255, 0, 0], [255, 255, 0], [0,255,0], [0,255,255], [0,0,255])
sim.createDisplay_continuous({
model: "TE_model",
property: "target_site",
label: "Target site",
num_colours: 50,
nticks: 7,
decimals: 1,
maxval: 1
})
sim.TE_model.statecolours.target_site[0] = 'black'
sim.createDisplay_continuous({
model: "TE_model",
property: "T_in_eDNA",
label: "TEs in eDNA pool",
fill: "inferno",
maxval: 10,
num_colours: 30
})
sim.TE_model.initialise = function() {
sim.initialGrid(sim.TE_model, "alive", 0, 1.0)
sim.initialGrid(sim.TE_model, "genomesize", undefined, 1.0)
sim.initialGrid(sim.TE_model, "T_in_eDNA", undefined, 1.0)
sim.initialGrid(sim.TE_model, "T_in_genomes", undefined, 1.0)
sim.max_g = 300
sim.max_t = 300
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(x, y, init_es, init_nc, init_tra, init_tra_rate) {
gp = sim.TE_model.grid[x][y]
gp.alive = 1
gp.genome = new Genome()
// console.log(gp.genome)
gp.genome.initialise(init_es, 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.specificity = gp.genome.specificity
gp.target_site = gp.genome.target_site
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
}
for (let x = 0; x < sim.TE_model.nc; x++)
for (let y = 0; y < sim.TE_model.nr; y++) {
this.grid[x][y].eDNA = [] // Initialise empty eDNA pool
this.grid[x][y].T_in_eDNA = 0
let midx = sim.ncol/2
let midy = sim.nrow/2
let dx = x - midx
let dy = y - midy
let dist = Math.sqrt(dx*dx + dy*dy)
if(dist>10) placeCell(x,y, init_es, init_nc, 0, init_tra_rate)
else placeCell(x,y, init_es, init_nc, 1, init_tra_rate)
}
}
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(x, y) {
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', non)
if (winner != undefined)
this.reproduce(x, y, winner)
}
}
//else if (this.rng.genrand_real1() < death_rate || this.grid[x][y].genome.fitness == 0)
else if (this.rng.genrand_real1() < death_rate || this.grid[x][y].genome.fitness == 0)
this.death_and_lysis(x, y)
else {
this.TEdynamicsI(x, y)
this.TEdynamicsII(x, y)
this.grid[x][y].T_in_genomes = this.grid[x][y].genome.nr_tra
}
// EDNA DYNAMICS
if (this.grid[x][y].eDNA.length > 0) {
for (let k = 0; k < this.grid[x][y].eDNA.length; k++)
if (this.rng.genrand_real1() < degr_rate_edna) // degr
this.grid[x][y].eDNA.splice(k, 1)
this.grid[x][y].T_in_eDNA = Math.min(sim.max_t, this.grid[x][y].eDNA.length) // Track number of TEs for visualisation purposes
} else
this.grid[x][y].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(x, y) {
moveDNA = function(k, direction) {
let coords = sim.TE_model.moore[direction]
let target = sim.TE_model.getGridpoint(coords[0] + x, coords[1] + y)
target.eDNA.push(sim.TE_model.grid[x][y].eDNA[k])
sim.TE_model.grid[x][y].eDNA.splice(k, 1)
}
for (let k = 0; k < sim.TE_model.grid[x][y].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 x,y ("reproduction")
sim.TE_model.reproduce = function(x, y, winner) {
this.grid[x][y].alive = winner.alive
this.grid[x][y].genome = winner.genome.copy(true)
this.grid[x][y].genomesize = Math.min(sim.max_g, this.grid[x][y].genome.chromosome.length)
this.grid[x][y].fitness = this.grid[x][y].genome.fitness
this.grid[x][y].specificity = this.grid[x][y].genome.specificity
this.grid[x][y].target_site = this.grid[x][y].genome.target_site
this.grid[x][y].T_in_genomes = Math.min(sim.max_t, this.grid[x][y].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 x,y
sim.TE_model.death_and_lysis = function(x, y) {
this.grid[x][y].alive = 0
this.grid[x][y].genomesize = undefined
this.grid[x][y].fitness = undefined
this.grid[x][y].specificity = undefined
this.grid[x][y].target_site = undefined
this.grid[x][y].T_in_genomes = undefined
for (let p = 0; p < this.grid[x][y].genome.chromosome.length; p++) {
if (this.grid[x][y].genome.chromosome[p].type == "T") {
let randomnr = sim.TE_model.rng.genrand_real1()
let direction = 0
if (randomnr < 1 / 5) direction = 1
else if (randomnr < 2 / 5) direction = 2
else if (randomnr < 3 / 5) direction = 3
else if (randomnr < 4/5) direction = 4
this.getNeighbour(sim.TE_model, x, y, direction).eDNA.push(this.grid[x][y].genome.chromosome[p])
}
}
}
get_insertion_chance = function(i, t, s) {
if(i < 0) return 0
let d = Math.abs(i - t)
return (1 - d * (s / (1 - s)))
}
// A custom function for TE dynamics during the lifetime of a cell
sim.TE_model.TEdynamicsI = function(x, y) {
jumping = []
for (let p = 0; p < this.grid[x][y].genome.chromosome.length; p++) {
if (this.grid[x][y].genome.chromosome[p].type == "T") {
if (this.rng.genrand_real1() < jump_attempt_rate)
jumping.push(this.grid[x][y].genome.chromosome[p])
}
}
let nr_jumps = 0
for (let p = 0; p < jumping.length; p++) {
let random_pos = Math.floor(this.rng.genrand_real1() * this.grid[x][y].genome.chromosome.length)
//1-x\cdot\frac{\left(b\right)}{\left(1-b\right)}
let site = this.grid[x][y].genome.chromosome[random_pos]
let chance = get_insertion_chance(site.insertion_site, jumping[p].target_site, jumping[p].specificity)
if (chance > 0 && this.rng.genrand_real1() < chance) {
nr_jumps ++
let new_TE_copy = jumping[p].copy(true)
if (this.rng.genrand_real1() < probability_TE_induced_damage) {
let new_ins_site = -1
if(this.grid[x][y].genome.chromosome[random_pos].type == "G")
new_ins_site = coding_motifs[Math.floor(Math.random() * coding_motifs.length)]
else new_ins_site = noncoding_motifs[Math.floor(Math.random() * noncoding_motifs.length)]
this.grid[x][y].genome.chromosome[random_pos].break(new_ins_site)
}
this.grid[x][y].genome.chromosome.splice(random_pos, 0, new_TE_copy)
}
/* chr = Object.values(this.grid[x][y].genome.chromosome).reduce((t, {type}) => t + type, '')
console.log(`${chr}`)
*/
}
if (jumping.length > 0) this.grid[x][y].genome.calculate_fitness()
successful_replications += nr_jumps
attempted_replications += jumping.length
}
// A custom function for TE dynamics from the eDNA pool
sim.TE_model.TEdynamicsII = function(x, y) {
let nr_jumps = 0
for (let p = 0; p < this.grid[x][y].eDNA.length; p++) {
let randomnr = this.rng.genrand_real1()
let uptake = randomnr < uptake_from_pool
if (uptake) {
let random_pos = Math.floor(this.rng.genrand_real1() * this.grid[x][y].genome.chromosome.length)
let site = this.grid[x][y].genome.chromosome[random_pos]
let TE = this.grid[x][y].eDNA[p]
let chance = get_insertion_chance(site.insertion_site, TE.target_site, TE.specificity)
attempted_replications++
if (chance > 0 && sim.rng.random() < chance) {
nr_jumps++
//let chance_failure = (1-chance)**10
//if(chance_failure < sim.rng.genrand_real1()){
//console.log(`{Chance: ${chance}\nFailure: ${chance_failure}`)
let new_TE_copy = TE.copy(true)
if (this.rng.genrand_real1() < probability_TE_induced_damage) {
this.grid[x][y].genome.chromosome[random_pos].break(sim.rng.random())
//this.grid[x][y].genome.chromosome[random_pos].break(TE.target_site)
}
this.grid[x][y].genome.chromosome.splice(random_pos, 0, new_TE_copy)
//}
}
this.grid[x][y].eDNA.splice(p, 1)
}
}
if (nr_jumps > 0) this.grid[x][y].genome.calculate_fitness()
successful_replications += nr_jumps
}
// Custom function to mix only the eDNA
sim.TE_model.mixeDNA = function() {
let all_eDNA_gps = [];
for (let x = 0; x < this.nc; x++)
for (let y = 0; y < this.nr; y++)
all_eDNA_gps.push(this.grid[x][y].eDNA)
all_eDNA_gps = shuffle(all_eDNA_gps, this.rng)
for (let x = 0; x < this.nc; x++)
for (let y = 0; y < this.nr; y++)
this.grid[x][y].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()
successful_replications = 0
attempted_replications = 0
this.synchronous2() // 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
let genome_sizes = []
let is_counts = []
let specificities = []
let TE_insertion_sites = []
let coding_insertion_sites = []
let safe_insertion_sites = []
let target_sites = []
for (let x = 0; x < sim.TE_model.nc; x++)
for (let y = 0; y < sim.TE_model.nr; y++) {
if (this.grid[x][y].alive == 1) {
num_alive++
gsizes += this.grid[x][y].genome.chromosome.length
fitnesses += this.grid[x][y].genome.fitness
let num_tra = 0
for (let p = 0; p < sim.TE_model.grid[x][y].genome.chromosome.length; p++){
let site = sim.TE_model.grid[x][y].genome.chromosome[p].insertion_site;
switch (sim.TE_model.grid[x][y].genome.chromosome[p].type) {
case "G":
hks++;
if(site >= 0) coding_insertion_sites.push(site);
break;
case "g":
nes++;
if(site >= 0) safe_insertion_sites.push(site);
break;
case ".":
non++;
if(site >= 0) safe_insertion_sites.push(site);
break;
case "T":
tra++;
num_tra++
TE_insertion_sites.push(sim.TE_model.grid[x][y].genome.chromosome[p].insertion_site)
target_sites.push(sim.TE_model.grid[x][y].genome.chromosome[p].target_site)
specificities.push(sim.TE_model.grid[x][y].genome.chromosome[p].specificity)
sum_tra_rates += sim.TE_model.grid[x][y].genome.chromosome[p].transposition_rate
break;
}
}
// if(this.grid[x][y].genome.chromosome.length != num_tra) throw new Error("he?")
genome_sizes.push(this.grid[x][y].genome.chromosome.length)
is_counts.push(num_tra)
}
}
this.plotArray(["Population size"],
[num_alive],
["blue"],
"Population size (nr. of living cells)", {
width: 600
})
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", "#00CC00", "grey", "red"],
"Avg genome size and composition", {
width: 600
})
this.plotArray(["Attempted replications", "Succesful replications"],
[successful_replications/attempted_replications],
["#00AA00"],
"Effective TE jumping success rate", {
width: 600
})
/* this.plotArray(["Fitness"],
[fitnesses / num_alive],
["black"],
"Avg fitness", {
width: 600
}) */
if (sim.time % 50 == 0) {
specificities = shuffle(specificities)
let spec = specificities.slice(0, Math.min(specificities.length, 100))
this.plotPoints(spec, "Specificities", {
labelsDivWidth: 0,
width: 600
})
target_sites = shuffle(target_sites)
let tsites = target_sites.slice(0, Math.min(target_sites.length, 100))
this.plotPoints(tsites, "Target_sites", {
width: 600,
labelsDivWidth: 0
})
}
let layout = {
paper_bgcolor: 'rgb(225,225,225)',
plot_bgcolor: 'rgba(225,225,225,0)',
font: {
size: "15"
},
title: `Histogram of IS-counts at T=${this.time}`,
width: 600,
height: 300,
yaxis: {
type: 'linear',
autorange: true
},
xaxis: {
range: [-5, 60]
}
};
let histo_dat = {
xbins: {
size: 1
},
marker: {
color: 'rgba(0,0,0,0.2)'
},
type: 'histogram',
name: 'Genome size',
offsetgroup: "1"
};
let is_dat = {
xbins: {
size: 1
},
marker: {
color: 'red'
},
type: 'histogram',
name: 'IS counts',
offsetgroup: "1"
};
let layout2 = {
paper_bgcolor: 'rgb(225,225,225)',
plot_bgcolor: 'rgba(225,225,225,0)',
font: {
size: "15"
},
title: `Histogram of IS-properties at T=${this.time}`,
width: 600,
height: 300,
yaxis: {
type: 'linear',
autorange: true
},
xaxis: {
range: [-0.02, 1.02]
}
};
let coding_ins = {
xbins: {
size: 0.02
},
marker: {
color: 'rgba(0,0,0,0.1)'
},
type: 'histogram',
name: 'Coding motifs',
offsetgroup: "1"
};
let safe_ins = {
xbins: {
size: 0.02
},
marker: {
color: 'rgba(0,100,0,0.4)'
},
type: 'histogram',
name: 'Safe motifs',
offsetgroup: "1"
};
let te_ins_dat = {
xbins: {
size: 0.02
},
marker: {
color: 'rgba(0,0,0,0.5)'
},
type: 'histogram',
name: 'TE motifs',
offsetgroup: "1"
};
let target_dat = {
xbins: {
size: 0.02
},
marker: {
color: 'rgba(200,0,0,0.5)'
},
type: 'histogram',
name: 'Target sites',
offsetgroup: "1"
};
let spec_dat = {
xbins: {
size: 0.02
},
marker: {
color: 'rgba(0,0,255,0.5)'
},
type: 'histogram',
name: 'Specificities',
offsetgroup: "1"
};
histo_dat.x = genome_sizes
is_dat.x = is_counts
target_dat.x = target_sites
coding_ins.x = coding_insertion_sites
safe_ins.x = safe_insertion_sites
te_ins_dat.x = TE_insertion_sites
spec_dat.x = specificities
Plotly.newPlot('histo', [histo_dat, is_dat], layout);
Plotly.newPlot('histo2', [target_dat, spec_dat], layout2);
Plotly.newPlot('histo3', [coding_ins, safe_ins, te_ins_dat], layout2);
}
// 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.start()
if (mix) sim.toggle_mix()
}
/*--------------------------------------------------------------------*/
/*--------------------------------CLASSES-----------------------------*/
/*--------------------------------------------------------------------*/
class Genome {
// Genome constructor
constructor() {
this.uid = genomeIds.next()
this.total_num_hk = init_es
}
initialise(init_hk, init_nc, init_tr, init_transposition_rate) {
this.generation = 1
this.chromosome = []
for (let i = 0; i < init_hk; i++) this.chromosome.push(new Gene({
type: "G",
func: i,
insertion_site: coding_motifs[Math.floor(Math.random() * coding_motifs.length)]
//insertion_site: coding_sites[i%coding_sites.length]
}))
for (let i = 0; i < init_nc; i++) this.chromosome.push(new Gene({
type: ".",
func: 0,
insertion_site: noncoding_motifs[Math.floor(Math.random() * noncoding_motifs.length)]
//insertion_site: noncoding_sites[i%noncoding_sites.length]
}))
for (let i = 0; i < init_tr; i++) this.chromosome.push(new Gene({
type: "T",
func: 0,
transposition_rate: init_tra_rate,
insertion_site: sim.rng.random(),
target_site: sim.rng.random(),
specificity: sim.rng.random()
}))
shuffle(this.chromosome)
this.calculate_fitness()
}
copy(mutate) {
let child = new Genome()
child.chromosome = []
for (let i = 0; i < this.chromosome.length; i++) {
let new_gene = this.chromosome[i].copy(true)
child.chromosome.push(new_gene)
}
// console.log(child.chromosome)
child.generation = this.generation + 1
child.fitness = this.fitness
child.specificity = this.specificity
child.target_site = this.target_site
child.nr_tra = this.nr_tra
if (mutate) child.mutate()
return child
}
calculate_fitness() {
this.fitness = 1.0
this.specificity = 0.0
this.target_site = 0.0
let hks = []
this.nr_tra = 1e-20
hks.length = this.total_num_hk
for (let i = 0; i < this.chromosome.length; i++) {
let gene = this.chromosome[i]
switch (gene.type) {
case "G":
hks[gene.func] = 1
break
case "T":
this.nr_tra++
this.specificity += gene.specificity
this.target_site += gene.target_site
break
}
}
if(this.nr_tra>1) this.specificity = this.specificity/this.nr_tra
if(this.nr_tra>1) this.target_site = this.target_site/this.nr_tra
let hks_present = 0
for (let i = 0; i < hks.length; i++)
if (hks[i] == 1) hks_present++
this.fitness -= this.nr_tra * transposon_fitness_cost
if(this.nr_tra>=1) this.fitness += transposon_benefit_one_copy
if (hks_present < this.total_num_hk) this.fitness = 0.0
this.fitness = Math.max(0, Math.min(this.fitness, 2.0))
}
mutate() {
let mutation = false
for (let i = 0; i < this.chromosome.length; i++) {
if (this.chromosome[i].type == 'T' && sim.rng.genrand_real1() < is_deletion_rate) {
this.chromosome[i] = new Gene({
type: ".",
func: 0,
insertion_site: this.chromosome[i].target_site
})
mutation = true
}
else if (sim.rng.genrand_real1() < influx_motifs) {
let new_ins_site = noncoding_motifs[Math.floor(Math.random() * noncoding_motifs.length)]
this.chromosome.splice(i, 0, new Gene({
type: ".",
func: 0,
insertion_site: new_ins_site}))
mutation = true
}
}
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) {
if(this.chromosome[i].type == "T") this.chromosome[i].break(this.chromosome[i].target_site)
else 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(true)
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_deletion_rate) {
let size = sim.rng.genrand_real1() * this.chromosome.length / 4
this.chromosome.splice(i, size) // Tandem deletion (splice up to a fourth of, plus one to ensure the last can be deleted)
mutation = true
} else if (randomnr < gene_deletion_rate + gene_duplication_rate + gene_deletion_rate + gene_duplication_rate) {
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_deletion_rate + gene_duplication_rate + gene_inactivation_rate) {
this.chromosome[i].type = '.'
mutation = true
}
}
if (mutation) this.calculate_fitness()
}
}
class Gene {
// Gene constructor
constructor(parent_gene) {
for (var k in parent_gene) this[k] = parent_gene[k];
this.uid = geneIds.next() // Just so it has a unique identifier, no biological function
}
copy(mutate) {
let new_gene = new Gene(this)
if (mutate) {
if (new_gene.type == "T") {
if (sim.rng.genrand_real1() < specificity_mutation_rate)
new_gene.specificity = gaussianRandom(new_gene.specificity, mut_step_size)
if (sim.rng.genrand_real1() < target_mutation_rate)
new_gene.target_site = gaussianRandom(new_gene.target_site, mut_step_size)
new_gene.specificity = Math.min(Math.abs(new_gene.specificity), 1.0)
new_gene.target_site = Math.min(Math.abs(new_gene.target_site), 1.0)
}
if (sim.rng.genrand_real1() < insert_mutation_rate) {
//new_gene.insertion_site += (sim.rng.genrand_real1() * 2 - 1) * mut_step_size
new_gene.insertion_site = gaussianRandom(new_gene.insertion_site, mut_step_size)
new_gene.insertion_site = Math.min(Math.abs(new_gene.insertion_site), 1.0)
}
}
return new_gene
}
break(new_insertion_site){
this.type = "."
this.insertion_site = new_insertion_site
}
}
/**
* 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_real1() * (i + 1));
x = a[i];
a[i] = a[j];
a[j] = x;
}
return a;
}
// Standard Normal variate using Box-Muller transform.
function gaussianRandom(mean=0, stdev=1) {
let u = 1 - Math.random(); //Converting [0,1) to (0,1)
let v = Math.random();
let z = Math.sqrt( -2.0 * Math.log( u ) ) * Math.cos( 2.0 * Math.PI * v );
// Transform to the desired mean and standard deviation:
return z * stdev + mean;
}
function* idGenerator() {
let id = 1;
while (true) {
yield id
id++
}
}
const genomeIds = idGenerator()
const geneIds = idGenerator()
///// OLD PARAMETERS (van Dijk et al 2021)
var phi_mutation_rate = 0.0 // The old "rate" at which TEs jumped, before the usage of insertion sites
var init_tra_rate = 1.00 //