ZUNI Example — Univariate Zero-Finding

This example uses Brent’s bracketing method (IMSL ZUNI) to find the zeros of two functions:

  1. \(f(x) = x^3 - x - 2\) on \([1, 2]\) → root ≈ 1.5214

  2. \(f(x) = \cos(x)\) on \([1, 2]\) → root ≈ \(\pi/2 \approx 1.5708\)

Example Code

"""IMSL ZUNI example: zero of a univariate function using Brent's method.

Demonstrates two zero-finding problems:
1. f(x) = x^3 - x - 2 on [1, 2] → root ≈ 1.5214
2. f(x) = cos(x) on [1, 2] → root ≈ π/2 ≈ 1.5708

Outputs:
- Table printed to stdout
- SVG plot of both functions with roots marked
- Saved to test_output/demo_imsl_zuni.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_univariate


def run_demo_imsl_zuni() -> Dict[str, object]:
    """Run IMSL ZUNI example: zero of x^3-x-2 and cos(x).

    Args:
        None

    Returns:
        Dict[str, object]: Result dict with keys ``root1`` (float),
            ``root2`` (float), and ``plot_path`` (str).
    """
    def f1(x: float) -> float:
        return x**3 - x - 2.0

    def f2(x: float) -> float:
        return math.cos(x)

    r1 = zero_univariate(f1, 1.0, 2.0)
    r2 = zero_univariate(f2, 1.0, 2.0)

    print("\nIMSL ZUNI Example: Zero of Univariate Functions")
    print("-" * 68)
    print(f"{'Function':<22} {'Bracket':>12} {'Root':>14} {'|f(root)|':>12} {'n_fev':>6}")
    print("-" * 68)
    print(f"{'x^3 - x - 2':<22} {'[1, 2]':>12} {r1.x[0]:>14.10f} {r1.fval:>12.2e} {r1.n_fev:>6}")
    print(f"{'cos(x)':<22} {'[1, 2]':>12} {r2.x[0]:>14.10f} {r2.fval:>12.2e} {r2.n_fev:>6}")
    print("-" * 68)
    print(f"{'Expected (f1)':<22} {'':>12} {1.5213797068:>14.10f}")
    print(f"{'Expected (f2 = π/2)':<22} {'':>12} {math.pi/2:>14.10f}")
    print("-" * 68)

    output_dir = Path("test_output")
    output_dir.mkdir(parents=True, exist_ok=True)
    plot_path = output_dir / "demo_imsl_zuni.svg"

    x_vals = np.linspace(0.8, 2.2, 400)
    y1 = np.array([f1(xi) for xi in x_vals])
    y2 = np.array([f2(xi) for xi in x_vals])

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

    ax1 = axes[0]
    ax1.plot(x_vals, y1, color="#0e7490", linewidth=2.0, label=r"$f(x) = x^3 - x - 2$")
    ax1.axhline(0, color="#888888", linewidth=0.8)
    ax1.axvline(r1.x[0], color="#d62728", linestyle="--", linewidth=1.5, label=f"Root≈{r1.x[0]:.6f}")
    ax1.scatter([r1.x[0]], [f1(r1.x[0])], color="#d62728", s=60, zorder=5)
    ax1.set_xlabel("x")
    ax1.set_ylabel("f(x)")
    ax1.set_title("Zero of $x^3 - x - 2$")
    ax1.legend(fontsize=9)
    ax1.grid(True, alpha=0.25)

    ax2 = axes[1]
    ax2.plot(x_vals, y2, color="#0891b2", linewidth=2.0, label=r"$f(x) = \cos(x)$")
    ax2.axhline(0, color="#888888", linewidth=0.8)
    ax2.axvline(r2.x[0], color="#d62728", linestyle="--", linewidth=1.5, label=f"Root≈{r2.x[0]:.6f} (π/2)")
    ax2.scatter([r2.x[0]], [f2(r2.x[0])], color="#d62728", s=60, zorder=5)
    ax2.set_xlabel("x")
    ax2.set_ylabel("f(x)")
    ax2.set_title(r"Zero of $\cos(x)$")
    ax2.legend(fontsize=9)
    ax2.grid(True, alpha=0.25)

    fig.suptitle("IMSL ZUNI: Univariate Zero-Finding", fontsize=13)
    fig.tight_layout()
    fig.savefig(plot_path, format="svg")
    plt.close(fig)

    return {"root1": float(r1.x[0]), "root2": float(r2.x[0]), "plot_path": str(plot_path)}


if __name__ == "__main__":
    run_demo_imsl_zuni()

Plot Output

Two side-by-side plots showing the zero locations of x^3-x-2 and cos(x)

Left: zero of \(x^3 - x - 2\) at \(x \approx 1.5214\). Right: zero of \(\cos(x)\) at \(x \approx \pi/2\). Both roots are marked with red dashed vertical lines.

Console Output

IMSL ZUNI Example: Zero of Univariate Functions
--------------------------------------------------------------------
Function                    Bracket           Root    |f(root)|  n_fev
--------------------------------------------------------------------
x^3 - x - 2                  [1, 2]   1.5213797067     4.90e-10     11
cos(x)                       [1, 2]   1.5707963258     1.02e-09      8
--------------------------------------------------------------------
Expected (f1)                         1.5213797068
Expected (f2 = π/2)                   1.5707963268
--------------------------------------------------------------------