chartx
Version:
Data Visualization Chart Library
419 lines (408 loc) • 13.4 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _commonjsHelpers308f = require("./common/_commonjsHelpers-308f7484.js");
var fmin = (0, _commonjsHelpers308f.a)(function (module, exports) {
(function (global, factory) {
factory(exports);
})(_commonjsHelpers308f.c, function (exports) {
/** finds the zeros of a function, given two starting points (which must
* have opposite signs */
function bisect(f, a, b, parameters) {
parameters = parameters || {};
var maxIterations = parameters.maxIterations || 100,
tolerance = parameters.tolerance || 1e-10,
fA = f(a),
fB = f(b),
delta = b - a;
if (fA * fB > 0) {
throw "Initial bisect points must have opposite signs";
}
if (fA === 0) return a;
if (fB === 0) return b;
for (var i = 0; i < maxIterations; ++i) {
delta /= 2;
var mid = a + delta,
fMid = f(mid);
if (fMid * fA >= 0) {
a = mid;
}
if (Math.abs(delta) < tolerance || fMid === 0) {
return mid;
}
}
return a + delta;
}
// need some basic operations on vectors, rather than adding a dependency,
// just define here
function zeros(x) {
var r = new Array(x);
for (var i = 0; i < x; ++i) {
r[i] = 0;
}
return r;
}
function zerosM(x, y) {
return zeros(x).map(function () {
return zeros(y);
});
}
function dot(a, b) {
var ret = 0;
for (var i = 0; i < a.length; ++i) {
ret += a[i] * b[i];
}
return ret;
}
function norm2(a) {
return Math.sqrt(dot(a, a));
}
function scale(ret, value, c) {
for (var i = 0; i < value.length; ++i) {
ret[i] = value[i] * c;
}
}
function weightedSum(ret, w1, v1, w2, v2) {
for (var j = 0; j < ret.length; ++j) {
ret[j] = w1 * v1[j] + w2 * v2[j];
}
}
/** minimizes a function using the downhill simplex method */
function nelderMead(f, x0, parameters) {
parameters = parameters || {};
var maxIterations = parameters.maxIterations || x0.length * 200,
nonZeroDelta = parameters.nonZeroDelta || 1.05,
zeroDelta = parameters.zeroDelta || 0.001,
minErrorDelta = parameters.minErrorDelta || 1e-6,
minTolerance = parameters.minErrorDelta || 1e-5,
rho = parameters.rho !== undefined ? parameters.rho : 1,
chi = parameters.chi !== undefined ? parameters.chi : 2,
psi = parameters.psi !== undefined ? parameters.psi : -0.5,
sigma = parameters.sigma !== undefined ? parameters.sigma : 0.5,
maxDiff;
// initialize simplex.
var N = x0.length,
simplex = new Array(N + 1);
simplex[0] = x0;
simplex[0].fx = f(x0);
simplex[0].id = 0;
for (var i = 0; i < N; ++i) {
var point = x0.slice();
point[i] = point[i] ? point[i] * nonZeroDelta : zeroDelta;
simplex[i + 1] = point;
simplex[i + 1].fx = f(point);
simplex[i + 1].id = i + 1;
}
function updateSimplex(value) {
for (var i = 0; i < value.length; i++) {
simplex[N][i] = value[i];
}
simplex[N].fx = value.fx;
}
var sortOrder = function sortOrder(a, b) {
return a.fx - b.fx;
};
var centroid = x0.slice(),
reflected = x0.slice(),
contracted = x0.slice(),
expanded = x0.slice();
for (var iteration = 0; iteration < maxIterations; ++iteration) {
simplex.sort(sortOrder);
if (parameters.history) {
// copy the simplex (since later iterations will mutate) and
// sort it to have a consistent order between iterations
var sortedSimplex = simplex.map(function (x) {
var state = x.slice();
state.fx = x.fx;
state.id = x.id;
return state;
});
sortedSimplex.sort(function (a, b) {
return a.id - b.id;
});
parameters.history.push({
x: simplex[0].slice(),
fx: simplex[0].fx,
simplex: sortedSimplex
});
}
maxDiff = 0;
for (i = 0; i < N; ++i) {
maxDiff = Math.max(maxDiff, Math.abs(simplex[0][i] - simplex[1][i]));
}
if (Math.abs(simplex[0].fx - simplex[N].fx) < minErrorDelta && maxDiff < minTolerance) {
break;
}
// compute the centroid of all but the worst point in the simplex
for (i = 0; i < N; ++i) {
centroid[i] = 0;
for (var j = 0; j < N; ++j) {
centroid[i] += simplex[j][i];
}
centroid[i] /= N;
}
// reflect the worst point past the centroid and compute loss at reflected
// point
var worst = simplex[N];
weightedSum(reflected, 1 + rho, centroid, -rho, worst);
reflected.fx = f(reflected);
// if the reflected point is the best seen, then possibly expand
if (reflected.fx < simplex[0].fx) {
weightedSum(expanded, 1 + chi, centroid, -chi, worst);
expanded.fx = f(expanded);
if (expanded.fx < reflected.fx) {
updateSimplex(expanded);
} else {
updateSimplex(reflected);
}
}
// if the reflected point is worse than the second worst, we need to
// contract
else if (reflected.fx >= simplex[N - 1].fx) {
var shouldReduce = false;
if (reflected.fx > worst.fx) {
// do an inside contraction
weightedSum(contracted, 1 + psi, centroid, -psi, worst);
contracted.fx = f(contracted);
if (contracted.fx < worst.fx) {
updateSimplex(contracted);
} else {
shouldReduce = true;
}
} else {
// do an outside contraction
weightedSum(contracted, 1 - psi * rho, centroid, psi * rho, worst);
contracted.fx = f(contracted);
if (contracted.fx < reflected.fx) {
updateSimplex(contracted);
} else {
shouldReduce = true;
}
}
if (shouldReduce) {
// if we don't contract here, we're done
if (sigma >= 1) break;
// do a reduction
for (i = 1; i < simplex.length; ++i) {
weightedSum(simplex[i], 1 - sigma, simplex[0], sigma, simplex[i]);
simplex[i].fx = f(simplex[i]);
}
}
} else {
updateSimplex(reflected);
}
}
simplex.sort(sortOrder);
return {
fx: simplex[0].fx,
x: simplex[0]
};
}
/// searches along line 'pk' for a point that satifies the wolfe conditions
/// See 'Numerical Optimization' by Nocedal and Wright p59-60
/// f : objective function
/// pk : search direction
/// current: object containing current gradient/loss
/// next: output: contains next gradient/loss
/// returns a: step size taken
function wolfeLineSearch(f, pk, current, next, a, c1, c2) {
var phi0 = current.fx,
phiPrime0 = dot(current.fxprime, pk),
phi = phi0,
phi_old = phi0,
phiPrime = phiPrime0,
a0 = 0;
a = a || 1;
c1 = c1 || 1e-6;
c2 = c2 || 0.1;
function zoom(a_lo, a_high, phi_lo) {
for (var iteration = 0; iteration < 16; ++iteration) {
a = (a_lo + a_high) / 2;
weightedSum(next.x, 1.0, current.x, a, pk);
phi = next.fx = f(next.x, next.fxprime);
phiPrime = dot(next.fxprime, pk);
if (phi > phi0 + c1 * a * phiPrime0 || phi >= phi_lo) {
a_high = a;
} else {
if (Math.abs(phiPrime) <= -c2 * phiPrime0) {
return a;
}
if (phiPrime * (a_high - a_lo) >= 0) {
a_high = a_lo;
}
a_lo = a;
phi_lo = phi;
}
}
return 0;
}
for (var iteration = 0; iteration < 10; ++iteration) {
weightedSum(next.x, 1.0, current.x, a, pk);
phi = next.fx = f(next.x, next.fxprime);
phiPrime = dot(next.fxprime, pk);
if (phi > phi0 + c1 * a * phiPrime0 || iteration && phi >= phi_old) {
return zoom(a0, a, phi_old);
}
if (Math.abs(phiPrime) <= -c2 * phiPrime0) {
return a;
}
if (phiPrime >= 0) {
return zoom(a, a0, phi);
}
phi_old = phi;
a0 = a;
a *= 2;
}
return a;
}
function conjugateGradient(f, initial, params) {
// allocate all memory up front here, keep out of the loop for perfomance
// reasons
var current = {
x: initial.slice(),
fx: 0,
fxprime: initial.slice()
},
next = {
x: initial.slice(),
fx: 0,
fxprime: initial.slice()
},
yk = initial.slice(),
pk,
temp,
a = 1,
maxIterations;
params = params || {};
maxIterations = params.maxIterations || initial.length * 20;
current.fx = f(current.x, current.fxprime);
pk = current.fxprime.slice();
scale(pk, current.fxprime, -1);
for (var i = 0; i < maxIterations; ++i) {
a = wolfeLineSearch(f, pk, current, next, a);
// todo: history in wrong spot?
if (params.history) {
params.history.push({
x: current.x.slice(),
fx: current.fx,
fxprime: current.fxprime.slice(),
alpha: a
});
}
if (!a) {
// faiiled to find point that satifies wolfe conditions.
// reset direction for next iteration
scale(pk, current.fxprime, -1);
} else {
// update direction using Polak–Ribiere CG method
weightedSum(yk, 1, next.fxprime, -1, current.fxprime);
var delta_k = dot(current.fxprime, current.fxprime),
beta_k = Math.max(0, dot(yk, next.fxprime) / delta_k);
weightedSum(pk, beta_k, pk, -1, next.fxprime);
temp = current;
current = next;
next = temp;
}
if (norm2(current.fxprime) <= 1e-5) {
break;
}
}
if (params.history) {
params.history.push({
x: current.x.slice(),
fx: current.fx,
fxprime: current.fxprime.slice(),
alpha: a
});
}
return current;
}
function gradientDescent(f, initial, params) {
params = params || {};
var maxIterations = params.maxIterations || initial.length * 100,
learnRate = params.learnRate || 0.001,
current = {
x: initial.slice(),
fx: 0,
fxprime: initial.slice()
};
for (var i = 0; i < maxIterations; ++i) {
current.fx = f(current.x, current.fxprime);
if (params.history) {
params.history.push({
x: current.x.slice(),
fx: current.fx,
fxprime: current.fxprime.slice()
});
}
weightedSum(current.x, 1, current.x, -learnRate, current.fxprime);
if (norm2(current.fxprime) <= 1e-5) {
break;
}
}
return current;
}
function gradientDescentLineSearch(f, initial, params) {
params = params || {};
var current = {
x: initial.slice(),
fx: 0,
fxprime: initial.slice()
},
next = {
x: initial.slice(),
fx: 0,
fxprime: initial.slice()
},
maxIterations = params.maxIterations || initial.length * 100,
learnRate = params.learnRate || 1,
pk = initial.slice(),
c1 = params.c1 || 1e-3,
c2 = params.c2 || 0.1,
temp,
functionCalls = [];
if (params.history) {
// wrap the function call to track linesearch samples
var inner = f;
f = function f(x, fxprime) {
functionCalls.push(x.slice());
return inner(x, fxprime);
};
}
current.fx = f(current.x, current.fxprime);
for (var i = 0; i < maxIterations; ++i) {
scale(pk, current.fxprime, -1);
learnRate = wolfeLineSearch(f, pk, current, next, learnRate, c1, c2);
if (params.history) {
params.history.push({
x: current.x.slice(),
fx: current.fx,
fxprime: current.fxprime.slice(),
functionCalls: functionCalls,
learnRate: learnRate,
alpha: learnRate
});
functionCalls = [];
}
temp = current;
current = next;
next = temp;
if (learnRate === 0 || norm2(current.fxprime) < 1e-5) break;
}
return current;
}
exports.bisect = bisect;
exports.nelderMead = nelderMead;
exports.conjugateGradient = conjugateGradient;
exports.gradientDescent = gradientDescent;
exports.gradientDescentLineSearch = gradientDescentLineSearch;
exports.zeros = zeros;
exports.zerosM = zerosM;
exports.norm2 = norm2;
exports.weightedSum = weightedSum;
exports.scale = scale;
});
});
var _default = exports.default = fmin;