initial
This commit is contained in:
commit
fb20596fba
36 changed files with 8679 additions and 0 deletions
131
www/src/Objective.js
Normal file
131
www/src/Objective.js
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
import { RK4, RK4getvalue } from "./runge-kutta.js";
|
||||
export class Objective {
|
||||
constructor(exper, f, res, mSize) {
|
||||
/*
|
||||
* res: a resolução utilizada para a resolução com RK4
|
||||
* mSize: numero de variáveis do modelo atual
|
||||
* exper: tabela de valores obtidos experimentalmente
|
||||
*/
|
||||
this.exper = exper;
|
||||
this.sampleSize = exper.length;
|
||||
// Tamanho da amostra obtida experimentalmente (número de pontos)
|
||||
this.f = f;
|
||||
// Função f(t) a ser utilizada para o ajuste
|
||||
// Vetor de tempos para resolução numérica
|
||||
let tf = exper[this.sampleSize - 1][0];
|
||||
this.timeArray = [];
|
||||
for (let i = 0; i <= res; i++) {
|
||||
this.timeArray[i] = (i * tf) / res;
|
||||
}
|
||||
// valor inicial é o primeiro ponto experimental
|
||||
this.y0 = [exper[1][2], exper[1][1]];
|
||||
|
||||
// Se o número de variaveis na tabela (descontando a coluna tempo)
|
||||
// for maior que o numero de variáveis do modelo,
|
||||
// a função objetivo é calculada com o número de variáveis do modelo.
|
||||
// Caso contrário, ela é calculada com o numero de variáveis da tabela.
|
||||
if (exper[1].length - 1 >= mSize) {
|
||||
this.nVar = mSize;
|
||||
} else {
|
||||
this.nVar = exper[1].length - 1;
|
||||
}
|
||||
|
||||
const headerRow = Array.isArray(exper[0]) ? exper[0] : [];
|
||||
const normalizedHeaders = headerRow.map((value) => {
|
||||
if (typeof value !== "string") {
|
||||
return "";
|
||||
}
|
||||
return value
|
||||
.normalize("NFD")
|
||||
.replace(/[\u0300-\u036f]/g, "")
|
||||
.toLowerCase();
|
||||
});
|
||||
|
||||
const dataColumns = exper[1] ? exper[1].length : 0;
|
||||
const maxValidIndex = Math.max(1, dataColumns - 1);
|
||||
const clampColumnIndex = (candidate) => {
|
||||
if (!Number.isFinite(candidate) || candidate < 1 || candidate >= dataColumns) {
|
||||
return Math.min(Math.max(1, candidate || 1), maxValidIndex);
|
||||
}
|
||||
return candidate;
|
||||
};
|
||||
|
||||
const findColumnIndex = (keywords, fallback) => {
|
||||
const idx = normalizedHeaders.findIndex((name, columnIndex) => {
|
||||
if (columnIndex === 0) {
|
||||
return false;
|
||||
}
|
||||
return keywords.some((keyword) => name.includes(keyword));
|
||||
});
|
||||
const candidate = idx > 0 ? idx : fallback;
|
||||
return clampColumnIndex(candidate);
|
||||
};
|
||||
|
||||
this.stateColumnIndex = [];
|
||||
if (this.nVar >= 1) {
|
||||
const fallbackCells = Math.min(2, maxValidIndex);
|
||||
this.stateColumnIndex[0] = findColumnIndex(
|
||||
["celula", "celulas", "celula", "cell", "cells", "biomassa", "biomass", "x"],
|
||||
fallbackCells,
|
||||
);
|
||||
}
|
||||
if (this.nVar >= 2) {
|
||||
this.stateColumnIndex[1] = findColumnIndex([
|
||||
"substrato",
|
||||
"substrate",
|
||||
"substrat",
|
||||
"s",
|
||||
], 1);
|
||||
}
|
||||
for (let j = 2; j < this.nVar; j++) {
|
||||
this.stateColumnIndex[j] = clampColumnIndex(Math.min(j + 1, maxValidIndex));
|
||||
}
|
||||
|
||||
// Média das colunas para normalização
|
||||
this.mean = new Array(this.nVar).fill(0);
|
||||
for (let i = 1; i < this.sampleSize; i++) {
|
||||
for (let j = 0; j < this.nVar; j++) {
|
||||
const columnIndex = this.stateColumnIndex[j];
|
||||
if (columnIndex < exper[i].length) {
|
||||
this.mean[j] += exper[i][columnIndex];
|
||||
}
|
||||
}
|
||||
}
|
||||
for (let j = 0; j < this.nVar; j++) {
|
||||
this.mean[j] /= this.sampleSize - 1;
|
||||
}
|
||||
}
|
||||
objective(params) {
|
||||
// Resolve o modelo pelo método RK4
|
||||
let sol = RK4(this.f, this.timeArray, this.y0, params);
|
||||
|
||||
// Pega predições pontuais do modelo
|
||||
let pSol = [];
|
||||
for (let i = 1; i < this.sampleSize; i++) {
|
||||
pSol[i - 1] = RK4getvalue(
|
||||
sol,
|
||||
this.timeArray,
|
||||
this.exper[i][0],
|
||||
this.f,
|
||||
params,
|
||||
);
|
||||
}
|
||||
|
||||
// Cálculo da função objetivo
|
||||
let obj = 0;
|
||||
for (let i = 2; i < this.sampleSize; i++) {
|
||||
for (let j = 0; j < this.nVar; j++) {
|
||||
const columnIndex = this.stateColumnIndex[j];
|
||||
if (columnIndex >= this.exper[i].length) {
|
||||
continue;
|
||||
}
|
||||
const observed = this.exper[i][columnIndex];
|
||||
const predicted = pSol[i - 1][j];
|
||||
const denom = this.mean[j] !== 0 ? this.mean[j] : 1;
|
||||
obj += ((predicted - observed) / denom) ** 2;
|
||||
}
|
||||
}
|
||||
// console.log(obj)
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
93
www/src/PSO.js
Normal file
93
www/src/PSO.js
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
import { rrandom } from "./rrandom.js";
|
||||
export class PSO {
|
||||
constructor(obj, n, bounds) {
|
||||
/*
|
||||
obj: função objetivo a ser minimizada
|
||||
n: numero de iterações a serem performadas
|
||||
bounds: vetor com os mínimos e maximos dos parametros da busca
|
||||
*/
|
||||
this.obj = obj;
|
||||
this.n = n;
|
||||
this.bounds = bounds;
|
||||
this.pos = [];
|
||||
this.vel = [];
|
||||
this.err = [];
|
||||
this.pos_best = [];
|
||||
this.err_best = [];
|
||||
|
||||
this.dimensions = bounds.length; // número de dimensões da busca (parâmetros)
|
||||
|
||||
for (var i = 0; i < this.n; i++) {
|
||||
/*
|
||||
inicia o vetor vel com valores aleatórios entre -1 e 1
|
||||
inicia o vetor pos com posições aleatórias dentro dos limites especificados
|
||||
*/
|
||||
var v = [];
|
||||
var x = [];
|
||||
for (var j = 0; j < this.dimensions; j++) {
|
||||
v.push(rrandom(-1, 1));
|
||||
x.push(rrandom(bounds[j][0], bounds[j][1]));
|
||||
}
|
||||
|
||||
this.vel.push(v.slice());
|
||||
this.pos.push(x.slice());
|
||||
this.pos_best.push(x.slice()); // inicia a melhor posição como a posiçaõ atual (aleatória)
|
||||
this.err_best.push(Infinity);
|
||||
}
|
||||
|
||||
this.err_best_g = Infinity;
|
||||
// inicia o melhor erro global como infinito (quanto menor melhor)
|
||||
this.pos_best_g = this.pos[0].slice();
|
||||
// inicia a melhor posição global como a posição da particula 0
|
||||
}
|
||||
|
||||
run(c1, c2, w, iteration) {
|
||||
for (let i = 0; i < iteration; i++) {
|
||||
this.update(c1, c2, w);
|
||||
// console.log(i, "/", iteration, "\t", this.err_best_g);
|
||||
}
|
||||
|
||||
console.log(this.pos_best_g);
|
||||
// console.log(this.err_best_g)
|
||||
// console.table(this.pos)
|
||||
}
|
||||
|
||||
update(c1, c2, w) {
|
||||
for (let i = 0; i < this.n; i++) {
|
||||
for (let j = 0; j < this.dimensions; j++) {
|
||||
let r1 = rrandom(0, 1);
|
||||
let r2 = rrandom(0, 1);
|
||||
|
||||
let vel_cognitive = c1 * r1 * (this.pos_best[i][j] - this.pos[i][j]);
|
||||
let vel_social = c2 * r2 * (this.pos_best_g[j] - this.pos[i][j]);
|
||||
const updatedVelocity = w * this.vel[i][j] + vel_cognitive + vel_social;
|
||||
let candidatePosition = this.pos[i][j] + updatedVelocity;
|
||||
|
||||
if (candidatePosition > this.bounds[j][1]) {
|
||||
candidatePosition = this.bounds[j][1];
|
||||
this.vel[i][j] = 0;
|
||||
} else if (candidatePosition < this.bounds[j][0]) {
|
||||
candidatePosition = this.bounds[j][0];
|
||||
this.vel[i][j] = 0;
|
||||
} else {
|
||||
this.vel[i][j] = updatedVelocity;
|
||||
}
|
||||
|
||||
this.pos[i][j] = candidatePosition;
|
||||
}
|
||||
|
||||
// Update error value
|
||||
this.err[i] = this.obj.objective(this.pos[i]);
|
||||
|
||||
// Update global values
|
||||
if (this.err[i] < this.err_best[i]) {
|
||||
this.pos_best[i] = this.pos[i].slice();
|
||||
this.err_best[i] = this.err[i];
|
||||
if (this.err[i] < this.err_best_g) {
|
||||
this.pos_best_g = this.pos[i].slice();
|
||||
this.err_best_g = this.err[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
49
www/src/conhecidos.js
Normal file
49
www/src/conhecidos.js
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
// MONOD, 1942
|
||||
export function monod(S, mu_max, K_S) {
|
||||
return (mu_max * S) / (K_S + S);
|
||||
}
|
||||
|
||||
// MOSER, 1958
|
||||
export function moser(S, mu_max, K_S, n) {
|
||||
return (mu_max * S ** n) / (K_S + S ** n);
|
||||
}
|
||||
|
||||
// CONTOIS, 1959
|
||||
export function contois(S, X, mu_max, K_S) {
|
||||
return (mu_max * S) / (K_S * X + S);
|
||||
}
|
||||
|
||||
// BERGTER, 1983
|
||||
export function bergter(S, t, mu_max, K_S, T) {
|
||||
return ((mu_max * S) / (K_S + S)) * (1 - Math.exp(-t / T));
|
||||
}
|
||||
|
||||
// TESSIER, 1942
|
||||
export function tessier(S, mu_max, K_S) {
|
||||
return mu_max * (1 - Math.exp(-S / K_S));
|
||||
}
|
||||
|
||||
// ANDREWS, 1968
|
||||
export function andrews(S, mu_max, K_S, K_I) {
|
||||
return (mu_max * S) / (K_S + S + (S ** 2) / K_I);
|
||||
}
|
||||
|
||||
// AIBA; SHODA; NAGATANI, 1968
|
||||
export function aiba(S, mu_max, K_S, K_I) {
|
||||
return ((mu_max * S) / (K_S + S)) * Math.exp(-K_I * S);
|
||||
}
|
||||
|
||||
// Morte celular
|
||||
|
||||
// SINCLAIR; KRISTIANSEN, 1987
|
||||
export function death(K_d) {
|
||||
return -K_d;
|
||||
}
|
||||
|
||||
// Consumo do substrato limitante para manutenção
|
||||
|
||||
// PIRT, 1965
|
||||
|
||||
export function pirt(mu, Y_XS, m_S) {
|
||||
return (1 / Y_XS) * mu + m_S;
|
||||
}
|
||||
13
www/src/consulta/model.js
Normal file
13
www/src/consulta/model.js
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
function model(time, y, params) {
|
||||
let K_S = params[0];
|
||||
let mu_max = params[1];
|
||||
let m_S = params[2];
|
||||
let Y_XS = params[3];
|
||||
let X = y[0];
|
||||
let S = y[1];
|
||||
let dmu = monod(S, mu_max, K_S);
|
||||
let dX = X * dmu;
|
||||
const qS = pirt(dmu, Y_XS, m_S);
|
||||
let dS = -qS * X;
|
||||
return [dX, dS];
|
||||
}
|
||||
170
www/src/consulta/modelos.js
Normal file
170
www/src/consulta/modelos.js
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
// Crescimento num único substrato limitante:
|
||||
|
||||
// MONOD, 1942
|
||||
|
||||
class monod {
|
||||
constructor() {
|
||||
this.nPar = 2;
|
||||
this.parName = ["mu_max", "K_S"]
|
||||
this.varName = ["Substrato"]
|
||||
this.varUsed = [1, 0, 0, 0, 0] // sub1, sub2, cel, prod, tempo
|
||||
this.equation = "\\mu_{X} = \\frac{\\mu_{max} S}{Ks + S}"
|
||||
this.name = "MONOD, 1942"
|
||||
}
|
||||
model(variavel, parametros) {
|
||||
return (parametros[0] * variavel[0]) / (parametros[1] + variavel[0])
|
||||
}
|
||||
}
|
||||
|
||||
// MOSER, 1958
|
||||
class moser {
|
||||
constructor() {
|
||||
this.nPar = 3;
|
||||
this.parName = ["mu_max", "K_S", "n"]
|
||||
this.varName = ["Substrato"]
|
||||
this.varUsed = [1, 0, 0, 0, 0] // sub1, sub2, cel, prod, tempo
|
||||
this.equation = "\\mu_{X} = \\frac{\\mu_{max} S^n}{Ks + S^n}"
|
||||
this.name = "MOSER, 1958"
|
||||
}
|
||||
model(variavel, parametros) {
|
||||
// moser(S, mu_max, K_S, n) {
|
||||
return (parametros[0] * variavel[0] ** parametros[2]) / (parametros[1] + variavel[0] ** parametros[2])
|
||||
}
|
||||
}
|
||||
|
||||
// CONTOIS, 1959
|
||||
class contois {
|
||||
constructor() {
|
||||
this.nPar = 2;
|
||||
this.parName = ["mu_max", "K_S"]
|
||||
this.varName = ["Substrato", "Células"]
|
||||
this.varUsed = [1, 0, 1, 0, 0] // sub1, sub2, cel, prod, tempo
|
||||
}
|
||||
model(variavel, parametros) {
|
||||
// model(S, X, mu_max, K_S) {
|
||||
return (parametros[0] * variavel[0]) / (parametros[1] * variavel[1] + variavel[0])
|
||||
}
|
||||
}
|
||||
|
||||
// BERGTER, 1983
|
||||
class bergter {
|
||||
constructor() {
|
||||
this.nPar = 3;
|
||||
this.parName = ["mu_max", "K_S", "T"]
|
||||
this.varName = ["Substrato", "tempo"]
|
||||
this.varUsed = [1, 0, 0, 0, 1] // sub1, sub2, cel, prod, tempo
|
||||
}
|
||||
model(variavel, parametros) {
|
||||
// (S, t, mu_max, K_S, T) {
|
||||
return ((parametros[0] * variavel[0]) / (parametros[1] + variavel[0])) * (1 - Math.exp(-variavel[1] / parametros[2]))
|
||||
}
|
||||
}
|
||||
|
||||
// Morte celular
|
||||
|
||||
// SINCLAIR; KRISTIANSEN, 1987
|
||||
class death {
|
||||
constructor() {
|
||||
this.nPar = 1;
|
||||
this.parName = ["k_d"]
|
||||
this.varName = []
|
||||
this.varUsed = [0, 0, 0, 0, 0] // sub1, sub2, cel, prod, tempo
|
||||
}
|
||||
model(parametros) {
|
||||
return -parametros[0]
|
||||
}
|
||||
}
|
||||
|
||||
// Crescimento em um único substrato limitante e inibidor
|
||||
|
||||
// AIBA; SHODA; NAGATANI; 1968
|
||||
class asn {
|
||||
constructor() {
|
||||
this.nPar = 3
|
||||
this.parName = ["mu_max", "K_S", "K_i"]
|
||||
this.varName = ["Substrato"]
|
||||
this.varUsed = [1, 0, 0, 0, 0] // sub1, sub2, cel, prod, tempo
|
||||
}
|
||||
model(variavel, parametros) {
|
||||
return ((parametros[0] * variavel[0]) / (parametros[1] + variavel[0])) * Math.exp(-variavel[0] / parametros[2])
|
||||
}
|
||||
}
|
||||
|
||||
// HALDANE, 1930
|
||||
class haldane {
|
||||
constructor() {
|
||||
this.nPar = 3
|
||||
this.parName = ["mu_xa", "K_S", "K_i"]
|
||||
this.varName = ["Substrato"]
|
||||
this.varUsed = [1, 0, 0, 0, 0] // sub1, sub2, cel, prod, tempo
|
||||
}
|
||||
model(variavel, parametros) {
|
||||
return (parametros[0] * variavel[0]) / (1 + parametros[1] / variavel[0] + variavel[0] / parametros[2])
|
||||
}
|
||||
}
|
||||
|
||||
// ANDREWS, 1968
|
||||
class andrews {
|
||||
constructor() {
|
||||
this.nPar = 3
|
||||
this.parName = ["mu_xa", "K_S", "K_i"]
|
||||
this.varName = ["Substrato"]
|
||||
this.varUsed = [1, 0, 0, 0, 0] // sub1, sub2, cel, prod, tempo
|
||||
}
|
||||
model(variavel, parametros) {
|
||||
return (parametros[0] * variavel[0]) / (variavel[0] + parametros[1] + (variavel[0] ** 2 / parametros[2]))
|
||||
}
|
||||
}
|
||||
|
||||
// EDWARDS, 1970
|
||||
class edwards {
|
||||
constructor() {
|
||||
this.nPar = 3
|
||||
this.parName = ["mu_xa", "K_S", "K_i"]
|
||||
this.varName = ["Substrato"]
|
||||
this.varUsed = [1, 0, 0, 0, 0] // sub1, sub2, cel, prod, tempo
|
||||
}
|
||||
model(variavel, parametros) {
|
||||
return (parametros[0] * variavel[0]) / (variavel[0] + parametros[1] + (variavel[0] ** 2 / parametros[2]) + (1 + variavel[0] / parametros[1]))
|
||||
}
|
||||
}
|
||||
|
||||
// WEBB, 1963
|
||||
class webb {
|
||||
constructor() {
|
||||
this.nPar = 3
|
||||
this.parName = ["mu_xa", "K_S", "K_i"]
|
||||
this.varName = ["Substrato"]
|
||||
this.varUsed = [1, 0, 0, 0, 0] // sub1, sub2, cel, prod, tempo
|
||||
}
|
||||
model(variavel, parametros) {
|
||||
return (parametros[0] * variavel[0] * (1 + variavel[0] / parametros[2])) / (variavel[0] + parametros[1] + (variavel[0] ** 2 / parametros[2]))
|
||||
}
|
||||
}
|
||||
|
||||
// TEISSIER, 1942
|
||||
class teissier {
|
||||
constructor() {
|
||||
this.nPar = 3
|
||||
this.parName = ["mu_max", "K_S", "K_i"]
|
||||
this.varName = ["Substrato"]
|
||||
this.varUsed = [1, 0, 0, 0, 0] // sub1, sub2, cel, prod, tempo
|
||||
}
|
||||
model(variavel, parametros) {
|
||||
return parametros[0] * (Math.exp(-variavel[0] / parametros[1]) - Math.exp(-variavel[0] / parametros[2]))
|
||||
}
|
||||
}
|
||||
|
||||
// WU et al., 1988
|
||||
class wu {
|
||||
constructor() {
|
||||
this.nPar = 4
|
||||
this.parName = ["mu_xa", "K_S", "K_i", "n"]
|
||||
this.varName = ["Substrato"]
|
||||
this.varUsed = [1, 0, 0, 0, 0] // sub1, sub2, cel, prod, tempo
|
||||
}
|
||||
model(variavel, parametros) {
|
||||
// return mu_xa / (1 + K_S / S + (S / K_i) ** n)
|
||||
return parametros[0] / (1 + parametros[1] / variavel[0] + (variavel[0] / parametros[2]) ** parametros[3])
|
||||
}
|
||||
}
|
||||
69
www/src/jquery.jslatex.js
Normal file
69
www/src/jquery.jslatex.js
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* jsLaTeX v1.2 - jQuery plugin
|
||||
*
|
||||
* Copyright (c) 2009 Andreas Grech
|
||||
*
|
||||
* Dual licensed under the MIT and GPL licenses:
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*
|
||||
* http://knowledge-aholic.blogspot.com
|
||||
*/
|
||||
|
||||
(function ($) {
|
||||
var attachToImage = function () {
|
||||
return $("<img/>").attr({
|
||||
src: this.src
|
||||
});
|
||||
},
|
||||
formats = {
|
||||
'gif': attachToImage,
|
||||
'png': attachToImage,
|
||||
'swf': function () {
|
||||
return $("<embed/>").attr({
|
||||
src: this.src,
|
||||
type: 'application/x-shockwave-flash'
|
||||
});
|
||||
}
|
||||
},
|
||||
sections = {
|
||||
'{f}': 'format',
|
||||
'{e}': 'equation'
|
||||
},
|
||||
escapes = {
|
||||
'+': '2B',
|
||||
'=': '3D'
|
||||
};
|
||||
|
||||
$.fn.latex = function (opts) {
|
||||
opts = $.extend({},
|
||||
$.fn.latex.defaults, opts);
|
||||
opts.format = formats[opts.format] ? opts.format : 'gif';
|
||||
return this.each(function () {
|
||||
var $this = $(this),
|
||||
format, s, element, url = opts.url;
|
||||
opts.equation = $.trim($this.text());
|
||||
for (s in sections) {
|
||||
if (sections.hasOwnProperty(s) && (format = url.indexOf(s)) >= 0) {
|
||||
url = url.replace(s, opts[sections[s]]);
|
||||
}
|
||||
}
|
||||
for (s in escapes) {
|
||||
if (escapes.hasOwnProperty(s) && (format = url.indexOf(s)) >= 0) {
|
||||
url = url.replace(s, '%' + escapes[s]);
|
||||
}
|
||||
}
|
||||
opts.src = url;
|
||||
element = formats[opts.format].call(opts);
|
||||
$this.html('').append(element);
|
||||
if (opts.callback) {
|
||||
opts.callback.call(element);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$.fn.latex.defaults = {
|
||||
format: 'gif',
|
||||
url: 'https://latex.codecogs.com/{f}.latex?{e}'
|
||||
};
|
||||
}(jQuery));
|
||||
4
www/src/rrandom.js
Normal file
4
www/src/rrandom.js
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
export function rrandom(min, max) {
|
||||
// cria um número aleatório dentro do intervalo
|
||||
return Math.random() * (max - min) + min;
|
||||
}
|
||||
70
www/src/runge-kutta.js
Normal file
70
www/src/runge-kutta.js
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
export function RK4(f, t, Y0, params) {
|
||||
/*
|
||||
* f: trata-se da função f(t) ser integrada
|
||||
* t: trata-se do vetor de pontos no tempo
|
||||
* Y0: trata-se do vetor de condições iniciais
|
||||
* params: paremetros passados para o modelo de crescimento
|
||||
*/
|
||||
|
||||
let resolution = t.length; // resolução é o numero de pontos amostrados
|
||||
let h = t[1]; // h é o tamanho do passo de integração
|
||||
|
||||
let y = []; // inicializa o vetor que será devolvido como eixo y
|
||||
|
||||
y[0] = Y0; //inicializa o vetor com as condições iniciais
|
||||
|
||||
for (let i = 0; i < resolution; i++) {
|
||||
y[i + 1] = RK4step(f, t[i], y[i], h, params);
|
||||
}
|
||||
|
||||
return y;
|
||||
}
|
||||
|
||||
function RK4step(f, t, y0, h, params) {
|
||||
// Algorítimo Runge-kutta 4
|
||||
|
||||
let s = [];
|
||||
let y = [];
|
||||
let nVar = y0.length;
|
||||
|
||||
const k1 = f(t, y0, params);
|
||||
for (let i = 0; i < nVar; i++) {
|
||||
s[i] = y0[i] + (k1[i] * h) / 2;
|
||||
}
|
||||
const k2 = f(t + h / 2, s, params);
|
||||
|
||||
for (let i = 0; i < nVar; i++) {
|
||||
s[i] = y0[i] + (k2[i] * h) / 2;
|
||||
}
|
||||
const k3 = f(t + h / 2, s, params);
|
||||
|
||||
for (let i = 0; i < nVar; i++) {
|
||||
s[i] = y0[i] + k3[i] * h;
|
||||
}
|
||||
const k4 = f(t + h, s, params);
|
||||
|
||||
for (let i = 0; i < nVar; i++) {
|
||||
s[i] = y0[i] + k3[i] * h;
|
||||
y[i] = y0[i] + (k1[i] / 6 + k2[i] / 3 + k3[i] / 3 + k4[i] / 6) * h;
|
||||
}
|
||||
|
||||
return y;
|
||||
}
|
||||
|
||||
export function RK4getvalue(sol, timearray, time, f, params) {
|
||||
/* A partir de uma solução gerada pela função RK4,
|
||||
* esta função permite pegar o valor de y para um ponto arbitrário de tempo,
|
||||
* para tal, é pego o valor mais próximo do ponto desejado
|
||||
* e calculado com um passo de Runge-kutta para o valor exato de tempo desejado
|
||||
*/
|
||||
|
||||
let stepsize = timearray[1];
|
||||
// o tamanho do passo é igual o ponto 1 do vetor tempo (ponto 0 é igual a 0)
|
||||
let h = time % stepsize;
|
||||
// o passo dado é igual ao modulo entre o tempo arbitrário e o passo na resolução original
|
||||
let y0 = sol[Math.floor(time / stepsize)];
|
||||
// inicia-se no ponto da curva imediatamente anterior ao ponto desejado
|
||||
let y = RK4step(f, time, y0, h, params);
|
||||
// calcula-se o y com um único passo RK4 a partir do ponto anterior
|
||||
return y;
|
||||
}
|
||||
559
www/src/search.js
Normal file
559
www/src/search.js
Normal file
|
|
@ -0,0 +1,559 @@
|
|||
import { PSO } from "./PSO.js";
|
||||
import { RK4, RK4getvalue } from "./runge-kutta.js";
|
||||
import { Objective } from "./Objective.js";
|
||||
import {
|
||||
monod,
|
||||
moser,
|
||||
contois,
|
||||
bergter,
|
||||
tessier,
|
||||
andrews,
|
||||
aiba,
|
||||
pirt,
|
||||
} from "./conhecidos.js";
|
||||
import "https://cdn.plot.ly/plotly-2.29.1.min.js";
|
||||
|
||||
const parameterCatalog = {
|
||||
K_S: {
|
||||
latex: "K_S",
|
||||
unitText: "g/L",
|
||||
unitLatex: "\\mathrm{g\\,L^{-1}}",
|
||||
overrides: {
|
||||
contois: {
|
||||
unitText: "g_S/g_X",
|
||||
unitLatex: "\\frac{\\mathrm{g}_{S}}{\\mathrm{g}_{X}}",
|
||||
},
|
||||
},
|
||||
},
|
||||
mu_max: {
|
||||
latex: "\\mu_{max}",
|
||||
unitText: "h⁻¹",
|
||||
unitLatex: "\\mathrm{h^{-1}}",
|
||||
},
|
||||
K_I: {
|
||||
latex: "K_I",
|
||||
unitText: "g/L",
|
||||
unitLatex: "\\mathrm{g\\,L^{-1}}",
|
||||
overrides: {
|
||||
aiba: {
|
||||
unitText: "L/g",
|
||||
unitLatex: "\\mathrm{L\\,g^{-1}}",
|
||||
},
|
||||
},
|
||||
},
|
||||
m_S: {
|
||||
latex: "m_S",
|
||||
unitText: "g_S/(g_X·h)",
|
||||
unitLatex: "\\frac{\\mathrm{g}_{S}}{\\mathrm{g}_{X}\\,\\mathrm{h}}",
|
||||
},
|
||||
Y_XS: {
|
||||
latex: "Y_{XS}",
|
||||
unitText: "g_X/g_S",
|
||||
unitLatex: "\\frac{\\mathrm{g}_{X}}{\\mathrm{g}_{S}}",
|
||||
},
|
||||
T: {
|
||||
latex: "T",
|
||||
unitText: "h",
|
||||
unitLatex: "\\mathrm{h}",
|
||||
},
|
||||
n: {
|
||||
latex: "n",
|
||||
unitText: null,
|
||||
unitLatex: null,
|
||||
},
|
||||
};
|
||||
|
||||
const modelParameters = {
|
||||
aiba: [
|
||||
{ key: "K_S", bounds: [0.005, 2] },
|
||||
{ key: "mu_max", bounds: [0.05, 0.9] },
|
||||
{ key: "K_I", bounds: [0.01, 1] },
|
||||
{ key: "m_S", bounds: [0.0015, 0.05] },
|
||||
{ key: "Y_XS", bounds: [0.3, 0.7] },
|
||||
],
|
||||
andrews: [
|
||||
{ key: "K_S", bounds: [0.005, 2] },
|
||||
{ key: "mu_max", bounds: [0.05, 0.9] },
|
||||
{ key: "K_I", bounds: [5, 150] },
|
||||
{ key: "m_S", bounds: [0.0015, 0.05] },
|
||||
{ key: "Y_XS", bounds: [0.3, 0.7] },
|
||||
],
|
||||
bergter: [
|
||||
{ key: "K_S", bounds: [0.005, 2] },
|
||||
{ key: "mu_max", bounds: [0.05, 0.9] },
|
||||
{ key: "T", bounds: [5, 80] },
|
||||
{ key: "m_S", bounds: [0.0015, 0.05] },
|
||||
{ key: "Y_XS", bounds: [0.3, 0.7] },
|
||||
],
|
||||
contois: [
|
||||
{ key: "K_S", bounds: [0.005, 2] },
|
||||
{ key: "mu_max", bounds: [0.05, 0.9] },
|
||||
{ key: "m_S", bounds: [0.0015, 0.05] },
|
||||
{ key: "Y_XS", bounds: [0.3, 0.7] },
|
||||
],
|
||||
monod: [
|
||||
{ key: "K_S", bounds: [0.005, 2] },
|
||||
{ key: "mu_max", bounds: [0.05, 0.9] },
|
||||
{ key: "m_S", bounds: [0.0015, 0.05] },
|
||||
{ key: "Y_XS", bounds: [0.3, 0.7] },
|
||||
],
|
||||
moser: [
|
||||
{ key: "K_S", bounds: [0.005, 2] },
|
||||
{ key: "mu_max", bounds: [0.05, 0.9] },
|
||||
{ key: "n", bounds: [0.8, 2.5] },
|
||||
{ key: "m_S", bounds: [0.0015, 0.05] },
|
||||
{ key: "Y_XS", bounds: [0.3, 0.7] },
|
||||
],
|
||||
tessier: [
|
||||
{ key: "K_S", bounds: [0.005, 2] },
|
||||
{ key: "mu_max", bounds: [0.2, 0.9] },
|
||||
{ key: "m_S", bounds: [0.005, 0.05] },
|
||||
{ key: "Y_XS", bounds: [0.3, 0.7] },
|
||||
],
|
||||
};
|
||||
|
||||
function getParamDisplayInfo(paramKey, modelKey) {
|
||||
const baseInfo = parameterCatalog[paramKey];
|
||||
if (!baseInfo) {
|
||||
throw new Error(`Unknown parameter: ${paramKey}`);
|
||||
}
|
||||
const override = baseInfo.overrides?.[modelKey];
|
||||
if (!override) {
|
||||
return baseInfo;
|
||||
}
|
||||
return { ...baseInfo, ...override };
|
||||
}
|
||||
|
||||
function getDefaultBounds(modelKey) {
|
||||
return modelParameters[modelKey].map((param) => param.bounds.slice());
|
||||
}
|
||||
|
||||
function getParamDetails(modelKey) {
|
||||
return modelParameters[modelKey].map((param) => {
|
||||
const { latex, unitText, unitLatex } = getParamDisplayInfo(
|
||||
param.key,
|
||||
modelKey,
|
||||
);
|
||||
return {
|
||||
key: param.key,
|
||||
latex,
|
||||
unitText,
|
||||
unitLatex,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function monodPirt(_, y, params) {
|
||||
let K_S = params[0];
|
||||
let mu_max = params[1];
|
||||
//pirt:
|
||||
let m_S = params[2];
|
||||
let Y_XS = params[3];
|
||||
let X = y[0];
|
||||
let S = y[1];
|
||||
let dmu = monod(S, mu_max, K_S);
|
||||
let dX = X * dmu;
|
||||
const qS = pirt(dmu, Y_XS, m_S);
|
||||
let dS = -qS * X;
|
||||
return [dX, dS];
|
||||
}
|
||||
|
||||
function moserPirt(_, y, params) {
|
||||
let K_S = params[0];
|
||||
let mu_max = params[1];
|
||||
let n = params[2];
|
||||
//pirt:
|
||||
let m_S = params[3];
|
||||
let Y_XS = params[4];
|
||||
let X = y[0];
|
||||
let S = y[1];
|
||||
let dmu = moser(S, mu_max, K_S, n);
|
||||
let dX = X * dmu;
|
||||
const qS = pirt(dmu, Y_XS, m_S);
|
||||
let dS = -qS * X;
|
||||
return [dX, dS];
|
||||
}
|
||||
|
||||
function contoisPirt(_, y, params) {
|
||||
let K_S = params[0];
|
||||
let mu_max = params[1];
|
||||
let m_S = params[2];
|
||||
let Y_XS = params[3];
|
||||
let X = y[0];
|
||||
let S = y[1];
|
||||
let dmu = contois(S, X, mu_max, K_S);
|
||||
let dX = X * dmu;
|
||||
const qS = pirt(dmu, Y_XS, m_S);
|
||||
let dS = -qS * X;
|
||||
return [dX, dS];
|
||||
}
|
||||
|
||||
function bergterPirt(time, y, params) {
|
||||
let K_S = params[0];
|
||||
let mu_max = params[1];
|
||||
let T = params[2];
|
||||
// pirt:
|
||||
let m_S = params[3];
|
||||
let Y_XS = params[4];
|
||||
let X = y[0];
|
||||
let S = y[1];
|
||||
let dmu = bergter(S, time, mu_max, K_S, T);
|
||||
let dX = X * dmu;
|
||||
const qS = pirt(dmu, Y_XS, m_S);
|
||||
let dS = -qS * X;
|
||||
return [dX, dS];
|
||||
}
|
||||
|
||||
function tessierPirt(_, y, params) {
|
||||
let K_S = params[0];
|
||||
let mu_max = params[1];
|
||||
// pirt:
|
||||
let m_S = params[2];
|
||||
let Y_XS = params[3];
|
||||
let X = y[0];
|
||||
let S = y[1];
|
||||
let dmu = tessier(S, mu_max, K_S);
|
||||
let dX = X * dmu;
|
||||
const qS = pirt(dmu, Y_XS, m_S);
|
||||
let dS = -qS * X;
|
||||
return [dX, dS];
|
||||
}
|
||||
|
||||
function andrewsPirt(_, y, params) {
|
||||
let K_S = params[0];
|
||||
let mu_max = params[1];
|
||||
let K_I = params[2];
|
||||
// pirt:
|
||||
let m_S = params[3];
|
||||
let Y_XS = params[4];
|
||||
let X = y[0];
|
||||
let S = y[1];
|
||||
let dmu = andrews(S, mu_max, K_S, K_I);
|
||||
let dX = X * dmu;
|
||||
const qS = pirt(dmu, Y_XS, m_S);
|
||||
let dS = -qS * X;
|
||||
return [dX, dS];
|
||||
}
|
||||
|
||||
function aibaPirt(_, y, params) {
|
||||
let K_S = params[0];
|
||||
let mu_max = params[1];
|
||||
let K_I = params[2];
|
||||
// pirt:
|
||||
let m_S = params[3];
|
||||
let Y_XS = params[4];
|
||||
let X = y[0];
|
||||
let S = y[1];
|
||||
let dmu = aiba(S, mu_max, K_S, K_I);
|
||||
let dX = X * dmu;
|
||||
const qS = pirt(dmu, Y_XS, m_S);
|
||||
let dS = -qS * X;
|
||||
return [dX, dS];
|
||||
}
|
||||
|
||||
export async function main(input, options = {}) {
|
||||
const alg = options.alg || {
|
||||
particles: 50,
|
||||
c1: 1.49618,
|
||||
c2: 1.49618,
|
||||
w: 0.7298,
|
||||
iterations: 150,
|
||||
};
|
||||
const boundsOpt = options.bounds || {};
|
||||
const onProgress = options.onProgress || (() => {});
|
||||
|
||||
const time = [];
|
||||
const subs = [];
|
||||
const cels = [];
|
||||
for (let i = 1; i < input.length; i++) {
|
||||
time[i] = input[i][0];
|
||||
subs[i] = input[i][1];
|
||||
cels[i] = input[i][2];
|
||||
}
|
||||
|
||||
const basePlot = [
|
||||
{
|
||||
x: time,
|
||||
y: subs,
|
||||
name: "Experimental substrate",
|
||||
mode: "markers",
|
||||
marker: { size: 10, color: "#4a90e2" },
|
||||
type: "scatter",
|
||||
},
|
||||
{
|
||||
x: time,
|
||||
y: cels,
|
||||
name: "Experimental cells",
|
||||
mode: "markers",
|
||||
marker: { size: 10, color: "#50e3c2" },
|
||||
type: "scatter",
|
||||
},
|
||||
];
|
||||
|
||||
function calculateAIC(obj, sol, params) {
|
||||
let sse = 0;
|
||||
let count = 0;
|
||||
|
||||
for (let i = 1; i < input.length; i++) {
|
||||
const timePoint = input[i][0];
|
||||
const prediction = RK4getvalue(
|
||||
sol,
|
||||
obj.timeArray,
|
||||
timePoint,
|
||||
obj.f,
|
||||
params,
|
||||
);
|
||||
|
||||
const cellResidual = prediction[0] - input[i][2];
|
||||
const substrateResidual = prediction[1] - input[i][1];
|
||||
|
||||
sse += cellResidual ** 2 + substrateResidual ** 2;
|
||||
count += 2;
|
||||
}
|
||||
|
||||
const n = count;
|
||||
const k = params.length;
|
||||
const meanSquaredError = n > 0 ? sse / n : 0;
|
||||
const safeMSE = Math.max(meanSquaredError, Number.EPSILON);
|
||||
|
||||
return {
|
||||
aic: 2 * k + n * Math.log(safeMSE),
|
||||
mse: meanSquaredError,
|
||||
};
|
||||
}
|
||||
|
||||
function runModel(cfg) {
|
||||
const obj = new Objective(input, cfg.ode, 500, 2);
|
||||
const optim = new PSO(obj, alg.particles, cfg.bounds);
|
||||
optim.run(alg.c1, alg.c2, alg.w, alg.iterations);
|
||||
|
||||
const sol = RK4(obj.f, obj.timeArray, obj.y0, optim.pos_best_g);
|
||||
const celsM = sol.map((row) => row[0]);
|
||||
const subsM = sol.map((row) => row[1]);
|
||||
|
||||
const { aic, mse } = calculateAIC(obj, sol, optim.pos_best_g);
|
||||
|
||||
let objectiveText = "MSE: N/A";
|
||||
if (Number.isFinite(mse)) {
|
||||
const absValue = Math.abs(mse);
|
||||
const formatted =
|
||||
absValue !== 0 && (absValue >= 1e3 || absValue < 1e-2)
|
||||
? mse.toExponential(4)
|
||||
: mse.toFixed(8);
|
||||
objectiveText = `MSE: ${formatted}`;
|
||||
}
|
||||
|
||||
Plotly.newPlot(
|
||||
document.getElementById(cfg.plotId),
|
||||
[
|
||||
...basePlot,
|
||||
{
|
||||
x: obj.timeArray,
|
||||
y: subsM,
|
||||
name: "Model fit for the substrate",
|
||||
mode: "lines",
|
||||
line: { color: "#4a90e2" },
|
||||
},
|
||||
{
|
||||
x: obj.timeArray,
|
||||
y: celsM,
|
||||
name: "Model fit for the cells",
|
||||
mode: "lines",
|
||||
line: { color: "#50e3c2" },
|
||||
},
|
||||
],
|
||||
{
|
||||
margin: { t: 10, b: 30 },
|
||||
paper_bgcolor: "#f0f4f8",
|
||||
plot_bgcolor: "#f0f4f8",
|
||||
xaxis: {
|
||||
title: { text: "Time (h)" },
|
||||
},
|
||||
yaxis: {
|
||||
title: { text: "Concentration (g/L)" },
|
||||
},
|
||||
legend: {
|
||||
orientation: "h",
|
||||
yanchor: "top",
|
||||
y: -0.2,
|
||||
xanchor: "left",
|
||||
x: 0,
|
||||
font: { size: 10 },
|
||||
},
|
||||
annotations: [
|
||||
{
|
||||
text: objectiveText,
|
||||
x: 1,
|
||||
y: 1,
|
||||
xref: "paper",
|
||||
yref: "paper",
|
||||
xanchor: "right",
|
||||
yanchor: "top",
|
||||
showarrow: false,
|
||||
font: { size: 12, color: "#1f2933" },
|
||||
bordercolor: "#d9e2ef",
|
||||
borderwidth: 1,
|
||||
borderpad: 6,
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
|
||||
const params = optim.pos_best_g.map((p) => p.toFixed(3));
|
||||
const paramContainer = document.getElementById(cfg.paramDiv);
|
||||
paramContainer.classList.add("model-params");
|
||||
paramContainer.innerHTML = "";
|
||||
|
||||
const label = document.createElement("div");
|
||||
label.className = "param-label";
|
||||
label.textContent = "Parameters:";
|
||||
paramContainer.appendChild(label);
|
||||
|
||||
const list = document.createElement("ul");
|
||||
list.className = "param-list";
|
||||
|
||||
params.forEach((value, i) => {
|
||||
const detail = cfg.paramDetails[i];
|
||||
const item = document.createElement("li");
|
||||
const mathSpan = document.createElement("span");
|
||||
const latexUnit = detail.unitLatex;
|
||||
const latexExpression = latexUnit
|
||||
? `${detail.latex} = ${value}\\,${latexUnit}`
|
||||
: `${detail.latex} = ${value}`;
|
||||
|
||||
if (typeof katex !== "undefined") {
|
||||
katex.render(latexExpression, mathSpan, { throwOnError: false });
|
||||
} else {
|
||||
mathSpan.textContent = latexUnit
|
||||
? `${detail.latex} = ${value} ${detail.unitText || ""}`
|
||||
: `${detail.latex} = ${value}`;
|
||||
}
|
||||
|
||||
item.appendChild(mathSpan);
|
||||
list.appendChild(item);
|
||||
});
|
||||
|
||||
paramContainer.appendChild(list);
|
||||
|
||||
return { title: cfg.title, aic, key: cfg.key };
|
||||
}
|
||||
|
||||
const models = [
|
||||
{
|
||||
key: "aiba",
|
||||
ode: aibaPirt,
|
||||
bounds: boundsOpt.aiba || getDefaultBounds("aiba"),
|
||||
plotId: "Plot1",
|
||||
paramDiv: "AibaParam",
|
||||
title: "Pirt-Aiba",
|
||||
paramDetails: getParamDetails("aiba"),
|
||||
container: document.getElementById("Plot1")?.closest(".box"),
|
||||
},
|
||||
{
|
||||
key: "andrews",
|
||||
ode: andrewsPirt,
|
||||
bounds: boundsOpt.andrews || getDefaultBounds("andrews"),
|
||||
plotId: "Plot2",
|
||||
paramDiv: "AndrewsParam",
|
||||
title: "Pirt-Andrews",
|
||||
paramDetails: getParamDetails("andrews"),
|
||||
container: document.getElementById("Plot2")?.closest(".box"),
|
||||
},
|
||||
{
|
||||
key: "bergter",
|
||||
ode: bergterPirt,
|
||||
bounds: boundsOpt.bergter || getDefaultBounds("bergter"),
|
||||
plotId: "Plot3",
|
||||
paramDiv: "BergterParam",
|
||||
title: "Pirt-Bergter",
|
||||
paramDetails: getParamDetails("bergter"),
|
||||
container: document.getElementById("Plot3")?.closest(".box"),
|
||||
},
|
||||
{
|
||||
key: "contois",
|
||||
ode: contoisPirt,
|
||||
bounds: boundsOpt.contois || getDefaultBounds("contois"),
|
||||
plotId: "Plot4",
|
||||
paramDiv: "ContoisParam",
|
||||
title: "Pirt-Contois",
|
||||
paramDetails: getParamDetails("contois"),
|
||||
container: document.getElementById("Plot4")?.closest(".box"),
|
||||
},
|
||||
{
|
||||
key: "monod",
|
||||
ode: monodPirt,
|
||||
bounds: boundsOpt.monod || getDefaultBounds("monod"),
|
||||
plotId: "Plot5",
|
||||
paramDiv: "MonodParam",
|
||||
title: "Pirt-Monod",
|
||||
paramDetails: getParamDetails("monod"),
|
||||
container: document.getElementById("Plot5")?.closest(".box"),
|
||||
},
|
||||
{
|
||||
key: "moser",
|
||||
ode: moserPirt,
|
||||
bounds: boundsOpt.moser || getDefaultBounds("moser"),
|
||||
plotId: "Plot6",
|
||||
paramDiv: "MoserParam",
|
||||
title: "Pirt-Moser",
|
||||
paramDetails: getParamDetails("moser"),
|
||||
container: document.getElementById("Plot6")?.closest(".box"),
|
||||
},
|
||||
{
|
||||
key: "tessier",
|
||||
ode: tessierPirt,
|
||||
bounds: boundsOpt.tessier || getDefaultBounds("tessier"),
|
||||
plotId: "Plot7",
|
||||
paramDiv: "TessierParam",
|
||||
title: "Pirt-Tessier",
|
||||
paramDetails: getParamDetails("tessier"),
|
||||
container: document.getElementById("Plot7")?.closest(".box"),
|
||||
},
|
||||
];
|
||||
|
||||
const modelByKey = new Map(models.map((model) => [model.key, model]));
|
||||
|
||||
const results = [];
|
||||
for (let idx = 0; idx < models.length; idx++) {
|
||||
const cfg = models[idx];
|
||||
const res = runModel(cfg);
|
||||
results.push(res);
|
||||
onProgress(idx + 1, models.length);
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
}
|
||||
|
||||
results.sort((a, b) => a.aic - b.aic);
|
||||
const comparisonBox = document.getElementById("comparison");
|
||||
const chartParent = models[0]?.container?.parentElement;
|
||||
|
||||
if (comparisonBox && chartParent) {
|
||||
results.forEach((result) => {
|
||||
const modelCfg = modelByKey.get(result.key);
|
||||
if (!modelCfg?.container) {
|
||||
return;
|
||||
}
|
||||
chartParent.insertBefore(modelCfg.container, comparisonBox);
|
||||
});
|
||||
}
|
||||
|
||||
const compDiv = document.getElementById("comparison");
|
||||
compDiv.innerHTML =
|
||||
`<h2>Model comparison</h2>
|
||||
<table class="abnt-table table-numbered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Model</th>
|
||||
<th>Akaike Information Criterion</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>` +
|
||||
results
|
||||
.map((r, index) => {
|
||||
return `<tr><td>${index + 1}</td><td>${r.title}</td><td>${r.aic.toFixed(
|
||||
2,
|
||||
)}</td></tr>`;
|
||||
})
|
||||
.join("") +
|
||||
"</tbody></table>";
|
||||
}
|
||||
45
www/src/solve.js
Normal file
45
www/src/solve.js
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
function solve(tf, Ks, mu_max, m_S, Y_XS, X0, S0) {
|
||||
let res = 5000;
|
||||
let timeArray = [];
|
||||
for (let i = 0; i <= res; i++) {
|
||||
timeArray[i] = (i * tf) / res;
|
||||
}
|
||||
|
||||
let sol = RK4(model, timeArray, [X0, S0], [Ks, mu_max, m_S, Y_XS]);
|
||||
let cels = [];
|
||||
let subs = [];
|
||||
for (let i = 0; i < sol.length; i++) {
|
||||
cels[i] = sol[i][0];
|
||||
subs[i] = sol[i][1];
|
||||
}
|
||||
|
||||
TESTER = document.getElementById("tester");
|
||||
Plotly.newPlot(
|
||||
TESTER,
|
||||
[
|
||||
{
|
||||
x: timeArray,
|
||||
y: subs,
|
||||
name: "Calculated substrate",
|
||||
line: { color: "#4a90e2" },
|
||||
},
|
||||
{
|
||||
x: timeArray,
|
||||
y: cels,
|
||||
name: "Calculated cells",
|
||||
line: { color: "#50e3c2" },
|
||||
},
|
||||
],
|
||||
{
|
||||
margin: { t: 10, b: 30 },
|
||||
paper_bgcolor: "#f0f4f8",
|
||||
plot_bgcolor: "#f0f4f8",
|
||||
legend: {
|
||||
orientation: "h",
|
||||
yanchor: "top",
|
||||
y: -0.2,
|
||||
font: { size: 10 },
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue