/** * Reaction-diffusion system (FitzHugh-Nagumo equation) * by Kazuki Maeda * Last-Modified: Apr 11, 2017 */ var WIDTH = 200, HEIGHT = 200; var maxNumCanvas = 2; var delta = 0.01, epsilon = 0.1; var timeInterval = 1000/60; var ncMinU = -1.1, ncMaxU = 1.1; var ncMinV = -1.1, ncMaxV = 1.1; var thread; var T; var Du, Dv, ca, cb, cc; var canvasU, ctxU, imageU; var canvasV, ctxV, imageV; var u, v, pu, pv; var numCanvas var nullcline, ncDist, ncDistCtx, ncDistImage; var ncCurveU, ncCurveV; var ncDistArray; window.onload = function(){ document.getElementById('startstop').addEventListener('click', startstop); document.getElementById('random').addEventListener('click', resetRandom); document.getElementById('target').addEventListener('click', resetTarget); document.getElementById('I').addEventListener('click', resetI); document.getElementById('Y').addEventListener('click', resetY); document.getElementById('double').addEventListener('click', showDouble); for(var c = 0; c < maxNumCanvas; ++c){ document.getElementById('ca'+c).addEventListener('keypress', updateNullclineK); document.getElementById('cb'+c).addEventListener('keypress', updateNullclineK); document.getElementById('cc'+c).addEventListener('keypress', updateNullclineK); document.getElementById('ca'+c).addEventListener('blur', updateNullcline); document.getElementById('cb'+c).addEventListener('blur', updateNullcline); document.getElementById('cc'+c).addEventListener('blur', updateNullcline); } init(); } function init(){ Du = [], Dv = [], ca = [], cb = [], cc = []; canvasU = [], canvasV = [], ctxU = [], ctxV = [], imageU = [], imageV = []; nullcline = [], ncDist = [], ncCurveU = [], ncCurveV = []; ncDistCtx = [], ncDistImage = []; var ncAxisU = [], ncAxisV = [], ncLabelU = [], ncLabelV = []; for(var c = 0; c < maxNumCanvas; ++c){ canvasU[c] = document.createElement('canvas'); canvasV[c] = document.createElement('canvas'); canvasU[c].width = WIDTH, canvasV[c].width = WIDTH; canvasU[c].height = HEIGHT, canvasV[c].height = HEIGHT; canvasU[c].style.verticalAlign = 'middle', canvasV[c].style.verticalAlign = 'middle'; nullcline[c] = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); ncDist[c] = document.createElement('canvas'); nullcline[c].style.verticalAlign = 'middle'; nullcline[c].style.position = 'absolute'; ncDist[c].style.verticalAlign = 'middle'; ncDist[c].style.position = 'absolute'; ncDist[c].width = WIDTH, ncDist[c].height = HEIGHT; document.getElementById('canvas'+c).removeChild(document.getElementById('canvas'+c).firstChild); document.getElementById('canvas'+c).appendChild(document.createTextNode('$u$: ')); document.getElementById('canvas'+c).appendChild(canvasU[c]); document.getElementById('canvas'+c).appendChild(document.createTextNode(' $v$: ')); document.getElementById('canvas'+c).appendChild(canvasV[c]); document.getElementById('canvas'+c).appendChild(document.createTextNode(' nullcline: ')); document.getElementById('canvas'+c).appendChild(nullcline[c]); document.getElementById('canvas'+c).appendChild(ncDist[c]); ctxU[c] = canvasU[c].getContext('2d'), ctxV[c] = canvasV[c].getContext('2d'); imageU[c] = ctxU[c].getImageData(0, 0, WIDTH, HEIGHT); imageV[c] = ctxV[c].getImageData(0, 0, WIDTH, HEIGHT); for(var i = 0; i < WIDTH*HEIGHT; ++i){ imageU[c].data[i*4+0] = 0; // R imageV[c].data[i*4+1] = 0; // G imageU[c].data[i*4+2] = 0; // B imageU[c].data[i*4+3] = 255, imageV[c].data[i*4+3] = 255; // A } nullcline[c].style.backgroundColor = 'black'; nullcline[c].setAttribute('width', WIDTH); nullcline[c].setAttribute('height', HEIGHT); ncAxisU[c] = document.createElementNS('http://www.w3.org/2000/svg', 'line'); ncAxisU[c].setAttribute('x1', ''+ncTrU(ncMinU)); ncAxisU[c].setAttribute('y1', ''+ncTrV(0)); ncAxisU[c].setAttribute('x2', ''+ncTrU(ncMaxU)); ncAxisU[c].setAttribute('y2', ''+ncTrV(0)); ncAxisU[c].setAttribute('stroke', 'white'); ncAxisU[c].setAttribute('stroke-width', '1'); nullcline[c].appendChild(ncAxisU[c]); ncAxisV[c] = document.createElementNS('http://www.w3.org/2000/svg', 'line'); ncAxisV[c].setAttribute('x1', ''+ncTrU(0)); ncAxisV[c].setAttribute('y1', ''+ncTrV(ncMinV)); ncAxisV[c].setAttribute('x2', ''+ncTrU(0)); ncAxisV[c].setAttribute('y2', ''+ncTrV(ncMaxV)); ncAxisV[c].setAttribute('stroke', 'white'); ncAxisV[c].setAttribute('stroke-width', '1'); nullcline[c].appendChild(ncAxisV[c]); ncCurveU[c] = document.createElementNS('http://www.w3.org/2000/svg', 'polyline'); ncCurveU[c].setAttribute('stroke', '#00FF00'); ncCurveU[c].setAttribute('stroke-width', '3'); ncCurveU[c].setAttribute('fill', 'none'); nullcline[c].appendChild(ncCurveU[c]); ncCurveV[c] = document.createElementNS('http://www.w3.org/2000/svg', 'polyline'); ncCurveV[c].setAttribute('stroke', '#FF00FF'); ncCurveV[c].setAttribute('stroke-width', '3'); ncCurveV[c].setAttribute('fill', 'none'); nullcline[c].appendChild(ncCurveV[c]); ncLabelU[c] = document.createElementNS('http://www.w3.org/2000/svg', 'text'); ncLabelU[c].setAttribute('x', ''+(ncTrU(ncMaxU)-10)); ncLabelU[c].setAttribute('y', ''+(ncTrV(0)+10*1.5)); ncLabelU[c].setAttribute('font-size', '10pt'); ncLabelU[c].setAttribute('font-family', 'serif'); ncLabelU[c].setAttribute('font-style', 'italic'); ncLabelU[c].setAttribute('fill', 'white'); ncLabelU[c].textContent = 'u'; nullcline[c].appendChild(ncLabelU[c]); ncLabelV[c] = document.createElementNS('http://www.w3.org/2000/svg', 'text'); ncLabelV[c].setAttribute('x', ''+(ncTrU(0)+10*0.5)); ncLabelV[c].setAttribute('y', ''+(ncTrV(ncMaxV)+10)); ncLabelV[c].setAttribute('font-size', '10pt'); ncLabelV[c].setAttribute('font-family', 'serif'); ncLabelV[c].setAttribute('font-style', 'italic'); ncLabelV[c].setAttribute('fill', 'white'); ncLabelV[c].textContent = 'v'; nullcline[c].appendChild(ncLabelV[c]); ncDistCtx[c] = ncDist[c].getContext('2d'); ncDistImage[c] = ncDistCtx[c].getImageData(0, 0, WIDTH, HEIGHT); for(var i = 0; i < WIDTH*HEIGHT; ++i){ ncDistImage[c].data[i*4+0] = 255; // R ncDistImage[c].data[i*4+1] = 255; // G ncDistImage[c].data[i*4+2] = 255; // B ncDistImage[c].data[i*4+3] = 0; // A } } ncDistArray = []; for(var i = 0; i < HEIGHT; ++i) ncDistArray[i] = []; numCanvas = 1; updateNullcline(null); resetRandom(null); } function resetRandom(e){ u = [], v = [], pu = [], pv = []; for(var c = 0; c < maxNumCanvas; ++c){ u[c] = [], v[c] = [], pu[c] = [], pv[c] = []; for(var i = 0; i < HEIGHT; ++i){ u[c][i] = [], v[c][i] = [], pu[c][i] = [], pv[c][i] = []; for(var j = 0; j < WIDTH; ++j){ if(c == 0){ u[c][i][j] = Math.random()*2-1; v[c][i][j] = Math.random()*2-1; } else { u[c][i][j] = u[0][i][j]; v[c][i][j] = v[0][i][j]; } } } } updateImage(); } function resetTarget(e){ u = [], v = [], pu = [], pv = []; for(var c = 0; c < maxNumCanvas; ++c){ u[c] = [], v[c] = [], pu[c] = [], pv[c] = []; for(var i = 0; i < HEIGHT; ++i){ u[c][i] = [], v[c][i] = [], pu[c][i] = [], pv[c][i] = []; for(var j = 0; j < WIDTH; ++j){ if(j > 0.49*WIDTH && j < 0.51*WIDTH && i > 0.49*HEIGHT && i < 0.51*HEIGHT){ u[c][i][j] = 1.0; v[c][i][j] = 0.0; } else { u[c][i][j] = 0.0; v[c][i][j] = 0.0; } } } } updateImage(); } function resetI(e){ u = [], v = [], pu = [], pv = []; for(var c = 0; c < maxNumCanvas; ++c){ u[c] = [], v[c] = [], pu[c] = [], pv[c] = []; for(var i = 0; i < HEIGHT; ++i){ u[c][i] = [], v[c][i] = [], pu[c][i] = [], pv[c][i] = []; for(var j = 0; j < WIDTH; ++j){ if(j > 0.49*WIDTH && j < 0.51*WIDTH){ u[c][i][j] = 1.0; v[c][i][j] = 0.0; } else { u[c][i][j] = 0.0; v[c][i][j] = 0.0; } } } } updateImage(); } function resetY(e){ u = [], v = [], pu = [], pv = []; for(var c = 0; c < maxNumCanvas; ++c){ u[c] = [], v[c] = [], pu[c] = [], pv[c] = []; for(var i = 0; i < HEIGHT; ++i){ u[c][i] = [], v[c][i] = [], pu[c][i] = [], pv[c][i] = []; for(var j = 0; j < WIDTH; ++j){ if(j > 0.49*WIDTH && j < 0.51*WIDTH && i > 0.49*HEIGHT){ u[c][i][j] = 1.0; v[c][i][j] = 0.0; } else { u[c][i][j] = 0.0; v[c][i][j] = 0.0; } } } for(var j = 0; j < 0.5*WIDTH; ++j){ for(var i = 0; i < 0.02*HEIGHT; ++i){ var tmpi = parseInt(j-0.01*HEIGHT+i); if(tmpi >= 0){ u[c][tmpi][j] = 1.0; u[c][tmpi][WIDTH-1-j] = 1.0; } } } } updateImage(); } function updateImage(){ // canvas for(var c = 0; c < numCanvas; ++c){ for(var i = 0; i < HEIGHT; ++i) for(var j = 0; j < WIDTH; ++j){ var val = parseInt((u[c][i][j]+1.0)/2*255); if(val < 0) val = 0; if(val > 255) val = 255; imageU[c].data[4*(i*WIDTH+j)+1] = val; // G val = parseInt((v[c][i][j]+1.0)/2*255); if(val < 0) val = 0; if(val > 255) val = 255; imageV[c].data[4*(i*WIDTH+j)+0] = val; // R imageV[c].data[4*(i*WIDTH+j)+2] = val; // B } ctxU[c].putImageData(imageU[c], 0, 0), ctxV[c].putImageData(imageV[c], 0, 0); } // distribution on nullcline for(var c = 0; c < numCanvas; ++c){ // init for(var i = 0; i < HEIGHT; ++i) for(var j = 0; j < WIDTH; ++j) ncDistArray[i][j] = 0; // construct for(var i = 0; i < HEIGHT; ++i) for(var j = 0; j < WIDTH; ++j){ var iU = parseInt(ncTrU(u[c][i][j])); var iV = parseInt(ncTrV(v[c][i][j])); if(iU >= 0 && iU < WIDTH && iV >= 0 && iV < HEIGHT) ++ncDistArray[iV][iU]; } // draw for(var i = 0; i < HEIGHT; ++i) for(var j = 0; j < WIDTH; ++j){ var val = parseInt(1.0*ncDistArray[i][j]/(WIDTH*HEIGHT/2000)*255); if(val > 255) val = 255; ncDistImage[c].data[4*(i*WIDTH+j)+3] = val; // A } ncDistCtx[c].putImageData(ncDistImage[c], 0, 0); } } function updateNullcline(e){ for(var c = 0; c < numCanvas; ++c){ ca[c] = parseFloat(document.getElementById('ca'+c).value); cb[c] = parseFloat(document.getElementById('cb'+c).value); cc[c] = parseFloat(document.getElementById('cc'+c).value); var points, unit; /* v = u-u^3 */ unit = parseFloat(ncMaxU-ncMinU)/WIDTH; points = '' + ncTrU(ncMinU) + ',' + ncTrV(ncMinU-ncMinU*ncMinU*ncMinU); for(var r = ncMinU+unit; r <= ncMaxU+unit; r += unit){ points += ' ' + ncTrU(r) + ',' + ncTrV(r-r*r*r); } ncCurveU[c].setAttribute('points', points); /* u = bv + c */ if(ca[c] != 0){ unit = parseFloat(ncMaxV-ncMinV)/HEIGHT; points = '' + ncTrU(cb[c]*ncMinV+cc[c]) + ',' + ncTrV(ncMinV); for(var r = ncMinV+unit; r <= ncMaxV+unit; r += unit){ points += ' ' + ncTrU(cb[c]*r+cc[c]) + ',' + ncTrV(r); } } else { points = ''; } ncCurveV[c].setAttribute('points', points); } } function updateNullclineK(e){ if(e.keyCode == 13) updateNullcline(null); } function ncTrU(x){ return (parseFloat(x)-ncMinU)/(ncMaxU-ncMinU)*WIDTH; } function ncTrV(y){ return HEIGHT-(parseFloat(y)-ncMinV)/(ncMaxV-ncMinV)*HEIGHT; } function startstop(e){ if(document.getElementById('startstop').value == 'Start') { document.getElementById('random').disabled = true; document.getElementById('target').disabled = true; document.getElementById('speed').disabled = true; document.getElementById('double').disabled = true; document.getElementById('I').disabled = true; document.getElementById('Y').disabled = true; T = parseFloat(document.getElementById('speed').value); for(var c = 0; c < numCanvas; ++c){ document.getElementById('Du'+c).disabled = true; document.getElementById('Dv'+c).disabled = true; document.getElementById('ca'+c).disabled = true; document.getElementById('cb'+c).disabled = true; document.getElementById('cc'+c).disabled = true; Du[c] = parseFloat(document.getElementById('Du'+c).value); Dv[c] = parseFloat(document.getElementById('Dv'+c).value); ca[c] = parseFloat(document.getElementById('ca'+c).value); cb[c] = parseFloat(document.getElementById('cb'+c).value); cc[c] = parseFloat(document.getElementById('cc'+c).value); } updateNullcline(null); thread = setInterval('calc();', timeInterval); document.getElementById('startstop').value = 'Stop'; } else { document.getElementById('random').disabled = false; document.getElementById('target').disabled = false; document.getElementById('I').disabled = false; document.getElementById('Y').disabled = false; document.getElementById('speed').disabled = false; document.getElementById('double').disabled = false; for(var c = 0; c < numCanvas; ++c){ document.getElementById('Du'+c).disabled = false; document.getElementById('Dv'+c).disabled = false; document.getElementById('ca'+c).disabled = false; document.getElementById('cb'+c).disabled = false; document.getElementById('cc'+c).disabled = false; } clearInterval(thread); document.getElementById('startstop').value = 'Start'; } } function showDouble(){ if(document.getElementById('double').checked){ document.getElementById('canvas1').style.display = 'block'; document.getElementById('control1').style.display = 'block'; numCanvas = 2; updateNullcline(null); updateImage(); } else { document.getElementById('canvas1').style.display = 'none'; document.getElementById('control1').style.display = 'none'; numCanvas = 1; } } function calc(){ updateImage(); for(var t = 0; t < T; ++t){ // swap var tmp; tmp = pu, pu = u, u = tmp; tmp = pv, pv = v, v = tmp; for(var c = 0; c < numCanvas; ++c){ // FitzHugh-Nagumo for(var i = 0 ; i < HEIGHT; ++i) for(var j = 0; j < WIDTH; ++j){ u[c][i][j] = pu[c][i][j]+delta*(Du[c]*(pu[c][trH(i+1)][j]+pu[c][trH(i-1)][j]+pu[c][i][trW(j+1)]+pu[c][i][trW(j-1)]-4.0*pu[c][i][j])/epsilon/epsilon+pu[c][i][j]-pu[c][i][j]*pu[c][i][j]*pu[c][i][j]-pv[c][i][j]); v[c][i][j] = pv[c][i][j]+delta*(Dv[c]*(pv[c][trH(i+1)][j]+pv[c][trH(i-1)][j]+pv[c][i][trW(j+1)]+pv[c][i][trW(j-1)]-4.0*pv[c][i][j])/epsilon/epsilon+ca[c]*(pu[c][i][j]-cb[c]*pv[c][i][j]-cc[c])); } } } } // periodic condition function trW(x){ return x == WIDTH ? 0 : x == -1 ? WIDTH-1 : x; } function trH(x){ return x == HEIGHT ? 0 : x == -1 ? HEIGHT-1 : x; }