Skip to content

Commit

Permalink
Added options parameter for adjusting optimization; added more tests;…
Browse files Browse the repository at this point in the history
… added more details to README
  • Loading branch information
tinybike committed Feb 8, 2016
1 parent f585072 commit 8006f2f
Show file tree
Hide file tree
Showing 6 changed files with 80 additions and 39 deletions.
22 changes: 20 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,31 @@ A minified, browserified file `dist/fzero.min.js` is included for use in the bro
```html
<script src="dist/fzero.min.js" type="text/javascript"></script>
```
`fzero` is a function that takes 3 arguments: the function to find the zero of, a lower-bound for the zero, and an upper-bound for the zero. Note: since `fzero` uses decimal.js for arithmetic, the input function should accept a string input (rather than a JS number).
`fzero` is a function that takes 3 required arguments: the function to find the zero of, a lower-bound for the zero, and an upper-bound for the zero. A fourth optional argument can be used to adjust fzero's settings; see tests for details.
```javascript
var myFunction = function (x) { Math.cos(Number(x)); };
var lowerBound = 0;
var upperBound = 3;
var zero = fzero(myFunction, lowerBound, upperBound);
var options = {maxiter: 50};
var zero = fzero(myFunction, lowerBound, upperBound, options);
```
`fzero` returns an object that has the following fields:

- `solution`: `x` such that `myFunction(x) = 0`
- `fval`: The numerical value of `myFunction` at `solution`.
- `code`: Exit flag which can have one of the following values.
- `1`: The algorithm converged to a solution.
- `0`: Maximum number of iterations or function evaluations has been reached.
- `-1`: The algorithm has been terminated from user output function.
- `-5`: The algorithm may have converged to a singular point.
- `diagnostic`: An object which has the following fields.
- `iterations`: The number of iterations completed.
- `functionEvals`: Number of times `myFunction` was evaluated.
- `bracketx`: An array `[lower, upper]` with the ending lower and upper x-bounds.
- `brackety`: An array `[f(lower), f(upper)]` with the ending lower and upper y-bounds.

Note: `fzero` uses [decimal.js](https://github.com/MikeMcl/decimal.js/) for arithmetic, so the input function should accept a string input (rather than a JS number).

Tests
-----
Unit tests are included in `test/`, and can be run using npm:
Expand Down
35 changes: 18 additions & 17 deletions dist/fzero.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,6 @@ function unique(arr) {
return a;
}

var mu = new Decimal("0.5");
var eps = new Decimal("0.001");
var tolx = new Decimal(0);
var maxiter = 100;

function toDecimal(x) {
if (x && x.constructor !== Decimal) {
if (x.toFixed && x.toFixed.constructor === Function) x = x.toFixed();
Expand All @@ -52,10 +47,16 @@ function toDecimal(x) {
return x;
}

module.exports = function (f, lower, upper) {
module.exports = function (f, lower, upper, options) {
options = options || {};
var mu = toDecimal(options.mu) || new Decimal("0.5");
var eps = toDecimal(options.eps) || new Decimal("0.001");
var tolx = toDecimal(options.tolx) || new Decimal(0);
var maxiter = options.maxiter || 100;
var maxfev = options.maxfev || maxiter;

// The default exit flag if exceeded number of iterations.
var info = 0;
var code = 0;
var niter = 0;
var nfev = 0;

Expand Down Expand Up @@ -110,14 +111,14 @@ module.exports = function (f, lower, upper) {
var fe = fu;
var mba = mu.times(b.minus(a));
var c, df;
while (niter < maxiter && nfev < maxiter) {
while (niter < maxiter && nfev < maxfev) {
switch (itype) {
case 1:
// The initial test.
if (b.minus(a).lte(u.abs().times(eps).times(new Decimal(2)).plus(tolx).times(new Decimal(2)))) {
x = u;
fval = fu;
info = 1;
code = 1;
} else {
if (fa.abs().lte(fb.abs().times(new Decimal(1000))) && (fb.abs().lte(fa.abs().times(new Decimal(1000))))) {
// Secant step.
Expand Down Expand Up @@ -235,7 +236,7 @@ module.exports = function (f, lower, upper) {
a = b;
fb = fc;
fa = fb;
info = 1;
code = 1;
} else {
// This should never happen.
throw new Error("zero point is not bracketed");
Expand All @@ -249,7 +250,7 @@ module.exports = function (f, lower, upper) {
fu = fb;
}
if (b.minus(a).lte(u.abs().times(eps).times(new Decimal(2)).plus(tolx))) {
info = 1;
code = 1;
}

// Skip bisection step if successful reduction.
Expand All @@ -263,25 +264,25 @@ module.exports = function (f, lower, upper) {
} // while

// Check solution for a singularity by examining slope.
if (info === 1) {
if (code === 1) {
var m;
if (new Decimal(1e6).gt(new Decimal("0.5").dividedBy(eps.plus(tolx)))) {
m = new Decimal(1e6);
} else {
m = new Decimal("0.5").dividedBy(eps.plus(tolx));
}
if (!b.minus(a).eq(new Decimal(0)) && fb.minus(fa).dividedBy(b.minus(a)).dividedBy(slope0).gt(m)) {
info = -5;
code = -5;
}
}

return {
x: x,
solution: x,
fval: fval,
info: info,
output: {
code: code,
diagnostic: {
iterations: niter,
funcCount: nfev,
functionEvals: nfev,
bracketx: [a, b],
brackety: [fa, fb]
}
Expand Down
4 changes: 2 additions & 2 deletions dist/fzero.min.js

Large diffs are not rendered by default.

33 changes: 17 additions & 16 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,6 @@ function unique(arr) {
return a;
}

var mu = new Decimal("0.5");
var eps = new Decimal("0.001");
var tolx = new Decimal(0);
var maxiter = 100;

function toDecimal(x) {
if (x && x.constructor !== Decimal) {
if (x.toFixed && x.toFixed.constructor === Function) x = x.toFixed();
Expand All @@ -45,10 +40,16 @@ function toDecimal(x) {
return x;
}

module.exports = function (f, lower, upper) {
module.exports = function (f, lower, upper, options) {
options = options || {};
var mu = toDecimal(options.mu) || new Decimal("0.5");
var eps = toDecimal(options.eps) || new Decimal("0.001");
var tolx = toDecimal(options.tolx) || new Decimal(0);
var maxiter = options.maxiter || 100;
var maxfev = options.maxfev || maxiter;

// The default exit flag if exceeded number of iterations.
var info = 0;
var code = 0;
var niter = 0;
var nfev = 0;

Expand Down Expand Up @@ -103,14 +104,14 @@ module.exports = function (f, lower, upper) {
var fe = fu;
var mba = mu.times(b.minus(a));
var c, df;
while (niter < maxiter && nfev < maxiter) {
while (niter < maxiter && nfev < maxfev) {
switch (itype) {
case 1:
// The initial test.
if (b.minus(a).lte(u.abs().times(eps).times(new Decimal(2)).plus(tolx).times(new Decimal(2)))) {
x = u;
fval = fu;
info = 1;
code = 1;
} else {
if (fa.abs().lte(fb.abs().times(new Decimal(1000))) && (fb.abs().lte(fa.abs().times(new Decimal(1000))))) {
// Secant step.
Expand Down Expand Up @@ -228,7 +229,7 @@ module.exports = function (f, lower, upper) {
a = b;
fb = fc;
fa = fb;
info = 1;
code = 1;
} else {
// This should never happen.
throw new Error("zero point is not bracketed");
Expand All @@ -242,7 +243,7 @@ module.exports = function (f, lower, upper) {
fu = fb;
}
if (b.minus(a).lte(u.abs().times(eps).times(new Decimal(2)).plus(tolx))) {
info = 1;
code = 1;
}

// Skip bisection step if successful reduction.
Expand All @@ -256,25 +257,25 @@ module.exports = function (f, lower, upper) {
} // while

// Check solution for a singularity by examining slope.
if (info === 1) {
if (code === 1) {
var m;
if (new Decimal(1e6).gt(new Decimal("0.5").dividedBy(eps.plus(tolx)))) {
m = new Decimal(1e6);
} else {
m = new Decimal("0.5").dividedBy(eps.plus(tolx));
}
if (!b.minus(a).eq(new Decimal(0)) && fb.minus(fa).dividedBy(b.minus(a)).dividedBy(slope0).gt(m)) {
info = -5;
code = -5;
}
}

return {
solution: x,
fval: fval,
info: info,
output: {
code: code,
diagnostic: {
iterations: niter,
funcCount: nfev,
functionEvals: nfev,
bracketx: [a, b],
brackety: [fa, fb]
}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "fzero",
"version": "0.1.3",
"version": "0.1.4",
"description": "Find a zero of a univariate function.",
"main": "index.js",
"scripts": {
Expand Down
23 changes: 22 additions & 1 deletion test/fzero.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,37 @@ var test = require("tape");
var Decimal = require("decimal.js");
var fzero = require("../");

var ONE = new Decimal(1);
var TWO = new Decimal(2);
var HALF = ONE.dividedBy(TWO);

function cos(x) {
return Math.cos(Number(x)).toString();
}
function exp(x) {
return (Math.exp(-Number(x)) - 2).toString();
}
function log(x) {
return Math.log(Number(x)).toString();
}
function hill(x) {
return new Decimal(x).minus(ONE).toPower(TWO).neg().plus(ONE);
}
function logistic(x) {
return ONE.dividedBy(ONE.plus(new Decimal(x).neg().exp())).minus(HALF);
}

test("fzero", function (t) {
t.plan(2);
t.plan(10);
t.equal(fzero(cos, 0, 3).solution.toFixed(8), (Math.PI / 2).toFixed(8), "fzero(cos, 0, 3) == Pi/2");
t.equal(fzero(cos, 0, 3, {eps: "0.05"}).solution.toFixed(8), (Math.PI / 2).toFixed(8), "fzero(cos, 0, 3, {eps: '0.05'}) == Pi/2");
t.equal(fzero(cos, 0, 3, {eps: "0.05", mu: "0.25"}).solution.toFixed(8), (Math.PI / 2).toFixed(8), "fzero(cos, 0, 3, {eps: '0.05', mu: '0.25'}) == Pi/2");
t.equal(fzero(cos, 0, 3, {maxiter: 50}).solution.toFixed(8), (Math.PI / 2).toFixed(8), "fzero(cos, 0, 3, {maxiter: 25}) == Pi/2");
t.equal(fzero(cos, 0, 3, {maxiter: 50, maxfev: 45}).solution.toFixed(8), (Math.PI / 2).toFixed(8), "fzero(cos, 0, 3, {maxiter: 25, maxfev: 20}) == Pi/2");
t.equal(fzero(exp, -10, 10).solution.toFixed(8), "-0.69314718", "fzero(exp(-x) - 2, -10, 10) == -0.69314718");
t.equal(fzero(log, 0.01, 2).solution.toFixed(8), "1.00000000", "fzero(log, 0.01, 2) == 1.00000000");
t.equal(fzero(hill, -1, 1).solution.toFixed(8), "-0.00000000", "fzero(-(x - 1)^2 + 1, -1, 1) == 0.00000000");
t.equal(fzero(hill, 1, 3).solution.toFixed(8), "2.00000000", "fzero(-(x - 1)^2 + 1, 1, 3) == 2.00000000");
t.equal(fzero(logistic, -10, 10).solution.toFixed(8), "-0.00000000", "fzero(1/(1 + e^(-x)) - 1/2, -10, 10) == 0.00000000");
t.end();
});

0 comments on commit 8006f2f

Please sign in to comment.