JS Genetic Curve Fitter

While I was working on Coronamap.it, in the new section dedicated to analysis, I felt the need to do some curve fitting, that is I had a series of points and I wanted to find a curve which best fit the points, given some constraints. Of course I expected this to be a well known and well solved problem, only to discover that of course yes it is, but it’s actually something not necessarily easy to do. It’s a complex problem which requires to search in a large space of solutions, and as that not an easy task to be done in JS (which I required). Fortunately, I found an implementation in JS which used a genetic algorithm approach, so I decided to rewrite another one from scratch using my old project JSGenetic. If it does not make any sense to you, it’s because it does not have any – except that I love to make this kind of stuff.

So enough for the story, let’s talk about the library:

Example here

Usage:

var cf = GeneticCurveFitter(points, functionGenerator, 3, { //3 is the number parameters you need to tune to find the fitting curve
    RANGES: [
        [1, 2000], //one range for every parameter - if not specified, they fall back to [-1000,1000] which is kind of silly
        [1580000000000, 1590000000000],
        [10000000, 1000000000]
    ]
});
functionGenerator

is a function which gets the generated parameters in input and returns the function which you want to optimize. Easier to explain with an example:

//polynomial functionGenerator
function polyGen(coeffs) {
  return function(x) {
    var result = 0;
    for(var i=0; i<coeffs.length; i++) {
      result += coeffs[i]*Math.pow(x,i);
    }
    return result;
  }
}

//gaussian from the example
var gaussGen = function (coeff) {
    return function (x) {
        return coeff[0] * Math.exp(-((x - coeff[1]) * (x - coeff[1])) / (2 * coeff[2] * coeff[2]));
    }
}

After initializing the object, it can be easily run like this:

var resultFn =   cf.fit();

which returns the best fit function it could find. The process takes some seconds and the time can vary by CPU power and number of coefficients to find. The returned function also has an helper method to generate a chart from it (read: to sample it) which goes like this:

var samples = resultFn.toDataset(startX,endX,numberOfSamples);
//samples is in the form [{x:,y:}, ...]

There are also other methods to better control the evolution of the coefficients: here are the library’s returned methods:

{
        step: step, //make a single step of the genetic algoritm, or N steps if you call cf.step(N)
        fit: fit, //already described before
        getCurrentSolution: getCurrentSolution, //gets current solution
        getCurrentFitness: getCurrentFitness,//gets current error
        getCurrentCoefficients: getCurrentCoefficients //get raw coefficients as an array
}

That’s all! Enjoy.