nelder-mead.js
4.52 KB
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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
/* *
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
/* eslint-disable valid-jsdoc */
var getCentroid = function (simplex) {
var arr = simplex.slice(0, -1), length = arr.length, result = [], sum = function (data, point) {
data.sum += point[data.i];
return data;
};
for (var i = 0; i < length; i++) {
result[i] = arr.reduce(sum, { sum: 0, i: i }).sum / length;
}
return result;
};
/**
* Finds an optimal position for a given point.
* @todo add unit tests.
* @todo add constraints to optimize the algorithm.
* @private
* @param {Highcharts.NelderMeadTestFunction} fn
* The function to test a point.
* @param {Highcharts.NelderMeadPointArray} initial
* The initial point to optimize.
* @return {Highcharts.NelderMeadPointArray}
* Returns the opimized position of a point.
*/
var nelderMead = function nelderMead(fn, initial) {
var maxIterations = 100, sortByFx = function (a, b) {
return a.fx - b.fx;
}, pRef = 1, // Reflection parameter
pExp = 2, // Expansion parameter
pCon = -0.5, // Contraction parameter
pOCon = pCon * pRef, // Outwards contraction parameter
pShrink = 0.5; // Shrink parameter
/**
* @private
*/
var weightedSum = function weightedSum(weight1, v1, weight2, v2) {
return v1.map(function (x, i) {
return weight1 * x + weight2 * v2[i];
});
};
/**
* @private
*/
var getSimplex = function getSimplex(initial) {
var n = initial.length, simplex = new Array(n + 1);
// Initial point to the simplex.
simplex[0] = initial;
simplex[0].fx = fn(initial);
// Create a set of extra points based on the initial.
for (var i = 0; i < n; ++i) {
var point = initial.slice();
point[i] = point[i] ? point[i] * 1.05 : 0.001;
point.fx = fn(point);
simplex[i + 1] = point;
}
return simplex;
};
var updateSimplex = function (simplex, point) {
point.fx = fn(point);
simplex[simplex.length - 1] = point;
return simplex;
};
var shrinkSimplex = function (simplex) {
var best = simplex[0];
return simplex.map(function (point) {
var p = weightedSum(1 - pShrink, best, pShrink, point);
p.fx = fn(p);
return p;
});
};
var getPoint = function (centroid, worst, a, b) {
var point = weightedSum(a, centroid, b, worst);
point.fx = fn(point);
return point;
};
// Create a simplex
var simplex = getSimplex(initial);
// Iterate from 0 to max iterations
for (var i = 0; i < maxIterations; i++) {
// Sort the simplex
simplex.sort(sortByFx);
// Create a centroid from the simplex
var worst = simplex[simplex.length - 1];
var centroid = getCentroid(simplex);
// Calculate the reflected point.
var reflected = getPoint(centroid, worst, 1 + pRef, -pRef);
if (reflected.fx < simplex[0].fx) {
// If reflected point is the best, then possibly expand.
var expanded = getPoint(centroid, worst, 1 + pExp, -pExp);
simplex = updateSimplex(simplex, (expanded.fx < reflected.fx) ? expanded : reflected);
}
else if (reflected.fx >= simplex[simplex.length - 2].fx) {
// If the reflected point is worse than the second worse, then
// contract.
var contracted;
if (reflected.fx > worst.fx) {
// If the reflected is worse than the worst point, do a
// contraction
contracted = getPoint(centroid, worst, 1 + pCon, -pCon);
if (contracted.fx < worst.fx) {
simplex = updateSimplex(simplex, contracted);
}
else {
simplex = shrinkSimplex(simplex);
}
}
else {
// Otherwise do an outwards contraction
contracted = getPoint(centroid, worst, 1 - pOCon, pOCon);
if (contracted.fx < reflected.fx) {
simplex = updateSimplex(simplex, contracted);
}
else {
simplex = shrinkSimplex(simplex);
}
}
}
else {
simplex = updateSimplex(simplex, reflected);
}
}
return simplex[0];
};
var content = {
getCentroid: getCentroid,
nelderMead: nelderMead
};
export default content;