Skip to main content

Hybrid contour-integral + Ehrlich-Aberth root-finder for analytic and transcendental functions on unbounded regions.

Project description

aberth-hybrid

Hybrid contour-integral + Ehrlich-Aberth pipeline for finding all zeros of an analytic function (or all eigenvalues of a matrix pencil) in a region of the complex plane. Handles polynomials, transcendentals, bounded regions, unbounded strips, and non-normal matrix nonlinear eigenvalue problems (NEPs).

Reference implementation for the preprint "A Hybrid Contour-Integral / Ehrlich-Aberth Root-Finder for Analytic and Transcendental Functions on Unbounded Regions" (arXiv: tbd).

Highlights

  • 2.2× faster and 58× tighter median residual than skzeros on the Bowhay-Nakatsukasa-Zaid combustion DDE benchmark (Python vs Python, same 2024 Apple M-series laptop). Julia port is 2.6× faster than skzeros.
  • 17× to 415× faster than cxroots on scalar analytic root-finding in a disk.
  • The only tool that solves the Hayes DDE unbounded strip in a single call (all 86 Lambert-$W$ zeros in $|\mathrm{Im}(z)| < 265$, at machine precision).
  • 10⁷× better separation resolution than the standard Hermitian Rayleigh polish on close-eigenvalue non-normal matrix pencils, from the biorthogonal block Aberth iteration.
  • Passes the standard NLEVP benchmark suite at machine-precision backward error (12 canonical problems from polynomial to transcendental).

Install

pip install aberth-hybrid          # once published to PyPI
# or, faster:
uv pip install aberth-hybrid

From source (for contributors):

git clone https://github.com/peppe-tringali/aberth-hybrid
cd aberth-hybrid
uv sync --extra dev
uv run pytest                      # 27 unit tests

Three canonical examples

Example 1: scalar analytic function in a bounded disk

import numpy as np
from aberth_hybrid import hybrid_solve

# Zeros of z + exp(-z) inside the disk |z| < 8
def f(z):  return z + np.exp(-z)
def fp(z): return 1.0 - np.exp(-z)

result = hybrid_solve(f, fp, center=0+0j, radius=8.0, tol=1e-13)
print(f"{result.n_zeros} zeros recovered")
for z in sorted(result.roots, key=lambda z: z.imag):
    print(f"  {z:+.6f}   |f(z)| = {abs(f(z)):.2e}")

Expected output:

4 zeros recovered
  -2.062278-7.588631j   |f(z)| = 9.93e-16
  -0.318132-1.337236j   |f(z)| = 2.29e-16
  -0.318132+1.337236j   |f(z)| = 2.29e-16
  -2.062278+7.588631j   |f(z)| = 9.93e-16

Example 2: unbounded strip (Hayes DDE)

import numpy as np
from aberth_hybrid import Window, solve_on_windows

def f(z):  return z + np.exp(-z)      # Hayes DDE characteristic function
def fp(z): return 1.0 - np.exp(-z)

# Cover a vertical strip along the imaginary axis with overlapping disks
windows = [Window(center=complex(-2.5, k * 8.8), radius=5.5)
           for k in range(-30, 31)]
result = solve_on_windows(f, fp, windows, tol=1e-13)

print(f"{len(result.roots)} zeros in the strip |Im(z)| < 265")

Expected: 86 zeros, each at machine precision, all Lambert-$W$ branches.

Example 3: matrix nonlinear eigenvalue problem

import numpy as np
from aberth_hybrid.beyn import beyn_extract
from aberth_hybrid.block_aberth import block_aberth_from_beyn

# Simple 3x3 matrix DDE: T(lam) = -lam*I + A + B*exp(-lam)
A = np.diag([-2.0, -3.0, -5.0]) + 0j
B = np.diag([1.5, 2.0, 1.2]) + 0j
def T(z):
    return -complex(z) * np.eye(3, dtype=complex) + A + B * np.exp(-complex(z))
def T_prime(z):
    return -np.eye(3, dtype=complex) - B * np.exp(-complex(z))

# Beyn extraction on a disk
beyn = beyn_extract(T, center=0+0j, radius=3.0, n_quad=1024,
                    n_probes=10, n_moments=10,
                    return_left_eigenvectors=True)

# Refine with prewarm + biorthogonal block Aberth
result = block_aberth_from_beyn(T, T_prime, beyn, tol=1e-14,
                                 prewarm_steps=3)
print(f"{len(result.eigenvalues)} eigenvalues:")
for lam in sorted(result.eigenvalues, key=lambda z: (z.real, z.imag)):
    print(f"  {lam:+.6f}")

For a walkthrough of the theory (7-layer pipeline, prewarm theorem, certificate), see the companion blog series. For a fuller worked notebook, open docs/getting_started.ipynb.

Public API

Top-level (from aberth_hybrid import ...):

Symbol Purpose
hybrid_solve(f, fp, center, radius, ...) Full pipeline on one disk
Window(center, radius) Disk descriptor for windowing
solve_on_windows(f, fp, windows, ...) Pipeline on multiple disks with dedup
solve_adaptive(f, fp, root_window, ...) Adaptive 2×2 subdivision
aberth(f, fp, initial_guesses, ...) Bare Ehrlich-Aberth iteration
zero_count_circle(f, fp, center, radius, n_quad) Argument principle count
hankel_recover_circle_svd(...) SVD-projected Hankel bootstrap

Matrix NEPs (from aberth_hybrid.beyn import ... and from aberth_hybrid.block_aberth import ...):

Symbol Purpose
beyn_extract(T, center, radius, ...) Beyn contour extraction with block moments
block_aberth(T, Tp, lam, V, W, ...) Biorthogonal block Aberth iteration
block_aberth_from_beyn(T, Tp, beyn_result, ...) Composed extract + polish
prewarm_eigenvectors(T, lam, V, W, steps) Fixed-shift two-sided inverse iteration
prewarm_certificate(T, Tp, lam, V, W) A-posteriori check of Theorem 4.11

Parameter continuation (from aberth_hybrid.continuation import ...):

Symbol Purpose
continuation_sweep(T, Tp, params, contour, ...) Certificate-gated warm-start sweep
matrix_pencil_zero_count(T, Tp, center, radius, n_quad) Trace-form count

Julia port

A performance-focused Julia translation lives in julia_port/. It matches the Python implementation's algorithm and runs the combustion DDE benchmark in 9.8 ms (median of 5 runs), 17% faster than the Python reference. To run:

cd julia_port
julia --project=. test/runtests.jl              # 12 unit tests
julia --project=. benchmarks/combustion_dde.jl  # combustion benchmark

Benchmarks

Reproducible benchmarks under benchmarks/:

uv run python -m benchmarks.nlevp.run_all                            # NLEVP suite
uv run python -m benchmarks.head_to_head.scalar_vs_cxroots           # vs cxroots
uv run python -m benchmarks.head_to_head.matrix_vs_neppack           # vs NEP-PACK
uv run python -m benchmarks.head_to_head.combustion_vs_skzeros       # vs skzeros
uv run python -m benchmarks.head_to_head.vs_big_math_libraries       # vs numpy/scipy
bash benchmarks/run_full_suite.sh                                    # everything

Or in Julia:

julia --project=julia_port julia_port/benchmarks/combustion_dde.jl

Tests

uv run pytest -q          # 27 unit tests, all pass
uv run ruff check .       # lint clean

Citation

If you use aberth-hybrid in your work, please cite:

@misc{TringaliAberthHybrid2026,
  author       = {Tringali, Giuseppe},
  title        = {A Hybrid Contour-Integral / {Ehrlich--Aberth} Root-Finder
                  for Analytic and Transcendental Functions on Unbounded
                  Regions},
  year         = {2026},
  eprint       = {tbd},
  archivePrefix= {arXiv},
  primaryClass = {math.NA}
}

License

MIT.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

aberth_hybrid-0.1.0.tar.gz (774.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

aberth_hybrid-0.1.0-py3-none-any.whl (38.6 kB view details)

Uploaded Python 3

File details

Details for the file aberth_hybrid-0.1.0.tar.gz.

File metadata

  • Download URL: aberth_hybrid-0.1.0.tar.gz
  • Upload date:
  • Size: 774.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.7.1

File hashes

Hashes for aberth_hybrid-0.1.0.tar.gz
Algorithm Hash digest
SHA256 d458566e8812f26b3a3ce4c4ed0abbbebb890e3df50b4bb13f25ebed3f9ca517
MD5 09c546939f48df83ece620eef674c531
BLAKE2b-256 f317bf4490cdd6654e51f38884d772f3af7b00665d78f59c1c93ab6b91d9bf40

See more details on using hashes here.

File details

Details for the file aberth_hybrid-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for aberth_hybrid-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 49922f35fcc034946fc544d8cd471b9cff7621334c459f18a2f38ea053184b2c
MD5 cffc22c7685ef6bcfc150bbb21ec3444
BLAKE2b-256 673bd8a9f1190f30d8e51ccd981e7f82b086953ae197461b188ac67371b94aeb

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page