# brent-zero-generator

This module implements Brent's algorithm to find a root of a function `f()`.
The module provides 3 functions: `zero()`, `zero_generator()`, and
`zero_generator2()`. The functions `zero_generator()` and `zero_generator2()`
facilitate solving asynchronous functions, and provide a lot of flexibility
(see example below). For typical use, the simpler `zero()` function should
be sufficient.

## WARNING: the Ignorant Suffer
Before you consider using another similar code, have a look at the README.md
of my [BrentZeroTester.JS](https://bitbucket.org/hbieshaar/brentzerotester.js/src/master/)
repository, where several alternative codes are compared. Both horrible
performances, and wrong results happen in some of these codes.

## TIP: T.R. Chandrupatla's zero
The package [chandrupatla-zero](https://www.npmjs.com/package/chandrupatla-zero) contains my JavaScript implementation of T.R. Chandrupatla's algorithm. The repository [compare-zeros](https://bitbucket.org/hbieshaar/compare-zeros/src/main/), in my opinion, shows it is a bit better than Brent's algorithm.

## Usage

The JavaScript code in this module follows Brent's original code of the
Algol 60 procedure `zero()` very closely. In the first comment in that code,
Brent describes procedure `zero()` as:
> Procedure *zero* retuns a zero *x* of the function *f* in the given
> interval [*a*, *b*], to within a tolerance *(4 macheps |x| + 2 t)*,
> where *macheps* is the relative machine precision and *t* is a positive
> tolerance. The procedure assumes that *f(a)* and *f(b)* have different signs

### Parameters

|Parameter | Description
|----------|------------
|f         | the function `f()` for which the root is sought. Only for the utility function `zero()`.
|a         | the finite lowerbound of the interval in which to search the root
|b         | the finite upperbound of the interval in which to search the root
|macheps   | the relative tolerance. This parameter may be increased if a higher relative error is acceptable, but it should not be decreased, for then rounding errors might prevent convergence. (optional, default: `Number.EPSILON`)
|t         | the absolute tolerance. (optional, default: `0.0`)

### Functions

- `zero_generator2(a, b, macheps, t)`, and
- `zero_generator(a, b, macheps, t)`, and
- `zero(f, a, b, macheps, t)`.

### Yields and returns

The generator functions `zero_generator()` and `zero_generator2()` only yield,
the 'x' values, to request function evaluations. When the `zero_generator2()`
function is 'done', it returns an array with the following contents:

 - res[0]: the x-value thought to be closest to the root,
 - res[1]: the corresponding y-value,
 - res[2]: the x-value closest to, but on the other side of the root,
 - res[3]: the corresponding y-value.

The functions `zero_generator()` and `zero()` only return the x-value,
thought to be closest to the root.

### Notes
 - *f(a)* and *f(b)* should have different signs.
 - the algorithm ends when `f(x) == 0` is found, or the interval [a, b] has
   been sufficiently shrunk to be sure the `x` is within the tolerance.
   This says nothing about the value of `f(x)`. For example the function
   `x => x < 3 ? -1 : 2`, will find a "root" close to 3, but `f(x)` will be -1.
 - the original comment in the Algol 60 code mentions
   'a tolerance *(6 macheps |x| + 2 t)*'. The code of the algorithm limits
   the tolerance to *4 macheps |x| + 2 t*. Brent adds '*2 macheps* |x|' due
   to inprecision in the calculation of the function values. I think this
   is confusing, so I changed the quote.

## Some Examples

The following examples shows simple usage. Given a function
*f(x) = x&sup2; - 5*.

### 1. Simplest `zero()` example
``` js
const brents = require('brent-zero-generator');

const f = x => x * x - 5;

console.log( brents.zero(f, 1, 4).toFixed(6) );
// expect 2.236068
 ```

### 2. Simple `zero_generator2()` example

``` js
const gen = brents.zero_generator2(-3, -2);

let result = gen.next();
while ( ! result.done ) {
    const x = result.value;
    const y = f(x);

    result = gen.next(y)    
}
console.log( result.value.toFixed(6) );
// expect [ -2.236068, 8.8e-16, -2.236068, -3.5e-15 ]
```

### 3. Modified `zero_generator()` example

Often implementations have additional parameters for maximum number of
iterations (`max_iter`), or an allowed tolerance for the root's function value
(`yTol`). Brent's original code does not have these options, and I am fairly
sure he would not agree with them. However, using the generator function,
they are easily implemented, as the following example function shows:
``` js
function myZero(f, a, b, max_iter, yTol) {
    const gen = zero_generator(a, b);

    let result = gen.next();
    for (let i=0; i<max_iter && ! result.done; ++i) {
        const x = result.value;
        const y = f(x);
        if (Math.abs(y) < yTol) break;

        result = gen.next(y)    
    }

    return result.value;
}
```

## API changes

 - 1.1 Added `zero_generator2()` as an export.

## Further Documentation
The ultimate documentation on this algorithm can be found on Emeritus Professor
Richard P. Brent's page dedicated to his book [Algorithms for Minimization
Without Derivatives](https://maths-people.anu.edu.au/~brent/pub/pub011.html).  
On that page, not only the errata for the different editions of the book are
available, but also a scanned copy of the Prentice-Hall edition is also 
available for download.

C, C++, Fortran66/Fortran77 and Fortran90 codes can be found on John Burkardt's [homepage](https://people.sc.fsu.edu/~jburkardt/).

A [Wolfram-MathWorld](https://mathworld.wolfram.com/BrentsMethod.html) page explains the way in which the *inverse quadratic interpolation*, which is important for the numerical stability, is implemented.

## License
[ISC](https://choosealicense.com/licenses/isc/)
