ZBREN Example — Brent’s Method for Scalar Root Finding

This example uses nonlinearequations.zero_brent() (IMSL ZBREN) to locate roots of two classic test functions using Brent’s bracketing method:

Problem 1 — cubic polynomial:

\[f(x) = x^3 - 2x - 5, \quad x \in [2, 3]\]

Root: \(x^* \approx 2.0946\).

Problem 2 — cosine fixed-point equation (Dottie number):

\[f(x) = \cos(x) - x, \quad x \in [0, 1]\]

Root: \(x^* \approx 0.7391\).

Brent’s method combines bisection, secant, and inverse-quadratic interpolation to guarantee convergence whenever a sign change is bracketed, achieving near-quadratic speed in smooth regions.

Example Code

"""IMSL ZBREN example: Brent's method for scalar root finding.

Demonstrates :func:`nonlinearequations.zero_brent` (IMSL ZBREN) on two
classic test cases:

  1. f(x) = x^3 - 2x - 5  on [2, 3]   (root ≈ 2.0946)
  2. f(x) = cos(x) - x    on [0, 1]   (Dottie number ≈ 0.7391)

Outputs:
- Table printed to stdout
- SVG plot of both function curves with roots marked
- Saved to test_output/example_imsl_zbren.svg
"""

from __future__ import annotations

import math
from pathlib import Path
from typing import Dict

import matplotlib.pyplot as plt
import numpy as np

from nonlinearequations import zero_brent


def run_demo_imsl_zbren() -> Dict[str, object]:
    """Run IMSL ZBREN example: Brent's method for two scalar root problems.

    Finds a root of x^3 - 2x - 5 on [2, 3] and a root of cos(x) - x on
    [0, 1] using Brent's bracketing method.

    Args:
        None

    Returns:
        Dict[str, object]: Result dict with keys ``root1`` (float),
            ``root2`` (float), ``n_fev1`` (int), ``n_fev2`` (int), and
            ``plot_path`` (str).
    """

    def f1(x: float) -> float:
        """Cubic polynomial f(x) = x^3 - 2x - 5.

        Args:
            x (float): Input value.

        Returns:
            float: f(x).
        """
        return x**3 - 2.0 * x - 5.0

    def f2(x: float) -> float:
        """Fixed-point equation f(x) = cos(x) - x.

        Args:
            x (float): Input value.

        Returns:
            float: f(x).
        """
        return math.cos(x) - x

    result1 = zero_brent(f1, 2.0, 3.0)
    result2 = zero_brent(f2, 0.0, 1.0)

    root1 = float(result1.x[0])
    root2 = float(result2.x[0])

    print("\nIMSL ZBREN Example: Brent's Method for Scalar Root Finding")
    print("-" * 60)
    print(f"{'Parameter':<35} {'Value':>20}")
    print("-" * 60)
    print("Problem 1: f(x) = x^3 - 2x - 5  on [2, 3]")
    print(f"{'  Root':<35} {root1:>20.10f}")
    print(f"{'  |f(root)|':<35} {result1.fval:>20.2e}")
    print(f"{'  Function evals':<35} {result1.n_fev:>20}")
    print(f"{'  Converged':<35} {str(result1.success):>20}")
    print()
    print("Problem 2: f(x) = cos(x) - x  on [0, 1]")
    print(f"{'  Root (Dottie number)':<35} {root2:>20.10f}")
    print(f"{'  |f(root)|':<35} {result2.fval:>20.2e}")
    print(f"{'  Function evals':<35} {result2.n_fev:>20}")
    print(f"{'  Converged':<35} {str(result2.success):>20}")
    print("-" * 60)

    # ------------------------------------------------------------------
    # Plot: two subplots, one per problem
    # ------------------------------------------------------------------
    output_dir = Path("test_output")
    output_dir.mkdir(parents=True, exist_ok=True)
    plot_path = output_dir / "example_imsl_zbren.svg"

    fig, axes = plt.subplots(1, 2, figsize=(12, 5))

    # ── Panel 1: cubic polynomial ──────────────────────────────────────
    x1 = np.linspace(1.5, 3.5, 400)
    y1 = x1**3 - 2.0 * x1 - 5.0
    ax1 = axes[0]
    ax1.plot(x1, y1, color="#0e7490", linewidth=2.0, label=r"$f(x) = x^3 - 2x - 5$")
    ax1.axhline(0, color="#888888", linewidth=0.8, linestyle="--")
    ax1.axvspan(2.0, 3.0, alpha=0.08, color="#0e7490", label="Search bracket [2, 3]")
    ax1.scatter([root1], [0.0], color="#d62728", s=100, zorder=5, label=f"Root ≈ {root1:.6f}")
    ax1.set_xlabel("x")
    ax1.set_ylabel("f(x)")
    ax1.set_title("Brent: cubic polynomial")
    ax1.legend(fontsize=8)
    ax1.grid(True, alpha=0.25)

    # ── Panel 2: cos(x) - x ───────────────────────────────────────────
    x2 = np.linspace(-0.2, 1.4, 400)
    y2 = np.cos(x2) - x2
    ax2 = axes[1]
    ax2.plot(x2, y2, color="#0891b2", linewidth=2.0, label=r"$f(x) = \cos(x) - x$")
    ax2.axhline(0, color="#888888", linewidth=0.8, linestyle="--")
    ax2.axvspan(0.0, 1.0, alpha=0.08, color="#0891b2", label="Search bracket [0, 1]")
    ax2.scatter([root2], [0.0], color="#d62728", s=100, zorder=5, label=f"Root ≈ {root2:.6f}")
    ax2.set_xlabel("x")
    ax2.set_ylabel("f(x)")
    ax2.set_title("Brent: cos(x) − x  (Dottie number)")
    ax2.legend(fontsize=8)
    ax2.grid(True, alpha=0.25)

    fig.suptitle("IMSL ZBREN: Brent's Method", fontsize=13, fontweight="bold")
    fig.tight_layout()
    fig.savefig(plot_path, format="svg")
    plt.close(fig)

    return {
        "root1": root1,
        "root2": root2,
        "n_fev1": result1.n_fev,
        "n_fev2": result2.n_fev,
        "plot_path": str(plot_path),
    }


if __name__ == "__main__":
    run_demo_imsl_zbren()

Plot Output

Two function curves with bracketing regions and root markers

Left: cubic \(x^3 - 2x - 5\) on the bracket \([2,3]\). Right: \(\cos(x) - x\) on \([0,1]\). Both roots are marked with red dots; the shaded regions indicate the search brackets.

Console Output

IMSL ZBREN Example: Brent's Method for Scalar Root Finding
------------------------------------------------------------
Parameter                                          Value
------------------------------------------------------------
Problem 1: f(x) = x^3 - 2x - 5  on [2, 3]
  Root                                      2.0945514815
  |f(root)|                                     3.55e-15
  Function evals                                      10
  Converged                                         True

Problem 2: f(x) = cos(x) - x  on [0, 1]
  Root (Dottie number)                      0.7390851346
  |f(root)|                                     2.26e-09
  Function evals                                       9
  Converged                                         True
------------------------------------------------------------