Roots.jl

repository·master·Indexed 19 days ago

https://github.com/juliamath/roots.jl

A collection of routines for finding roots (zeros) of scalar functions of a single real variable using floating-point math. It provides the `find_zero` interface for single roots and `find_zeros` for multiple roots within a range. The library supports a wide array of algorithms, including bracketing methods (Bisection, Brent), derivative-free methods (Order0 through Order16, Secant, Steffensen), and derivative-based methods (Newton, Halley, Schroder). It also includes a CommonSolve interface via `ZeroProblem` for compatibility with the SciML ecosystem.

Tokens
12.2K
Snippets
34
Records
51
Agent score
62%

What's inside Roots.jl

  1. Understand rates of convergence for root finding methods

    master

    Root finding methods are characterized by their order of convergence $q$ (where $e_{i+1} \approx e_i^q$) and their asymptotic efficiency $q^{1/p}$, where $p$ is the number of function evaluations per step.

    When choosing a method, consider the trade-off between the convergence rate and the number of function evaluations (F evals) required per iteration. For example, Newton's method has quadratic convergence ($q=2$) but requires 2 function evaluations, whereas the Secant method has an order of $\varphi \approx 1.618$ but only requires 1 evaluation per step.

    Common method types include:

    • Classical: Methods like Newton, Halley, and SuperHalley that typically use derivatives.
    • Derivative Free: Methods like Secant, Steffensen, and Order16 that do not require derivatives.
    • Bracketing: Methods like Brent, ITP, and Ridders that work within a known interval $[a, b]$.
    • Robust: Methods like Schroder or Thukral designed for stability.
    | Type            | Method                       | Order                  | F evals | Asymptotic efficiency                 |
    |:--------------- | :--------------------------- | :--------------------- | :------ | :------------------------------------ |
    | Hybrid          | Order0                       |                        |         | ``\approx 1.618\dots``               |
    | Derivative Free | Secant                       | ``\varphi=1.618\dots`` | ``1``   | ``1.618\dots``                       |
    | Derivative Free | Steffensen                   | ``2``                  | ``2``   | ``1.414\dots``                       |
    | Derivative Free | Order5                       | ``5``                  | ``4``   | ``1.495\dots``                       |
    | Derivative Free | Order8                       | ``8``                  | ``4``   | ``1.681\dots``                       |
    | Derivative Free | Order16                      | ``16``                 | ``5``   | ``1.718\dots``                       |
    | Classical       | Newton                       | ``2``                  | ``2``   | ``1.414\dots``                       |
    | Classical       | Halley                       | ``3``                  | ``3``   | ``1.442\dots``                       |
    | Classical       | QuadraticInverse             | ``3``                  | ``3``   | ``1.442\dots``                       |
    | Classical       | ChebyshevLike                 | ``3``                  | ``3``   | ``1.442\dots``                       |
    | Classical       | SuperHalley                  | ``3``                  | ``3``   | ``1.442\dots``                       |
    | MultiStep       | LithBoonkkampIJzerman{S,D}    | ``p^s=\sum p^k(d+\sigma_k)`` | ``D+1`` | varies, ``1.92\dots`` max            |
    | Bracketing      | BisectionExact               | ``1``                  | ``1``   | ``1``                                |
    | Bracketing      | A42                          | ``(2 + 7^{1/2})``      | ``3,4`` |``(2 + 7^{1/2})^{1/3} = 1.6686\dots`` |
    | Bracketing      | AlefeldPotraShi              |                        | ``3,4`` | ``1.618\dots``                       |
    | Bracketing      | Brent                        | ``\leq 1.89\dots``     | ``1``   | ``\leq 1.89\dots``                  |
    | Bracketing      | ITP                          | ``\leq \varphi``       | ``1``   | ``\leq \varphi``                    |
    | Bracketing      | Ridders                      | ``1.83\dots``           | ``2``   | ``1.225\dots``                      |
    | Bracketing      | RegularFalsi{:classic}        | ``1``                  | ``1``   | ``1``                                |
    | Bracketing      | RegularFalsi{:Illinois}       | ``1.442\dots``         | ``1``   | ``1.442\dots``                       |
    | Bracketing      | RegulaFalsi{:AndersonBjork}  | ``1.681\dots``         | ``1``   | ``1.681\dots``                       |
    | Bracketing      | RegulaFalsi{:Ford4}          | ``1.681\dots``         | ``1``   | ``1.681\dots``                       |
    | Bracketing      | ModAB                        | ``≈1.7\dots``          | ``1``   | ``1.7\dots``                         |
    | Bracketing      | LithBoonkkampIJzermanBracket | ``2.91``               | ``3``   | ``1.427\dots``                       |
    | Robust          | King                         | ``\varphi=1.618\dots`` | ``2``   | ``1.272\dots``                       |
    | Robust          | Esser                        | ``2``                  | ``3``   | ``1.259\dots``                       |
    | Robust          | Schroder                     | ``2``                  | ``3``   | ``1.259\dots``                       |
    | Robust          | Thukral3                     | ``3``                  | ``4``   | ``1.316\dots``                       |
    | Robust          | Thukral4                     | ``4``                  | ``5``   | ``1.319\dots``                       |
    | Robust          | Thukral5                     | ``5``                  | ``6``   | ``1.307\dots``                       |
  2. Choose between derivative-based and derivative-free methods

    master

    When selecting an algorithm, consider whether you can provide derivatives of your function:

    Classical methods (Derivative-based)

    These require the function and its derivatives (e.g., Newton, Halley). They are often faster (higher order of convergence) but require more user effort to provide derivatives.

    Derivative-free methods

    These only require the function itself (e.g., Secant, Steffensen).

    • Secant method: Uses the slope of a secant line. It is the default method used by find_zero when a single initial point is provided. It requires only one new function call per step.
    • Steffensen's method: A quadratically converging derivative-free method, but it requires more function calls per step and a good initial guess.
  3. Understand the difference between simple and non-simple zeros

    master

    The convergence behavior of many algorithms depends on whether the zero is "simple" or "non-simple":

    • Simple Zero: A value $\alpha$ where $f(x) = (x-\alpha) \cdot g(x)$ and $g(\alpha) \neq 0$.
    • Non-simple Zero: A value where the root has a multiplicity greater than 1 (e.g., $f(x) = (x-\alpha)^{1+\beta} \cdot g(x)$ with $\beta > 0$).

    Many classical methods (like Newton's method) exhibit quadratic convergence near simple zeros but may lose this property or require more function calls per step near non-simple zeros. Specialized methods like Roots.Schroder or Roots.AbstractThukralBMethod are designed to handle non-simple zeros more efficiently.

  4. Configure convergence tolerances

    master

    Convergence is typically decided when $|f(x_n)| ext{ is within tolerance}$ or when the change in $x$ is sufficiently small ($x_n ext{ vs } x_{n-1}$).

    Available Keyword Arguments:

    • atol: Absolute tolerance for function value.
    • rtol: Relative tolerance for function value.
    • xatol: Absolute tolerance for the change in $x$.
    • xrtol: Relative tolerance for the change in $x$.
    • maxevals: Maximum number of function evaluations allowed.

    Convergence Logic:

    • The algorithm stops if it hits NaN, Inf, or maxevals.
    • If the relaxed convergence criteria are met at the stop point, the zero is returned; otherwise, a Roots.ConvergenceFailed error is thrown.
  5. Use bracketing methods for guaranteed convergence

    master

    Bracketing methods require an interval $[a, b]$ such that $f(a)$ and $f(b)$ have different signs ($f(a) \cdot f(b) < 0$).

    • Bisection: The simplest bracketing method. It is slow but guaranteed to converge to a zero or a zero-crossing (even for non-continuous functions).
    • Other Bracketing Methods: Methods like Roots.Brent, Roots.Ridders, and Roots.AlefeldPotraShi exploit the shape of the function to be significantly more efficient than basic bisection while maintaining convergence guarantees.
  6. Understand floating point nuances in root finding

    master

    When using Float64, mathematical zeros may not behave as expected due to precision limits:

    • Multiple Zeros: A single mathematical zero might appear as multiple floating-point zeros due to evaluation differences (e.g., iszero(f(x)) being true for multiple adjacent values).
    • Sign Changes: A mathematical function that changes sign might not satisfy f(x) * f(nextfloat(x)) < 0 exactly due to rounding. An exact zero is identified if iszero(f(x)) is true OR if there is a sign change between adjacent floating-point numbers.
    • Residual Magnitude: At a floating-point zero $x$, the function value $f(x)$ is not necessarily $0$. It is typically on the scale of $f'(x) \cdot |x| \cdot \epsilon$, where $\epsilon$ is the machine epsilon.

    Example: Verifying sign changes near a zero

    f(x) = exp(x) - x^4;
    F(x) = sign(f(x));
    x = 8.613169456441398
    F(prevfloat(x)), F(x), F(nextfloat(x))
    # (-1.0, -1.0, 1.0)
  7. Configure tolerances for find_zero

    master

    Root finding involves balancing absolute and relative tolerances. Because errors in function evaluation can be proportional to the size of $x$, a robust check often uses both:

    abs(f(x)) < max(atol, abs(x) * rtol)

    Relative Tolerance Pitfalls

    For functions with sublinear growth, extremely large values of $x$ might be incorrectly identified as zeros because the relative tolerance $|x| \cdot \epsilon$ becomes larger than the function value $f(x)$.

    Example of misidentified zero with Thukral8:

    find_zero(cbrt, 1, Roots.Thukral8())
    # 1.725042287244107e23

    To avoid this, you can set the relative tolerance to $0$ and use a more generous absolute tolerance (e.g., sqrt(eps()) or 1e-8).

    Roots.jl Tolerance Strategy

    Roots.jl uses a dual approach for faster algorithms:

    1. Tight Check: Checks if the difference between the last two $x_n$ values is small AND the residual $f(x_n)$ is small with a tight tolerance.
    2. Relaxed Check: If the $x$ values are close AND the function value is close to zero with a relaxed tolerance, an approximate zero is declared.
  8. Choose a bracketing method for known intervals

    master

    If you have a bracketing interval $[a, b]$ where $f(a)$ and $f(b)$ have opposite signs, you can use various bracketing methods.

    • Default: Bisection is the default for basic floating-point types because it is robust.
    • High Performance: Roots.ModAB, A42, and AlefeldPotraShi typically converge in fewer iterations and are more performant than standard bisection.
    • Other available methods: A42, AlefeldPotraShi, Roots.Brent, Roots.Chandrapatlu, Roots.ITP, Roots.Ridders, Roots.ModAB, various flavors of FalsePosition, and RegularFalsi.
  9. Use the problem-algorithm-solve interface

    master

    Following the pattern used in DifferentialEquations.jl, Roots provides a structured way to solve problems using ZeroProblem and the CommonSolve.jl interface. This is useful for separating the problem definition from the solver choice.

    1. Define the problem: Create a ZeroProblem(f, x0) where f is the function and x0 is the initial guess or bracket.
    2. Solve the problem: Use solve(Z, M) where Z is the problem and M is the algorithm (e.g., Secant(), Bisection(), Order2()).

    This interface supports parameterized functions by passing parameters as positional arguments or keyword arguments to solve.

    using Roots
    
    f(x) = sin(x)
    x0 = (3, 4)
    M = Secant()
    
    # 1. Setup the problem
    Z = ZeroProblem(f, x0)
    
    # 2. Solve with a specific algorithm
    result = solve(Z, M)
    
    # Solving parameterized functions
    g(x, p=1) = cos(x) - x/p
    Z_param = ZeroProblem(g, (0.0, pi/2))
    solve(Z_param, Secant(), 2) # p=2 as positional argument
    solve(Z_param, Bisection(); p=3, xatol=1/16) # p=3 via keyword
  10. Choose a method requiring derivatives

    master

    For functions where derivatives are available, you can use specialized methods:

    • Classical: Roots.Newton and Roots.Halley.
    • Multiplicity-free: Roots.Schroder (quadratic), Roots.ThukralXB (where X is 2, 3, 4, or 5), and Roots.QuadraticInverse, Roots.ChebyshevLike, or Roots.SuperHalley.
    • Derivative count: In ThukralXB, the X denotes the number of derivatives that must be specified.
    • Step/Derivative controlled: Roots.LithBoonkkampIJzerman{S,D} methods where S is the number of steps remembered and D is the number of derivatives used.
  11. Use hybrid methods for robust root finding

    master

    Hybrid methods attempt to combine the speed of non-bracketing methods with the reliability of bracketing methods. They start with a non-bracketing approach and switch to a bracketing method if a bracket is encountered.

    This is the default strategy used by find_zero(f, a) when a single initial starting point is provided.

  12. Choose a derivative-free method

    master

    If you do not want to provide derivatives, you can use derivative-free methods. These are categorized by their approximate order of convergence:

    • Order0: The default and most robust method. It finishes by using a bracketing method if a bracket is encountered.
    • Order1: The secant method.
    • Order2: The Steffensen method.
    • Higher Orders: Order5, Order8, and Order16 promise faster convergence, though they may not always result in fewer function calls than Order1 or Order2.
    • Multiplicity-independent: Roots.Order1B and Roots.Order2B are superlinear and quadratically converging methods that work independently of the multiplicity of the zero.
    • Other: Roots.Sidi is a family of methods.