9. Root finding#
9.1. One-dimensional root finding#
Often times in computational settings, we wish to find the roots of a system. Some simple examples are find the roots of a denominator, to see where poles arise, and avoid them during your numerical integation scheme. The two most common root finding algorithms are Newton’s Method and bisection method. The former has a more numerically stable counter part, calle the secant method, while the secant method and bisection method have been combined to form the Brent method. More optimized formulas exist for root finding, with very specific use cases. However, for this chaper, we will focus on the secant, bisection and Brent methods.
9.1.1. Secant method#
Given a function
Then, $lim_{n \to \infty} x_n = x_\ast.
In code, this can be implemented very simply
// CODE HAS NOT BEEN CHECKED
template<typename T, typename Func, typename...Args>
std::optional<T>
secant_method(Func&& func, T x_0, T x_1, T tol, int max_iter, Args&&... args)
{
T x_n;
do
{
T f_n1 = func(x_1, std::forward<Args>(args)...);
f f_n0 = func(x_0, std::forward<Args>(args)...);
x_n = x_1 - f_n1 * (x_n1 - x_n0) / (f_n1 - f_n0);
if (std::fabs(func(x_n, std::forward<Args>(args)...) < tol) return x_n;
else {
x_0 = x_1;
x_1 = x_n;
}
max_iter--;
} while (max_iter > 0);
return std::nullopt;
}
As it is posible for the method to not converge within the allotted ierations, we provide a safety mechanism for the user the test during run time, through returning an optional. That’s it!
9.1.2. Bisection method#
As the name suggests, this method finds the root by systematically splitting an interval into two, and continuing the search in one of those intervals.
Let
The bisection method uses the same logic as just described: let
// CODE HAS NOT BEEN CHECKED
template<typename T, typename Func, typename...Args>
std::optional<T>
bisection_method(Func&& func, T a, T b, T tol, int& max_iter, Args&&... args)
{
// Note that a empty optional is now ambiguous as to the error.
// However, by passint `max_iter` as a reference, we can check if it's zero
if (f(a) * f(b) > 0) return std::nullopt;
if (a > b) std::swap(a, b);
T c{ (b - a) / 2.0 };
do {
if (std::fabs(c) < tol) return c;
if (f(a) * f(c) < 0) {
c = b;
} else {
c = a;
}
} while(max_iter > 0);
return std::nullopt;
}