Team:Paris Saclay/simbox.js

From 2013.igem.org

/* Simulator written by Damir Vodenicarevic for the Paris-Saclay 2013 iGEM team */

simulations= new Array();

function simboxes_load() {

var simboxes= document.getElementsByClassName("simbox");
for(var i=0 ; i < simboxes.length ; ++i)
 init_simbox(simboxes[i], i);

}

function molecule() {

this.name= undefined;
this.init_qtty= 0;
this.quantity= 0;
this.curve_show= false;
this.curve_color= "#000000";
this.const= false;
this.adjustable= false;
this.history= new Array();

}

function reaction() {

this.inputs= new Array();
this.outputs= new Array();
this.probability= 0.0;
this.precomp_probfactor= 0.0;
this.precomp_n_consumed= new Array();
this.precomp_n_in= new Array();

}

function simulation() {

this.id= undefined;
this.volume= undefined;
this.timestep= 0.0001;
this.alpha= 7.4e-7;
this.molecules= new Array();
this.reactions= new Array();
this.reac_order= new Array();
this.running= false;
this.refresh_run_time= 70;
this.refresh_interval= 30;
this.timeout= null;
this.curtime= 0.0;
this.curstep= 0;
this.canv= null;
this.ctx= null;
this.draw_min_t= 0.0;
this.draw_max_t= 100.0;
this.draw_min_v= 0.0;
this.draw_max_v= 1000.0;

}

function simbox_get_molecule_id(simid, name) {

for(var i= 0 ; i < simulations[simid].molecules.length ; ++i)
{
 if(simulations[simid].molecules[i].name == name)
  return i;
}
return null;

}

function init_simbox(sb, id) {

simulations[id]= new simulation();
//Get parameters
var xmlfile= sb.getAttribute('data-load')+'?action=raw&ctype=text/css&ps_tstamp='+new Date().getTime();
var width= parseInt(sb.getAttribute('data-width'));
var height= parseInt(sb.getAttribute('data-height'));
//Open XML data file
var xmldata= null;
try
{
 var xhr= new XMLHttpRequest();
 xhr.open("GET", xmlfile, false);
 xhr.send();
 var parser= new DOMParser();
 xmldata= parser.parseFromString(xhr.responseText, "application/xml");
 if(xmldata.documentElement.nodeName != 'sim')
  throw 'XML parsing error';
}
catch(err)
{
 sb.innerHTML='Error : ' + err;
 return false;
}
//Load display parameters
if(xmldata.getElementsByTagName("yscale").length > 0)
 simulations[id].draw_max_v= parseFloat(xmldata.getElementsByTagName("yscale")[0].childNodes[0].nodeValue);
//Load simulation parameters
if(xmldata.getElementsByTagName("volume").length > 0)
 simulations[id].volume= parseFloat(xmldata.getElementsByTagName("volume")[0].childNodes[0].nodeValue);
if(xmldata.getElementsByTagName("timestep").length > 0)
 simulations[id].timestep= parseFloat(xmldata.getElementsByTagName("timestep")[0].childNodes[0].nodeValue);
if(xmldata.getElementsByTagName("alpha").length > 0)
 simulations[id].alpha= parseFloat(xmldata.getElementsByTagName("alpha")[0].childNodes[0].nodeValue);
//Load molecules
var molecule_tags= xmldata.getElementsByTagName("molecule");
for(var i= 0 ; i < molecule_tags.length ; ++i)
{ 
 simulations[id].molecules[i]= new molecule();
 simulations[id].molecules[i].name= molecule_tags[i].getAttribute('name');
 if(molecule_tags[i].hasAttribute('quantity'))
 {
  simulations[id].molecules[i].init_qtty= parseFloat(molecule_tags[i].getAttribute('quantity'));
  simulations[id].molecules[i].quantity= parseFloat(molecule_tags[i].getAttribute('quantity'));
 }
 if(molecule_tags[i].hasAttribute('curve_show'))
  simulations[id].molecules[i].curve_show= (molecule_tags[i].getAttribute('curve_show') == 'true');
 if(molecule_tags[i].hasAttribute('curve_color'))
  simulations[id].molecules[i].curve_color= molecule_tags[i].getAttribute('curve_color');
 if(molecule_tags[i].hasAttribute('const'))
  simulations[id].molecules[i].const= (molecule_tags[i].getAttribute('const') == 'true');
 if(molecule_tags[i].hasAttribute('adjustable'))
  simulations[id].molecules[i].adjustable= (molecule_tags[i].getAttribute('adjustable') == 'true');
}
//Load reactions
var reaction_tags= xmldata.getElementsByTagName("reaction");
for(var i= 0 ; i < reaction_tags.length ; ++i)
{
 simulations[id].reactions[i]= new reaction();
 if(reaction_tags[i].hasAttribute('probability'))
  simulations[id].reactions[i].probability= parseFloat(reaction_tags[i].getAttribute('probability'));
 //precomputing
 simulations[id].reactions[i].precomp_probfactor= simulations[id].reactions[i].probability;
 
 var input_tags= reaction_tags[i].getElementsByTagName("in");
 if(input_tags.length > 2)
 {
  sb.innerHTML='Error : reactions with more than 2 reactices are not supported.';
  return false;
 }
 for(var j= 0 ; j < input_tags.length ; ++j)
 {
   var tmpid= simbox_get_molecule_id(id, input_tags[j].childNodes[0].nodeValue);
   if(tmpid == null)
   {
    sb.innerHTML='Error : molecule "'+input_tags[j].childNodes[0].nodeValue+'" not defined.';
    return false;
   }
   simulations[id].reactions[i].inputs[j]= tmpid;
   //precomputing
   if(!simulations[id].molecules[tmpid].const)
   {
    if(simulations[id].reactions[i].precomp_n_in[tmpid] == undefined)
    {
     simulations[id].reactions[i].precomp_n_in[tmpid] = 1;
     simulations[id].reactions[i].precomp_n_consumed[tmpid] = 1;
    }
    else
    {
     simulations[id].reactions[i].precomp_n_in[tmpid] ++;
     simulations[id].reactions[i].precomp_n_consumed[tmpid] ++;
    }
   }
 }
 //precomputing
 if(input_tags.length == 2)
 {
  simulations[id].reactions[i].precomp_probfactor *= (simulations[id].alpha/simulations[id].volume);
  if(simulations[id].reactions[i].inputs[0] == simulations[id].reactions[i].inputs[1])
   simulations[id].reactions[i].precomp_probfactor /= 2;
 }
 var output_tags= reaction_tags[i].getElementsByTagName("out");
 for(var j= 0 ; j < output_tags.length ; ++j)
 {
   var tmpid= simbox_get_molecule_id(id, output_tags[j].childNodes[0].nodeValue);
   if(tmpid == null)
   {
    sb.innerHTML='Error : molecule "'+output_tags[j].childNodes[0].nodeValue+'" not defined.';
    return false;
   }
   simulations[id].reactions[i].outputs[j]= tmpid;
   //precomputing
   if(simulations[id].reactions[i].precomp_n_consumed[tmpid] != undefined)
   {
    simulations[id].reactions[i].precomp_n_consumed[tmpid] --;
    if(simulations[id].reactions[i].precomp_n_consumed[tmpid] <= 0) //not consuming
     simulations[id].reactions[i].precomp_n_consumed.splice(tmpid, 1);
   }
 }
 //precompute index array for easy shuffling
 simulations[id].reac_order[i]= i;
}

//Clear loading text
sb.innerHTML= ;
//Create canvas
simulations[id].canv= document.createElement('canvas');
simulations[id].ctx= simulations[id].canv.getContext("2d");
simulations[id].canv.id= "simbox_canv_"+id;
simulations[id].canv.width= width;
simulations[id].canv.height= height;
sb.appendChild(simulations[id].canv);

//Init param box
var paramdiv= document.createElement('div');
sb.appendChild(paramdiv);
//legend
var reshtmlparamdiv= ;
for(var i= 0 ; i < simulations[id].molecules.length ; ++i)
{
 if(simulations[id].molecules[i].curve_show)
  reshtmlparamdiv += ''+simulations[id].molecules[i].name+' ';
}
reshtmlparamdiv += 't = 0';
//quantity controls
for(var i= 0 ; i < simulations[id].molecules.length ; ++i)
{
 if(simulations[id].molecules[i].adjustable)
 {
  reshtmlparamdiv += '

'; reshtmlparamdiv += simulations[id].molecules[i].name + ' = <input id="simbox_ctl_'+id+'_'+i+'" type="number" min="0" value="'+simulations[id].molecules[i].quantity+'"/>'; } } reshtmlparamdiv += '

'; //controls reshtmlparamdiv += '<button onclick="simbox_startclick(this,'+id+')" id="simbox_start_'+id+'">START</button><button onclick="simbox_resetclick(this,'+id+')" id="simbox_reset_'+id+'">RESET</button>'; //about reshtmlparamdiv += '
Programmed by Damir Vodenicarevic, based on the work of Patrick Amar and Loïc Paulevé [HSIM: an hybrid stochastic simulation system for systems biology, Electronic Notes in Theoretical Computer Science www.elsevier.nl/locate/entcs]';
paramdiv.innerHTML= reshtmlparamdiv;
simbox_update_controls(id);
return true;

}

function is_posint(str) {

 var parsed= parseInt(str);
 if(isNaN(parsed)) return false;
 if(!isFinite(parsed)) return false;
 if(parsed < 0) return false;
 return true;

}

function simbox_startclick(btn, simid) {

if(simulations[simid].running)
{
  window.clearTimeout(simulations[simid].timeout);
  simulations[simid].running= false;
  btn.innerHTML= 'START';
}
else
{
 for(var i= 0 ; i < simulations[simid].molecules.length ; ++i)
 {
  if(simulations[simid].molecules[i].adjustable)
  {
   var tmpval= document.getElementById('simbox_ctl_'+simid+'_'+i).value;
   if(!is_posint(tmpval))
   {
    alert('Invalid quantity set for molecule "'+simulations[simid].molecules[i].name+'".');
    return false;
   }
   simulations[simid].molecules[i].quantity= parseInt(tmpval);
  }
 }
 simulations[simid].running= true;
 btn.innerHTML= 'PAUSE';
 simbox_refresh(simid);
}
simbox_update_controls(simid);
return true;

}

function simbox_resetclick(btn, simid) {

if(simulations[simid].running)
{
 window.clearTimeout(simulations[simid].timeout);
 simulations[simid].running= false;
 document.getElementById('simbox_start_'+simid).innerHTML= 'START';
}

//Reset quantities, clear curves
simulations[simid].curtime= 0;
simulations[simid].curstep= 0;
for(var i= 0 ; i < simulations[simid].molecules.length ; ++i)
{
 simulations[simid].molecules[i].quantity= simulations[simid].molecules[i].init_qtty;
 simulations[simid].molecules[i].history.length= 0;
}
//Update controls
simbox_update_controls(simid);

}

function simbox_update_controls(simid) {

for(var i= 0 ; i < simulations[simid].molecules.length ; ++i)
{
 if(simulations[simid].molecules[i].adjustable)
 {
  document.getElementById('simbox_ctl_'+simid+'_'+i).value= simulations[simid].molecules[i].quantity;
  document.getElementById('simbox_ctl_'+simid+'_'+i).disabled= simulations[simid].running;
 }
}
document.getElementById('simbox_time_'+simid).innerHTML= 't = ' + simulations[simid].curtime.toPrecision(5) + ' [step n°'+simulations[simid].curstep+']';

}

function simbox_refresh(simid) {

var tmpdate= new Date();
var time1= tmpdate.getTime();
while(tmpdate.getTime() - time1 < simulations[simid].refresh_run_time)
{
 simulation_step(simid);
 tmpdate= new Date();
}

simulations[simid].draw_min_t= (simulations[simid].curtime - 20 < 0 ? 0 : simulations[simid].curtime - 20);
simulations[simid].draw_max_t= simulations[simid].curtime;
simbox_draw_graph(simid);
simbox_update_controls(simid);
simulations[simid].timeout= setTimeout(function() {simbox_refresh(simid);}, simulations[simid].refresh_interval);

}

function shuffle_array(array) {

for(var i= array.length-1; i > 0 ; --i)
{
 var j= Math.floor(Math.random() * (i + 1));
 var tmp= array[i];
 array[i]= array[j];
 array[j]= tmp;
}

}

function simulation_step(simid) {

var sim= simulations[simid];
//Shuffle reaction order
shuffle_array(sim.reac_order);
for(var i= 0 ; i < sim.reac_order.length ; ++i)
{
 var reac= sim.reactions[sim.reac_order[i]];
 var proba= reac.precomp_probfactor;
 if(reac.inputs.length == 1)
  proba *= sim.molecules[reac.inputs[0]].quantity;
 else if(reac.inputs.length == 2)
 {
  if(reac.inputs[0] != reac.inputs[1])
   proba *= sim.molecules[reac.inputs[0]].quantity * sim.molecules[reac.inputs[1]].quantity;
  else if(sim.molecules[reac.inputs[0]].quantity >= 2)
   proba *= sim.molecules[reac.inputs[0]].quantity *(sim.molecules[reac.inputs[0]].quantity - 1.0);
  else
   proba= 0.0;
 }
 var n= Math.floor(proba);
 var frac= proba - n;
 if(frac > 0)
 {
  if(Math.random() < frac)
   ++n;
 }
 for(var elt in reac.precomp_n_consumed)
 {
  if( n * reac.precomp_n_consumed[elt] >= sim.molecules[elt].quantity )
  {
   var tmpe= Math.floor(sim.molecules[elt].quantity / reac.precomp_n_consumed[elt]);
   if(tmpe < n)
    n= tmpe;
  }
 }

 //check for initial possibility
 for(var elt in reac.precomp_n_in)
 {
  if( sim.molecules[elt].quantity < reac.precomp_n_in[elt] )
  {
   n= 0;
   break;
  }
 }
 for(var j= 0 ; j < reac.inputs.length ; ++j)
 {
  if(!sim.molecules[reac.inputs[j]].const)
   sim.molecules[reac.inputs[j]].quantity -= n;
 }
 for(var j= 0 ; j < reac.outputs.length ; ++j)
 {
  if(!sim.molecules[reac.outputs[j]].const)
   sim.molecules[reac.outputs[j]].quantity += n;
 }
}
for(var i= 0 ; i < sim.molecules.length ; ++i)
{
 if(sim.molecules[i].curve_show)
  sim.molecules[i].history.push(sim.molecules[i].quantity);
}
sim.curtime += sim.timestep;
sim.curstep++;

}

function simbox_drawXcoord(simid, t) {

var tmin= simulations[simid].draw_min_t;
var tmax= simulations[simid].draw_max_t;
return simulations[simid].canv.width*(t-tmin)/(tmax-tmin);

} function simbox_drawYcoord(simid, v) {

var vmin= simulations[simid].draw_min_v;
var vmax= simulations[simid].draw_max_v;
return simulations[simid].canv.height*(1.0 - (v-vmin)/(vmax-vmin));

}

function simbox_draw_graph(simid) {

var sim= simulations[simid];
var canv= sim.canv;
var ctx= sim.ctx;
ctx.clearRect(0, 0, canv.width, canv.height);
for(var i= 0 ; i < sim.molecules.length ; ++i)
{
 if(sim.molecules[i].curve_show)
 {
  var dta= sim.molecules[i].history;
  var tstart= Math.floor(sim.draw_min_t/sim.timestep);
  var tend= Math.ceil(sim.draw_max_t/sim.timestep);
  if(tstart > dta.length)
   tstart= dta.length;
  if(tend > dta.length)
   tend= dta.length;
 
  var step= Math.ceil((tend-tstart)/(200));
  ctx.beginPath();
  for(var ti= tstart ; ti < tend ; ti += step)
  {
   if(ti > tstart)
    ctx.lineTo( simbox_drawXcoord(simid, ti*sim.timestep), simbox_drawYcoord(simid, dta[ti]) );
   else
    ctx.moveTo( simbox_drawXcoord(simid, ti*sim.timestep), simbox_drawYcoord(simid, dta[ti]) );
  }
  ctx.strokeStyle= sim.molecules[i].curve_color;
  ctx.stroke();
 }
}

}


window.addEventListener("load", simboxes_load, false);