-
Notifications
You must be signed in to change notification settings - Fork 55
/
Copy pathbrent.js
203 lines (203 loc) · 5.15 KB
/
brent.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
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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
/* brent.js
* JS implementation of the Brent's method of root finding
*
* 6th February 2015
*
* The 'findRoot' method tries to find the tightest bracket (sign change) to one side or the other of an initial guess 'I'
* and then performs Brent's method. If a given bracket distance suggests roots on either side of 'I', both brackets are
* evaluated and then the root closest to 'I' is returned. If no root is found, extremes are returned which can then be
* replaced with minimum and maximum values found across the set.
*
* Sources of information:
* Wikipedia: http://en.wikipedia.org/wiki/Brent%27s_method
* Matlab Script by David Eagle: http://uk.mathworks.com/matlabcentral/fileexchange/39973-a-matlab-script-for-earth-to-mars-mission-design/content/brent.m
*
* By Ben Turley, http://turley.tv
* First License: GPLv2
*/
function Brent(func, a, b) {
this.func = func; // Object providing function / data set for analysis - requires a method 'f' which supplies f(x)
if (typeof a === 'number' && typeof b === 'number') {
this.setRange(a,b);
} else {
this.setRange(0,1);
}
this.delta = 1;
this.tol = 0.00000001; // tolerence - OK for Float64s, too small for Float32s
}
Brent.prototype.setRange = function(a,b) {
this.a = a; // For f(x), minimum value of x
this.fMin = this.func.f(a);
this.b = b; // For f(x), maximum value of x
this.fMax = this.func.f(b);
this.mid = this.func.f((a+b)/2);
};
Brent.prototype.setDelta = function(d) {
this.delta = d;
};
Brent.prototype.setTolerance = function(tol) {
this.tol = tol;
};
Brent.prototype.minMax = function(x) {
if (x < this.a) {
this.a = x;
this.fMin = this.o;
} else if (x > this.b) {
this.b = x;
this.fMax = this.o;
}
};
Brent.prototype.getMinMax = function() {
return { a: this.a, fMin: this.fMin, b: this.b, fMax: this.fMax };
};
Brent.prototype.findRoot = function(I,O) { // Public single root method, O is the f(x) to match to if not zero (root), finds closest root to I
var tol = this.tol;
if (typeof O === 'number') {
this.o = O;
} else {
this.o = 0;
}
var y0 = this.func.f(I) - this.o;
if (Math.abs(y0)<tol) {
this.minMax(I);
return I;
}
var dx;
var yl,yh;
var dl,dh;
for (var j=1; j<25; j++) {
dx = this.delta * (Math.pow(1.04,j) - 1);
yl = this.func.f(I-dx) - this.o;
yh = this.func.f(I+dx) - this.o;
if (Math.abs(yl)<tol) {
this.minMax(I-dx);
return I-dx;
} else if (Math.abs(yh)<tol) {
this.minMax(I+dx);
return I+dx;
} else {
dl = yl*y0;
dh = yh*y0;
if (dl<0 && dh<0) { // Bracket on both sides of the first guess
var rl = this.brent(I, y0, I-dx, yl, tol);
var rh = this.brent(I, y0, I+dx, yh, tol);
if (Math.abs(rl - I) < Math.abs(rh - I)) {
this.minMax(rl);
return this.clamp(rl);
} else {
this.minMax(rh);
return this.clamp(rh);
}
} else if (dl<0) { // Bracket below first guess
var r = this.brent(I, y0, I-dx, yl, tol);
this.minMax(r);
return this.clamp(r);
} else if (dh<0) { // Bracket above first guess
var r = this.brent(I, y0, I+dx, yh, tol);
this.minMax(r);
return this.clamp(r);
}
}
}
// If a bracket can't be found, set to an extreme based on the f(x) value halfway within a<x<b,
// then when finished, iterate through the arrays swapping the extremes for the min and max
// values of x calculated.
if (O<this.mid) {
return -65536;
} else {
return 65536;
}
};
Brent.prototype.clamp = function(i) {
if (i > 65536) {
return 65536;
} else if (i < -65536) {
return -65536;
} else {
return i;
}
};
Brent.prototype.brent = function(a,fa,b,fb,rtol) {
var eps = 2.22e-16; // Machine epsilon for Float64Arrays
var e = 0;
var tol1, xm, min, tmp;
var p, q, r, s;
var fc = fb;
for (var j=0; j<80; j++) {
if (fb * fc > 0) {
c = a;
fc = fa;
d = b - a;
e = d;
}
if (Math.abs(fc) < Math.abs(fb)) {
a = b;
b = c;
c = a;
fa = fb;
fb = fc;
fc = fa;
}
tol1 = (2*eps*Math.abs(b))+(0.5*rtol);
xm = 0.5 * (c - b);
if (Math.abs(xm) <= tol1 || fb === 0) {
return b;
}
if (Math.abs(e) >= tol1 && Math.abs(fa) > Math.abs(fb)) {
s = fb / fa;
if (a === c) {
p = 2*xm*s;
q = 1-s;
} else {
q = fa/fc;
r = fb/fc;
p = s*((2*xm*q*(q - r))-((b - a)*(r - 1)));
q = (q - 1)*(r - 1)*(s - 1);
}
if (p > 0) {
q = -q;
}
p = Math.abs(p);
min = Math.abs(e * q);
tmp = (3*xm*q) - Math.abs(tol1*q);
if (min < tmp) {
min = tmp;
}
if (2*p < min) {
e = d;
d = p / q;
} else {
d = xm;
e = d;
}
} else {
d = xm;
e = d;
}
a = b;
fa = fb;
if (Math.abs(d) > tol1) {
b = b + d;
} else {
if (xm >=0) {
b = b + tol1;
} else {
b = b - tol1;
}
}
fb = this.func.f(b) - this.o;
}
console.log('none');
return b;
};
// Stringify for inline Web Workers
function getBrentString() {
var out = "";
// Brent
out += Brent.toString() + "\n";
for (var j in Brent.prototype) {
out += 'Brent.prototype.' + j + '=' + Brent.prototype[j].toString() + "\n";
}
return out;
}
var workerBrentString = getBrentString();