-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.js
97 lines (85 loc) · 3.1 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
'use latest';
import querystring from 'querystring';
import { NelderMead } from './src/nelder_mead';
import template from './demo';
// get the post data and parse the form
function getBody(req) {
return new Promise( (resolve) => {
let body = '';
req.on('data', (chunk) => {
body += chunk;
});
req.on('end', () => {
body = querystring.parse(body);
if(body.observedData) {
body.observedData = body.observedData.split('\n').map( (stringPoint) =>
stringPoint.split(',').map( (v) => Number(v) )
);
}
if(body.initialGuess) {
body.initialGuess = body.initialGuess.split(',').map( (v) => Number(v) );
}
resolve(body);
});
});
}
// if no target function was passed in, this defines the default
function defaultTargetFunction() {
return `function (x, a, b, c) {
return a * Math.pow(x, 2) + b * x + c;
}`;
}
// if no initial guess was passed in, this defines the default
function defaultInitialGuess() {
return [2, 3, 4];
}
//if no observed data was passed in, generate some default data
function defaultObservedData(y, initialGuess) {
return [...Array(50).keys()].map( (x) => {
x = x - 25;
let trueVal = y(x, ...initialGuess);
let noise = (Math.random() - 0.5);
return [x, trueVal + trueVal * noise];
});
}
// run the nelder mead optimization to fit the curve
function runOptimization(y, observedData, initialGuess) {
let minimization = (params) => {
return observedData.reduce( (errorSum, point) => {
// least squares fit (sum the squared residuals)
return errorSum + Math.pow(y(point[0], ...params) - point[1], 2);
}, 0);
};
let nm = new NelderMead(initialGuess, minimization);
nm.iterateNTimes(1000);
return nm.getNthPoint(0);
}
function getRSquared(observedData, y, optimizedParams) {
let mean = observedData.reduce( (sum, value) => sum + value[1], 0 ) / observedData.length;
let SStot = observedData.reduce( (sum, value) => sum + Math.pow(value[1] - mean, 2), 0 );
let SSres = observedData.reduce( (sum, value) => sum + Math.pow(value[1] - y(value[0], ...optimizedParams), 2), 0 );
return 1 - (SSres / SStot);
}
module.exports = function(context, req, res) {
getBody(req).then( (body) => {
let targetFunctionString = body.targetFunction || defaultTargetFunction();
let initialGuess = body.initialGuess || defaultInitialGuess();
let y = eval('(' + targetFunctionString + ')');
let fit = [];
let error = '';
let optimizedParams = [];
let observedData = [];
let rSquared = 0;
if(y.length !== initialGuess.length + 1) {
res.writeHead(400);
error = 'There must be as many initial guess parameters as there are unknowns';
} else {
observedData = (body.editManually && body.observedData) ? body.observedData : defaultObservedData(y, initialGuess);
optimizedParams = runOptimization(y, observedData, initialGuess);
fit = observedData.map( (point) => [point[0], y(point[0], ...optimizedParams)]);
rSquared = getRSquared(observedData, y, optimizedParams);
res.writeHead(200, { 'Content-Type': 'text/html '});
}
res.end(template(error, fit, optimizedParams, rSquared, targetFunctionString, initialGuess, observedData, body.editManually));
});
};