/
BrentRoot.cs
241 lines (202 loc) · 7.6 KB
/
BrentRoot.cs
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
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
/*
* Copyright Lamont Granquist, Sebastien Gaggini and the MechJeb contributors
* SPDX-License-Identifier: LicenseRef-PD-hp OR Unlicense OR CC0-1.0 OR 0BSD OR MIT-0 OR MIT OR LGPL-2.1+
*/
#nullable enable
using System;
using MechJebLib.Utils;
using static MechJebLib.Utils.Statics;
using static System.Math;
// ReSharper disable CompareOfFloatsByEqualityOperator
namespace MechJebLib.Rootfinding
{
/// <summary>
/// Brent's rootfinding method.
/// </summary>
public static class BrentRoot
{
/// <summary>
/// Brent's rootfinding method, bounded search. Uses secant, inverse quadratic interpolation and bisection.
/// </summary>
/// <param name="f">1 dimensional function</param>
/// <param name="a">minimum bounds</param>
/// <param name="b">maximum bounds</param>
/// <param name="o">state object to pass to function</param>
/// <param name="maxiter">maximum iterations</param>
/// <param name="rtol">tolerance</param>
/// <param name="sign">try to return x such that f(x0) has the same sign as this (0 is best match)</param>
/// <returns>value for which the function evaluates to zero</returns>
/// <exception cref="ArgumentException">guess does not brack the root</exception>
public static double Solve(Func<double, object?, double> f, double a, double b, object? o, int maxiter = 100, double rtol = EPS,
int sign = 0)
{
double fa = f(a, o);
double fb = f(b, o);
Check.Finite(a);
Check.Finite(fa);
Check.Finite(b);
Check.Finite(fb);
if (fa == 0)
return a;
if (fb == 0)
return b;
if (fa * fb > 0)
throw new ArgumentException("Brent's rootfinding method: guess does not bracket the root");
if (Abs(fa) < Abs(fb))
{
(a, b) = (b, a);
(fa, fb) = (fb, fa);
}
return _Solve(f, a, b, fa, fb, o, maxiter, rtol, sign);
}
/// <summary>
/// Brent's rootfinding method, unbounded search. Uses secant, inverse quadratic interpolation and bisection.
/// </summary>
/// <param name="f">1 dimensional function</param>
/// <param name="x">Initial guess</param>
/// <param name="o">state object to pass to function</param>
/// <param name="maxiter">maximum iterations</param>
/// <param name="rtol">tolerance</param>
/// <param name="sign">try to return x such that f(x0) has the same sign as this (0 is best match)</param>
/// <returns>value for which the function evaluates to zero</returns>
public static double Solve(Func<double, object?, double> f, double x, object? o, int maxiter = 100, double rtol = EPS, int sign = 0)
{
double a = x;
double b = x;
double fa = f(x, o);
double fb = fa;
Check.Finite(x);
Check.Finite(fa);
if (fa == 0)
return a;
// initial guess to expand search for the zero
double dx = Abs(x / 50);
// if x is zero use a larger expansion
if (dx <= 2.24e-15)
dx = 1 / 50d;
// expand by sqrt(2) each iteration
double sqrt2 = Sqrt(2);
// iterate until we find a range that brackets the root
while (fa > 0 == fb > 0)
{
dx *= sqrt2;
a = x - dx;
fa = f(a, o);
Check.Finite(a);
Check.Finite(fa);
if (fa == 0)
return a;
if (fa > 0 != fb > 0)
break;
b = x + dx;
fb = f(b, o);
Check.Finite(b);
Check.Finite(fb);
if (fb == 0)
return b;
}
return _Solve(f, a, b, fa, fb, o, maxiter, rtol, sign);
}
// This is the actual algorithm
private static double _Solve(Func<double, object?, double> f, double a, double b, double fa, double fb, object? o, int maxiter,
double rtol, int sign)
{
bool maybeOneMore = sign != 0;
double i = 0;
// this ensures we always run the first if block first and initialize c,d,e
double fc = fb;
double c = double.NaN;
double d = double.NaN;
double e = double.NaN;
// ReSharper disable once CompareOfFloatsByEqualityOperator
while (fb != 0 && a != b)
{
if (fb > 0 == fc > 0)
{
c = a;
fc = fa;
d = b - a;
e = d;
}
if (Abs(fc) < Abs(fb))
{
a = b;
b = c;
c = a;
fa = fb;
fb = fc;
fc = fa;
}
double m = 0.5 * (c - b);
double toler = 2.0 * rtol * Max(Abs(b), 1.0);
if (Abs(m) <= toler || fb == 0.0)
{
// try one more round to improve fc if fb does not match the sign
if (maybeOneMore && Sign(fb) != sign)
maybeOneMore = false;
else
break;
}
if (Abs(e) < toler || Abs(fa) <= Abs(fb))
{
// Bisection
d = m;
e = m;
}
else
{
// Interpolation
double s = fb / fa;
double p;
double q;
// ReSharper disable once CompareOfFloatsByEqualityOperator
if (a == c)
{
// Linear interpolation
p = 2.0 * m * s;
q = 1.0 - s;
}
else
{
// Inverse quadratic interpolation
q = fa / fc;
double r = fb / fc;
p = s * (2.0 * m * q * (q - r) - (b - a) * (r - 1.0));
q = (q - 1.0) * (r - 1.0) * (s - 1.0);
}
if (p > 0)
q = -q;
else
p = -p;
// Acceptibility check
if (2.0 * p < 3.0 * m * q - Abs(toler * q) && p < Abs(0.5 * e * q))
{
e = d;
d = p / q;
}
else
{
d = m;
e = m;
}
}
a = b;
fa = fb;
if (Abs(d) > toler)
b += d;
else if (b > c)
b -= toler;
else
b += toler;
fb = f(b, o);
Check.Finite(a);
Check.Finite(fa);
if (i++ >= maxiter && maxiter > 0)
throw new TimeoutException("Brent's rootfinding method: maximum iterations exceeded: " + Abs(a - c));
}
if (sign != 0 && Sign(fb) != sign)
return c;
return b;
}
}
}