function plot(params) {
$("main.container").addClass("progress"); progress(params, plot);
// ################ // The rest defines the functions: // ################
// Gets data from form and validates. function progress(params, plot) { // Print an error
// Empty alert box and results box
$('#alert').html(""); $('#results').html(""); Message("Processing...");
// Getting all the data from the form. var data = params.data.value.trim(); var thousands_seperator = params.thousands_seperator.value; var thousands_checked = params.thousands_usage.checked; var value_seperator = params.value_seperator.value; var decimal_seperator = params.decimal_seperator.value;
if (value_seperator === 't') value_seperator = '\t'; Message("Validating format...");
// validation and manipulation if (thousands_checked) { if (thousands_seperator == value_seperator) { Warning("Your thousands seperator is the same as the value seperator."); } if (thousands_seperator == decimal_seperator) { Warning("Your thousands seperator is the same as the value seperator."); } data = replaceAll(data, thousands_seperator, ); } if (value_seperator == decimal_seperator) { return Error("Your value seperator is the same as the decimal seperator."); } // turn value seperator into ',' and decimal seperator into '.', and preventing those to be mixed up. if (data.indexOf('¬') === -1) { dataWithStrange = replaceAll(data, value_seperator, '¬'); dataDecimalToPoint = replaceAll(dataWithStrange, decimal_seperator, '.'); data = replaceAll(dataDecimalToPoint, '¬', ','); } else { return Error("¬ cannot be used as delimiter."); }
data = replaceAll(data, "\"", "");
Message("Parsing..."); aa = numeric.parseCSV(data);
Message("Validating data...") aa[0] = aa[0].filter(filterExceptZero); aa[1] = aa[1].filter(filterExceptZero); if (aa.length > 2) { aa = numeric.transpose(aa); }
if (aa.length !== 2) { return Error("Enter data with 2 columns or rows."); }
if (aa[0].length !== aa[1].length) { Warning("Your two columns or rows are of different length. The longest one is truncated to match the other."); var min = Math.min(aa[0].length, aa[1].length); if (aa[0].length == min) { aa[1] = aa[1].slice(0, min); } else { aa[0] = aa[0].slice(0, min); } } aa = [aa[0], aa[1]]; aa = numeric.transpose(aa); DATA = numeric.transpose(aa);
Message("Plotting..."); plot(aa, fit);
}
// Plots the data function plot(data, fit) { result("Your data has size " + data.length + "."); $.plot($("#placeholder"), [{ color: 'yellow', data: data }]); fit(data, plotfit); }
function fit(data, plotfit) { data = numeric.transpose(data); var t = data[0]; var N = data[1]; var r = find_r(t, N); var objective = makeObjective(logistic, t, N); var possible_K = [0.5, 1, 1.5]; var possible_r = [0.9, 1, 1.1]; var pos_min = new Array(6); var pos_r = new Array(6); for (var i = 0; i < 3; i++) { pre_K = possible_K[i]; for (var k = 0; k < 3; k++) { pre_r = possible_r[k]; var initial = [N[0], pre_K * N[N.length - 1], pre_r * r]; try { var minimiser = numeric.uncmin(objective, initial); pos_min[i+k] = minimiser; pos_r[i+k] = r_sq(logistic, minimiser.solution, N, t); } catch (err) { pos_r[i+k] = 0; Warning(err); } } } console.log(pos_r); var indexOfMaxValue = pos_r.reduce((iMax, x, i, arr) => x > arr[iMax] ? i : iMax, 0); minimiser = pos_min[indexOfMaxValue]; result("Solution found after " + minimiser.iterations + " iterations."); let r2 = r_sq(logistic, minimiser.solution, N, t); result("With an R squared of " + r2 + "."); result(minimiser.message); draw(minimiser.solution); var t = numeric.linspace(t[0], t[t.length - 1], t.length); var N = vectorize(setParamInFunc(minimiser.solution, logistic), t); // var N = (func, x) - > { // var res = Array(x.length); // for (var i = 0; i < x.length; i++) { // res[i] = func(x[i]); // } // return res; // }((func, params) - > { // return (x) - > // return func(params, x); // }, t); var datafit = [t, N]; datafit = numeric.transpose(datafit); data = numeric.transpose(data); plotfit(datafit, data, callback); } FINDR = find_r; function find_r(t, N) { var K = Math.max(...N); var N_N = vectorize(((N) => N/K), N); var lijntje = vectorize(((N_N) =>Math.log(N_N/(1-N_N))), N_N); lijntje = removeAfterInfinity(lijntje); t = t.slice(0, lijntje.length); var initial = [0.005, lijntje[0]]; var objective = makeObjective(line, t, lijntje); try { var minimiser = numeric.uncmin(objective, initial); } catch (err) { Warning("cannot find r: " + err); return 2; } return minimiser.solution[0]; } function r_sq(f, popt, ydata, xdata) { let residuals = new Array(ydata.length); for(var i = 0; i<ydata.length; i++) { residuals[i] = ydata[i] - f(popt, xdata[i]); } var ss_res = 0; for (var i = 0; i<residuals.length; i++) { ss_res += Math.pow(residuals[i], 2); } let mean = ydata.reduce((a,b)=>a+b, 0) / ydata.length; var ss_tot = 0; for (var i = 0; i<ydata.length; i++) { ss_tot += Math.pow(ydata[i] - mean, 2); } return 1 - (ss_res / ss_tot); } function removeAfterInfinity(arr) { var res = []; for (i=0; i<arr.length; i++) { if (arr[i] !== Infinity) { res.push(arr[i]); } else { return res; } } } // lijntje = line(params, t) function line(params, t) { var a, b; [a, b] = params return a*t + b; }
function plotfit(datafit, data, callback) { $.plot($("#placeholder"), [{ label: "fitted data", data: datafit, color: 'green' }, { label: "input data", data: data, color: 'yellow' }], { legend: { position: "nw" } }); callback(); }
// Tells user we are done. function callback() { $("main.container").removeClass("progress"); }
// ################ // The following functions are out of the callback work loop // ################
// Decorator for functions to perform vectorized function vectorize(func, x) { var res = new Array(x.length); for (var i = 0; i < x.length; i++) { res[i] = func(x[i]); } return res; }
// Decorator for functions to set params fixed function setParamInFunc(params, func) { function resFunc(x) { return func(params, x); } return resFunc; }
// Function to fit // N = logistic(params,t); function logistic(params, t) { var N0, K, r, ans; [N0, K, r] = params; var below = (K + N0 * (Math.exp(r * t) - 1)); if (below === Infinity) { ans = 0; } else { ans = (K * N0 * Math.exp(r * t)) / (K + N0 * (Math.exp(r * t) - 1)); } return ans; }
// Least square objective // Build as factory with a decorator function makeObjective(targetFunc, x, y) { function objective(params) { var total = 0.0 for (var i = 0; i < y.length; i++) { var result = targetFunc(params, x[i]) var delta = result - y[i]; total += delta * delta; } return total; } return objective; }
function draw(params) { param_names = ["N0", "K", "r"]; for (var i = 0; i < param_names.length; i++) { result(param_names[i] + " is " + params[i]); } var latex = katex.renderToString("N(t) = \\frac{K \\cdot N_0 \\cdot e^{rt}}{K + N_0 \\cdot (e^{rt}-1)}"); result("For the function:") resultHTML(latex); }
function result(e) { $("#results").append($("<p />", { class: "result", text: e })); }
function resultHTML(e) { $("#results").append($("<p />", { class: "result", html: e })); }
// Print an error (This function is usually returned.) function Error(e) { $("#alert").prepend($('<p />', { class: 'error', text: e })); $("main.container").removeClass("progress"); }
// Print a warming function Warning(e) { $("#alert").append($('<p />', { class: 'warning', text: e })); }
// Print a message function Message(e) { $("#alert").append($('<p />', { class: 'message', text: e })); } // This functions takes a string and adds \ before any character that needs to be escaped in a regex. // This is necesarry when putting user input into a regex. function escapeRegExp(string) { return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string } // This functions replaces all matched characters and also does this safe for user input function replaceAll(str, find, replace) { return str.replace(new RegExp(escapeRegExp(find), 'g'), replace); } // This function returns false when NaN, false, and undefined, but returns true for 0 function filterExceptZero(el) { if (el === 0) return true; else return Boolean(el); }
}