Difference between revisions of "Team:TUDelft/js/plotfit"

 
(3 intermediate revisions by the same user not shown)
Line 2: Line 2:
 
     $("main.container").addClass("progress");
 
     $("main.container").addClass("progress");
 
     progress(params, plot);
 
     progress(params, plot);
    //Globals:
+
 
    var DATA;
+
  
 
     // ################
 
     // ################
Line 20: Line 19:
  
 
         // Getting all the data from the form.
 
         // Getting all the data from the form.
         var data = params.data.value;
+
         var data = params.data.value.trim();
 
         var thousands_seperator = params.thousands_seperator.value;
 
         var thousands_seperator = params.thousands_seperator.value;
 
         var thousands_checked = params.thousands_usage.checked;
 
         var thousands_checked = params.thousands_usage.checked;
Line 26: Line 25:
 
         var decimal_seperator = params.decimal_seperator.value;
 
         var decimal_seperator = params.decimal_seperator.value;
  
 +
        if (value_seperator === 't') value_seperator = '\t';
 
         Message("Validating format...");
 
         Message("Validating format...");
  
Line 36: Line 36:
 
                 Warning("Your thousands seperator is the same as the value seperator.");
 
                 Warning("Your thousands seperator is the same as the value seperator.");
 
             }
 
             }
             data = data.replace(thousands_seperator, '');
+
             data = replaceAll(data, thousands_seperator, '');
 
         }
 
         }
 
         if (value_seperator == decimal_seperator) {
 
         if (value_seperator == decimal_seperator) {
Line 43: Line 43:
 
         // turn value seperator into ',' and decimal seperator into '.', and preventing those to be mixed up.
 
         // turn value seperator into ',' and decimal seperator into '.', and preventing those to be mixed up.
 
         if (data.indexOf('¬') === -1) {
 
         if (data.indexOf('¬') === -1) {
             data = data.replace(value_seperator, '¬').replace(decimal_seperator, '.').replace('¬', ',');
+
             dataWithStrange = replaceAll(data, value_seperator, '¬');
 +
            dataDecimalToPoint = replaceAll(dataWithStrange, decimal_seperator, '.');
 +
            data = replaceAll(dataDecimalToPoint, '¬', ',');
 
         } else {
 
         } else {
 
             return Error("¬ cannot be used as delimiter.");
 
             return Error("¬ cannot be used as delimiter.");
 
         }
 
         }
  
         data = data.replace("\"", "");
+
         data = replaceAll(data, "\"", "");
  
 
         Message("Parsing...");
 
         Message("Parsing...");
Line 54: Line 56:
  
 
         Message("Validating data...")
 
         Message("Validating data...")
 +
        aa[0] = aa[0].filter(filterExceptZero);
 +
        aa[1] = aa[1].filter(filterExceptZero);
 
         if (aa.length > 2) {
 
         if (aa.length > 2) {
 
             aa = numeric.transpose(aa);
 
             aa = numeric.transpose(aa);
Line 59: Line 63:
  
 
         if (aa.length !== 2) {
 
         if (aa.length !== 2) {
             return Error("Enter data with 2 collumns or rows.");
+
             return Error("Enter data with 2 columns or rows.");
 
         }
 
         }
  
 
         if (aa[0].length !== aa[1].length) {
 
         if (aa[0].length !== aa[1].length) {
             Warning("Your two collumns or rows are of different length. The longest one is trancutated to match the other.");
+
             Warning("Your two columns or rows are of different length. The longest one is truncated to match the other.");
             var min = Math.min(a[0].length !== a[1].length);
+
             var min = Math.min(aa[0].length, aa[1].length);
 
             if (aa[0].length == min) {
 
             if (aa[0].length == min) {
 
                 aa[1] = aa[1].slice(0, min);
 
                 aa[1] = aa[1].slice(0, min);
Line 71: Line 75:
 
             }
 
             }
 
         }
 
         }
 
+
        aa = [aa[0], aa[1]];
 
         aa = numeric.transpose(aa);
 
         aa = numeric.transpose(aa);
         DATA = aa;
+
         DATA = numeric.transpose(aa);
  
 
         Message("Plotting...");
 
         Message("Plotting...");
Line 94: Line 98:
 
         var t = data[0];
 
         var t = data[0];
 
         var N = data[1];
 
         var N = data[1];
         var initial = [N[0], N[N.length - 1], 1];
+
         var r = find_r(t, N);
 
         var objective = makeObjective(logistic, t, N);
 
         var objective = makeObjective(logistic, t, N);
         var minimiser = numeric.uncmin(objective, initial);
+
         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.");
 
         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);
 
         result(minimiser.message);
 
         draw(minimiser.solution);
 
         draw(minimiser.solution);
 
         var t = numeric.linspace(t[0], t[t.length - 1], t.length);
 
         var t = numeric.linspace(t[0], t[t.length - 1], t.length);
         var N = vectorize(
+
         var N = vectorize(setParamInFunc(minimiser.solution, logistic), t);
            setParamInFunc(minimiser.solution, logistic), t
+
        );
+
 
         //        var N = (func, x) - > {
 
         //        var N = (func, x) - > {
 
         //            var res = Array(x.length);
 
         //            var res = Array(x.length);
Line 118: Line 143:
 
         data = numeric.transpose(data);
 
         data = numeric.transpose(data);
 
         plotfit(datafit, data, callback);
 
         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;
 
     }
 
     }
  
Line 150: Line 226:
 
     function vectorize(func, x) {
 
     function vectorize(func, x) {
 
         var res = new Array(x.length);
 
         var res = new Array(x.length);
        testtest = func;
 
 
         for (var i = 0; i < x.length; i++) {
 
         for (var i = 0; i < x.length; i++) {
 
             res[i] = func(x[i]);
 
             res[i] = func(x[i]);
Line 168: Line 243:
 
     // N = logistic(params,t);
 
     // N = logistic(params,t);
 
     function logistic(params, t) {
 
     function logistic(params, t) {
         var N0, K, r;
+
         var N0, K, r, ans;
    [N0, K, r] = params;
+
        [N0, K, r] = params;
         return (K * N0 * Math.exp(r * t)) / (K + N0 * (Math.exp(r * t) - 1));
+
         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;
 
     }
 
     }
  
Line 194: Line 275:
 
         }
 
         }
 
         var latex = katex.renderToString("N(t) = \\frac{K \\cdot N_0 \\cdot e^{rt}}{K + N_0 \\cdot (e^{rt}-1)}");
 
         var latex = katex.renderToString("N(t) = \\frac{K \\cdot N_0 \\cdot e^{rt}}{K + N_0 \\cdot (e^{rt}-1)}");
        console.log(latex);
 
 
         result("For the function:")
 
         result("For the function:")
 
         resultHTML(latex);
 
         resultHTML(latex);
Line 236: Line 316:
 
             text: e
 
             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);
 
     }
 
     }
 
}
 
}

Latest revision as of 01:43, 2 November 2017

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);
   }

}