LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
rootfind.h
Go to the documentation of this file.
1/*
2 * Copyright (c) 2012-2026, QORE Lab, Imperial College London
3 * All rights reserved.
4 */
5#ifndef LINE_UTIL_ROOTFIND_H
6#define LINE_UTIL_ROOTFIND_H
7
8/**
9 * @file
10 * @ingroup line_util
11 * Deterministic scalar root finding.
12 *
13 * This is the replacement for MATLAB's fsolve in the scalar cases the API
14 * layer needs (the characteristic-time equations of the TTL cache
15 * approximations, matlab/src/api/cache/cache_ttl_lrua.m and
16 * cache_t_lrum_map.m, whose per-list residual is monotone in its own time).
17 * fsolve is a trust-region method seeded from a caller-supplied -- in
18 * cache_ttl_lrua.m, a *randomly generated* -- initial point, and its answer
19 * therefore depends on rng state and on Optimization Toolbox availability.
20 * Everything here is bracket based and deterministic: the same bracket and
21 * tolerance always produce the same digits, with no global state, no toolbox,
22 * and no fallback path that silently changes method.
23 *
24 * Three methods are provided:
25 * root_bisect - bisection, one bit per iteration, cannot fail once the
26 * bracket has a sign change
27 * root_brent - Brent's method (inverse quadratic interpolation, secant
28 * and bisection), superlinear but never worse than
29 * bisection because every step is kept inside the bracket
30 * root_newton - plain Newton from a starting point, for roots of even
31 * multiplicity, which no bracketing method can see
32 * root_newton_safe - Newton safeguarded by a bracket: the Newton step is
33 * taken only when it lands inside the current bracket and
34 * reduces it, otherwise the step is a bisection
35 *
36 * ARITHMETIC: comparisons, addition, multiplication and division only, so
37 * these instantiate at every backend including exact rationals. Note that a
38 * root is still only located to the caller's tolerance: exact arithmetic makes
39 * the iterates exact, not the answer, since an algebraic root need not be
40 * rational at all.
41 */
42
43#include <cstddef>
44
45#include "line/num/number.h"
46#include "line/util/error.h"
47
48namespace line {
49
50/** Outcome of a scalar solve. */
51template <class T>
52struct RootResult {
53 T root; ///< best estimate of the root
54 T value; ///< f(root)
55 T bracket_width; ///< final |b - a|, zero for the unbracketed Newton
56 unsigned iterations;///< iterations actually performed
57 bool converged; ///< tolerance was met before the iteration cap
58};
59
60/**
61 * Bisection on a bracket with a sign change.
62 *
63 * @param f callable T -> T
64 * @param a,b bracket endpoints, in either order
65 * @param tol absolute width of the final bracket
66 * @param maxiter iteration cap
67 * @throws InputError if f(a) and f(b) have the same sign and neither is a root
68 */
69template <class T, class F>
70RootResult<T> root_bisect(F f, const T& a, const T& b, const T& tol, unsigned maxiter = 200) {
71 const T zero = num_traits<T>::from_int(0);
72 const T two = num_traits<T>::from_int(2);
73 T lo = a < b ? a : b;
74 T hi = a < b ? b : a;
75 T flo = f(lo);
76 T fhi = f(hi);
77
79 r.iterations = 0;
80 r.bracket_width = T(hi - lo);
81 if (flo == zero) {
82 r.root = lo;
83 r.value = flo;
84 r.converged = true;
85 return r;
86 }
87 if (fhi == zero) {
88 r.root = hi;
89 r.value = fhi;
90 r.converged = true;
91 return r;
92 }
93 if ((flo > zero) == (fhi > zero))
94 throw InputError("root_bisect: the bracket endpoints do not straddle a root");
95
96 for (unsigned it = 0; it < maxiter; ++it) {
97 r.iterations = it + 1;
98 const T mid = (lo + hi) / two;
99 const T fm = f(mid);
100 if (fm == zero) {
101 r.root = mid;
102 r.value = fm;
103 r.bracket_width = zero;
104 r.converged = true;
105 return r;
106 }
107 if ((fm > zero) == (flo > zero)) {
108 lo = mid;
109 flo = fm;
110 } else {
111 hi = mid;
112 fhi = fm;
113 }
114 if (T(hi - lo) <= tol) break;
115 }
116 const T mid = (lo + hi) / two;
117 r.root = mid;
118 r.value = f(mid);
119 r.bracket_width = T(hi - lo);
120 r.converged = T(hi - lo) <= tol;
121 return r;
122}
123
124/**
125 * Brent's method on a bracket with a sign change. Falls back to bisection
126 * whenever the interpolated step is not a strict improvement, so the bracket
127 * is never lost.
128 */
129template <class T, class F>
130RootResult<T> root_brent(F f, const T& a0, const T& b0, const T& tol, unsigned maxiter = 200) {
131 const T zero = num_traits<T>::from_int(0);
132 const T two = num_traits<T>::from_int(2);
133 const T three = num_traits<T>::from_int(3);
134 const T half = num_traits<T>::from_rational(1, 2);
135
136 T a = a0;
137 T b = b0;
138 T fa = f(a);
139 T fb = f(b);
140
142 r.iterations = 0;
143 r.bracket_width = num_abs(T(b - a));
144 if (fa == zero) {
145 r.root = a;
146 r.value = fa;
147 r.converged = true;
148 return r;
149 }
150 if (fb == zero) {
151 r.root = b;
152 r.value = fb;
153 r.converged = true;
154 return r;
155 }
156 if ((fa > zero) == (fb > zero))
157 throw InputError("root_brent: the bracket endpoints do not straddle a root");
158
159 if (num_abs(T(fa)) < num_abs(T(fb))) {
160 T t = a;
161 a = b;
162 b = t;
163 t = fa;
164 fa = fb;
165 fb = t;
166 }
167 T c = a;
168 T fc = fa;
169 T d = T(b - a);
170 T e = d;
171 bool used_bisect = true;
172
173 for (unsigned it = 0; it < maxiter; ++it) {
174 r.iterations = it + 1;
175 if ((fb > zero) == (fc > zero)) {
176 c = a;
177 fc = fa;
178 d = T(b - a);
179 e = d;
180 }
181 if (num_abs(T(fc)) < num_abs(T(fb))) {
182 a = b;
183 b = c;
184 c = a;
185 fa = fb;
186 fb = fc;
187 fc = fa;
188 }
189 const T m = half * T(c - b);
190 if (num_abs(T(m)) <= tol || fb == zero) break;
191
192 bool interpolate = false;
193 T s = zero, p = zero, q = zero;
194 if (num_abs(T(e)) >= tol && num_abs(T(fa)) > num_abs(T(fb))) {
195 s = fb / fa;
196 if (a == c) { // secant
197 p = two * m * s;
198 q = num_traits<T>::from_int(1) - s;
199 } else { // inverse quadratic
200 const T qq = fa / fc;
201 const T rr = fb / fc;
202 p = s * (two * m * qq * (qq - rr) - T(b - a) * (rr - num_traits<T>::from_int(1)));
203 q = (qq - num_traits<T>::from_int(1)) * (rr - num_traits<T>::from_int(1)) *
205 }
206 if (p > zero)
207 q = -q;
208 else
209 p = -p;
210 const T lim1 = three * m * q - num_abs(T(tol * q));
211 const T lim2 = num_abs(T(e * q));
212 interpolate = two * p < (lim1 < lim2 ? lim1 : lim2);
213 }
214 e = interpolate ? d : m;
215 d = interpolate ? p / q : m;
216 used_bisect = !interpolate;
217 (void)used_bisect;
218
219 a = b;
220 fa = fb;
221 if (num_abs(T(d)) > tol)
222 b += d;
223 else
224 b += (m > zero ? tol : T(-tol));
225 fb = f(b);
226 }
227 r.root = b;
228 r.value = fb;
229 r.bracket_width = num_abs(T(c - b));
230 r.converged = num_abs(T(half * T(c - b))) <= tol || fb == zero;
231 return r;
232}
233
234/**
235 * Plain Newton from a starting point. The only method here that can find a
236 * root of even multiplicity, where f does not change sign and no bracket
237 * exists; convergence is then linear rather than quadratic.
238 *
239 * @param f callable T -> T
240 * @param df callable T -> T, the derivative
241 * @param x0 starting point of the iteration
242 * @param tol convergence tolerance on the Newton step
243 * @param maxiter iteration cap (default 200)
244 * @throws NumericError if the derivative vanishes at an iterate
245 */
246template <class T, class F, class DF>
247RootResult<T> root_newton(F f, DF df, const T& x0, const T& tol, unsigned maxiter = 200) {
248 const T zero = num_traits<T>::from_int(0);
249 T x = x0;
251 r.iterations = 0;
252 r.bracket_width = zero;
253 r.converged = false;
254 for (unsigned it = 0; it < maxiter; ++it) {
255 r.iterations = it + 1;
256 const T fx = f(x);
257 if (fx == zero) {
258 r.root = x;
259 r.value = fx;
260 r.converged = true;
261 return r;
262 }
263 const T dfx = df(x);
264 if (dfx == zero) throw NumericError("root_newton: the derivative vanished at an iterate");
265 const T step = fx / dfx;
266 x -= step;
267 if (num_abs(T(step)) <= tol) {
268 r.root = x;
269 r.value = f(x);
270 r.converged = true;
271 return r;
272 }
273 }
274 r.root = x;
275 r.value = f(x);
276 return r;
277}
278
279/**
280 * Newton safeguarded by a bracket with a sign change: the Newton step is used
281 * only when it stays inside the bracket and at least halves it, otherwise the
282 * step is a bisection. Never diverges and never leaves the bracket.
283 */
284template <class T, class F, class DF>
285RootResult<T> root_newton_safe(F f, DF df, const T& a0, const T& b0, const T& tol,
286 unsigned maxiter = 200) {
287 const T zero = num_traits<T>::from_int(0);
288 const T two = num_traits<T>::from_int(2);
289 T lo = a0 < b0 ? a0 : b0;
290 T hi = a0 < b0 ? b0 : a0;
291 T flo = f(lo);
292 T fhi = f(hi);
293
295 r.iterations = 0;
296 r.bracket_width = T(hi - lo);
297 if (flo == zero) {
298 r.root = lo;
299 r.value = flo;
300 r.converged = true;
301 return r;
302 }
303 if (fhi == zero) {
304 r.root = hi;
305 r.value = fhi;
306 r.converged = true;
307 return r;
308 }
309 if ((flo > zero) == (fhi > zero))
310 throw InputError("root_newton_safe: the bracket endpoints do not straddle a root");
311
312 T x = (lo + hi) / two;
313 for (unsigned it = 0; it < maxiter; ++it) {
314 r.iterations = it + 1;
315 const T fx = f(x);
316 if (fx == zero) {
317 r.root = x;
318 r.value = fx;
319 r.bracket_width = zero;
320 r.converged = true;
321 return r;
322 }
323 if ((fx > zero) == (flo > zero)) {
324 lo = x;
325 flo = fx;
326 } else {
327 hi = x;
328 fhi = fx;
329 }
330 const T width = T(hi - lo);
331 if (width <= tol) break;
332
333 const T dfx = df(x);
334 bool take_newton = false;
335 T xn = x;
336 if (!(dfx == zero)) {
337 xn = x - fx / dfx;
338 take_newton = xn > lo && xn < hi;
339 }
340 x = take_newton ? xn : (lo + hi) / two;
341 }
342 r.root = x;
343 r.value = f(x);
344 r.bracket_width = T(hi - lo);
345 r.converged = T(hi - lo) <= tol;
346 return r;
347}
348
349/**
350 * Expand a bracket to the right until f changes sign, doubling the upper end.
351 * Used by the TTL cache fixed points, where the residual is monotone in the
352 * characteristic time but no upper bound is known a priori.
353 *
354 * @throws NumericError if no sign change is found before the cap
355 */
356template <class T, class F>
357void bracket_expand(F f, const T& a, T& b, unsigned maxdoubling = 200) {
358 const T zero = num_traits<T>::from_int(0);
359 const T two = num_traits<T>::from_int(2);
360 const T fa = f(a);
361 if (fa == zero) {
362 b = a;
363 return;
364 }
365 for (unsigned it = 0; it < maxdoubling; ++it) {
366 const T fb = f(b);
367 if (fb == zero || (fb > zero) != (fa > zero)) return;
368 b *= two;
369 }
370 throw NumericError("bracket_expand: no sign change found while expanding the bracket");
371}
372
373} // namespace line
374
375#endif // LINE_UTIL_ROOTFIND_H
InputError(const std::string &what)
Definition error.h:39
NumericError(const std::string &what)
Definition error.h:45
The exception types the port throws.
T num_abs(const T &v)
Definition number.h:172
RootResult< T > root_newton_safe(F f, DF df, const T &a0, const T &b0, const T &tol, unsigned maxiter=200)
Newton safeguarded by a bracket with a sign change: the Newton step is used only when it stays inside...
Definition rootfind.h:285
RootResult< T > root_bisect(F f, const T &a, const T &b, const T &tol, unsigned maxiter=200)
Bisection on a bracket with a sign change.
Definition rootfind.h:70
RootResult< T > root_brent(F f, const T &a0, const T &b0, const T &tol, unsigned maxiter=200)
Brent's method on a bracket with a sign change.
Definition rootfind.h:130
void bracket_expand(F f, const T &a, T &b, unsigned maxdoubling=200)
Expand a bracket to the right until f changes sign, doubling the upper end.
Definition rootfind.h:357
RootResult< T > root_newton(F f, DF df, const T &x0, const T &tol, unsigned maxiter=200)
Plain Newton from a starting point.
Definition rootfind.h:247
Number-type abstraction for the templated API port.
Outcome of a scalar solve.
Definition rootfind.h:52
bool converged
tolerance was met before the iteration cap
Definition rootfind.h:57
T root
best estimate of the root
Definition rootfind.h:53
T bracket_width
final |b - a|, zero for the unbracketed Newton
Definition rootfind.h:55
T value
f(root)
Definition rootfind.h:54
unsigned iterations
iterations actually performed
Definition rootfind.h:56