From 024bbcc532823b8ed82b4f0f6a9bc696ff2982e1 Mon Sep 17 00:00:00 2001 From: Toby Driscoll Date: Mon, 29 Jun 2026 10:28:50 -0400 Subject: [PATCH 1/5] Add CONTRIBUTING.md --- CONTRIBUTING.md | 108 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..1d95a003 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,108 @@ +# Contributing to RationalFunctionApproximation.jl + +Thanks for your interest in contributing! Contributions of all kinds are welcome: bug reports, documentation +fixes, new features, performance improvements, and tests. + +## Ways to contribute + +- **Report a bug** by opening an [issue](https://github.com/complexvariables/RationalFunctionApproximation.jl/issues). + Please include a minimal, reproducible example, the output you got, the output + you expected, and your Julia and package versions (`versioninfo()` and + `] status RationalFunctionApproximation`). +- **Suggest a feature or enhancement** by opening an issue describing the use case. +- **Improve documentation**, including the docstrings, the walkthrough, and this file. +- **Submit code** via a pull request (see below). + +For larger changes, please open an issue to discuss the design before investing +significant effort. + +## Development setup + +1. Fork and clone the repository. +2. Start Julia in the project and instantiate the environment: + + ```julia + julia> ] activate . + julia> ] instantiate + ``` + +3. For interactive development, [Revise.jl](https://github.com/timholy/Revise.jl) + is recommended so that edits are picked up without restarting Julia: + + ```julia + julia> using Revise + julia> using RationalFunctionApproximation + ``` + +## Running the tests + +The test suite uses [ReTest](https://github.com/JuliaTesting/ReTest.jl). To run +the full suite: + +```julia +julia> ] test +``` + +or from the shell: + +```sh +julia --project -e 'using Pkg; Pkg.test()' +``` + +ReTest also lets you run a subset of tests interactively. From the `test` +environment: + +```julia +julia> include("test/RFATests.jl") +julia> RFATests.runtests() # run everything +julia> RFATests.runtests("real_interval") # run tests matching a pattern +``` + +Please add or update tests for any change in behavior. New tests generally +belong in the relevant file under `test/` (e.g. `real_interval.jl`, +`circle.jl`, `parfrac.jl`) and should be wired into `test/RFATests.jl` if you +add a new file. + +## Building the documentation + +The documentation is built with [Documenter.jl](https://documenter.juliadocs.org/). +To build it locally: + +```sh +julia --project=docs -e 'using Pkg; Pkg.instantiate()' +julia --project=docs docs/make.jl +``` + +The generated site appears in `docs/build`. + +## Pull request guidelines + +- Branch from `main` and keep each PR focused on a single logical change. +- Match the existing code style: this package uses a two-parameter type system + (`T` for float precision, `S` for value type) and supports generic arithmetic. + See `CLAUDE.md` for an overview of the source layout and key types. +- Add docstrings to new public functions and types, and update the relevant + documentation pages. +- Ensure the full test suite passes locally before opening the PR. +- Write a clear PR description explaining the motivation and approach. Reference + any related issues. +- CI (tests, documentation, and benchmarks) runs automatically on PRs; please + address any failures. + +## Reporting security issues + +If you discover a security-sensitive issue, please contact the maintainer +directly (see the email in `Project.toml`) rather than opening a public issue. + +## License and attribution + +By contributing, you agree that your contributions will be licensed under the +[MIT License](LICENSE) that covers this project. + +If your work builds on or contributes to the methods implemented here, the +relevant references are listed in `README.md`, `CITATION.bib`, and `CLAUDE.md`. + +## Code of conduct + +Please be respectful and constructive in all interactions. We aim to maintain a +welcoming and collaborative community. From ceaaa545d60eb8c70cd5b33f59d62448c104f841 Mon Sep 17 00:00:00 2001 From: Toby Driscoll Date: Mon, 29 Jun 2026 10:30:44 -0400 Subject: [PATCH 2/5] add installation instructions to docs --- docs/make.jl | 1 + docs/src/index.md | 1 + docs/src/install.md | 14 ++++++++++++++ docs/src/python.md | 2 +- 4 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 docs/src/install.md diff --git a/docs/make.jl b/docs/make.jl index 06303a9f..01c1de4c 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -19,6 +19,7 @@ makedocs(; ), pages=[ "Introduction" => "index.md", + "Installation" => "install.md", "Algorithms" => "algorithms.md", "Domains" => "domains.md", "Discrete data" => "discrete.md", diff --git a/docs/src/index.md b/docs/src/index.md index 9fe56764..f82fcc2f 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -108,6 +108,7 @@ scatter(sort(abs.(z)), axis=(ylabel="| node |", yscale=log10,)) ## Contents +* [Installation](@ref) describes the algorithms available for rational approximation. * [Algorithms](@ref) describes the algorithms available for rational approximation. * [Approximation on domains](@ref) shows how to approximate functions on different domains. * [Discrete data](@ref) shows how to approximate data given as points and values rather than as functions. diff --git a/docs/src/install.md b/docs/src/install.md new file mode 100644 index 00000000..2cb3712e --- /dev/null +++ b/docs/src/install.md @@ -0,0 +1,14 @@ +# Installation + +You can download and install this package using the general registry: + +```julia +import Pkg; Pkg.add("RationalFunctionInterpolation") +``` + +If you are going to use domains other than $[-1,1]$ and the unit circle, you should also install `ComplexRegions`: + +```julia +Pkg.add("ComplexRegions") +``` + diff --git a/docs/src/python.md b/docs/src/python.md index 2f0d3257..db28d605 100644 --- a/docs/src/python.md +++ b/docs/src/python.md @@ -2,7 +2,7 @@ You can call the functions in this package from Python using the [`PythonCall`/`JuliaCall`](https://juliapy.github.io/PythonCall.jl/stable/) package. -## Installation +## Installation for Python It's recommended to create a new virtual environment to try this out. In Python, you need to install `juliacall` via From 6a02c8d294953ab7e1d02bb22f7783c8264e68d8 Mon Sep 17 00:00:00 2001 From: Toby Driscoll Date: Mon, 29 Jun 2026 10:36:08 -0400 Subject: [PATCH 3/5] swap out @warn for @info --- src/barycentric.jl | 6 +++--- src/parfrac.jl | 2 +- src/thiele.jl | 12 ++++++------ 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/barycentric.jl b/src/barycentric.jl index 1e9d3f86..cae4ca59 100644 --- a/src/barycentric.jl +++ b/src/barycentric.jl @@ -417,7 +417,7 @@ function approximate(::Type{Barycentric}, status = quitting_check(history, stagnation, tol, fmax, max_iter, allowed) if status > 0 - @warn("Stopping with estimated error $(round(history[status].error, sigdigits=4)) after $n iterations") + @info("Stopping with estimated error $(round(history[status].error, sigdigits=4)) after $n iterations") r = history[status].interpolant end (status != 0) && break @@ -431,7 +431,7 @@ function approximate(::Type{Barycentric}, # look for the best acceptable case status = quitting_check(history, stagnation, tol, fmax, 1, allowed) r = history[status].interpolant - @warn("Unable to add new node; stopping with estimated error $(round(history[status].error, sigdigits=4))") + @info("Unable to add new node; stopping with estimated error $(round(history[status].error, sigdigits=4))") break end @@ -498,7 +498,7 @@ function approximate(::Type{Barycentric}, status = quitting_check(history, stagnation, tol, fmax, max_iter, allowed) if status > 0 - @warn("Stopping with estimated error $(round(history[status].error, sigdigits=4)) after $n iterations") + @info("Stopping with estimated error $(round(history[status].error, sigdigits=4)) after $n iterations") r = history[status].interpolant end (status != 0) && break diff --git a/src/parfrac.jl b/src/parfrac.jl index c8cd8adb..9668dcc0 100644 --- a/src/parfrac.jl +++ b/src/parfrac.jl @@ -107,7 +107,7 @@ function refine_by_singularity(d::ComplexCurveOrPath, ζ::AbstractVector; end add_node!(path, (idx[1], idx[2]+1)) end - @warn "Refinement was not successful" + @info "Refinement was not successful" return path end diff --git a/src/thiele.jl b/src/thiele.jl index bd083241..b345b473 100644 --- a/src/thiele.jl +++ b/src/thiele.jl @@ -491,7 +491,7 @@ function approximate(::Type{Thiele}, status = quitting_check(history, stagnation, tol, fmax, max_iter, allowed) if status > 0 - @warn("Stopping with estimated error $(round(history[status].error, sigdigits=4)) after $n iterations") + @info("Stopping with estimated error $(round(history[status].error, sigdigits=4)) after $n iterations") r = history[status].interpolant end (status != 0) && break @@ -505,7 +505,7 @@ function approximate(::Type{Thiele}, # look for the best acceptable case status = quitting_check(history, stagnation, tol, fmax, 1, allowed) r = history[status].interpolant - @warn("NaN weight encountered; stopping with estimated error $(round(history[status].error, sigdigits=4))") + @info("NaN weight encountered; stopping with estimated error $(round(history[status].error, sigdigits=4))") @debug("Error $e") break end @@ -518,7 +518,7 @@ function approximate(::Type{Thiele}, # look for the best acceptable case status = quitting_check(history, stagnation, tol, fmax, 1, allowed) r = history[status].interpolant - @warn("Maximum path refinement exceeded; stopping with estimated error $(round(history[status].error, sigdigits=4))") + @info("Maximum path refinement exceeded; stopping with estimated error $(round(history[status].error, sigdigits=4))") break end @@ -579,10 +579,10 @@ function approximate(::Type{Thiele}, status = quitting_check(history, stagnation, tol, fmax, max_iter, allowed) if status > 0 if isinf(err_max) - @warn("Used all sample values without convergence") + @info("Used all sample values without convergence") status = max_iter else - @warn("Stopping with estimated error $(round(history[status].error, sigdigits=4)) after $n iterations") + @info("Stopping with estimated error $(round(history[status].error, sigdigits=4)) after $n iterations") end r = history[status].interpolant end @@ -599,7 +599,7 @@ function approximate(::Type{Thiele}, # look for the best acceptable case status = quitting_check(history, stagnation, tol, fmax, 1, allowed) r = history[status].interpolant - @warn("Adding node failed; stopping with estimated error $(round(history[status].error, sigdigits=4))") + @info("Adding node failed; stopping with estimated error $(round(history[status].error, sigdigits=4))") @debug("Error $e") break end From ab95b20cdd51db6f108ab64bf0599c3e9bd6a81b Mon Sep 17 00:00:00 2001 From: Toby Driscoll Date: Mon, 29 Jun 2026 10:54:49 -0400 Subject: [PATCH 4/5] improvements to docs intro and installation --- docs/src/index.md | 6 ++++-- docs/src/install.md | 8 ++++++++ docs/src/refs.bib | 17 +++++++++++++++++ 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/docs/src/index.md b/docs/src/index.md index f82fcc2f..39ad73fb 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -2,9 +2,11 @@ *Documentation for [RationalFunctionApproximation.jl](https://github.com/complexvariables/RationalFunctionApproximation.jl).* -This package computes rational approximations of a function or data given in the complex plane. For background reading, see [NakatsukasaAAAAlgorithm2018](@cite), [DriscollAAARational2024](@cite) (or the related arXiv version [DriscollAAARational2023a](@cite)), [CostaAAAleastSquares2023](@cite), and [SalazarCelisNumericalContinued2024](@cite). +This package computes rational approximations of a function or data given in a domain in the complex plane (including real intervals). -A rational function is a ratio of two polynomials. Rational functions are capable of very high accuracy, usually requiring fewer degrees of freedom than polynomial approximations. They are an especially good choice for approximating functions with singularities near the domain of approximation. However, they do not depend linearly on the data, which has historically made them difficult to compute and work with, but breakthroughs in algorithms over the last decade have changed that. +A rational function is a ratio of two polynomials. Rational functions are capable of very high accuracy, usually requiring fewer degrees of freedom than polynomial approximations. They are an especially good choice for approximating functions with singularities near the domain of approximation. Rational functions have applications to finding singularities and roots, computing derivatives, continuation across boundaries, solving PDEs, and more; for a survey of applications, see [NakatsukasaApplicationsAAA2026](@cite). For background on the algorithms in this package, see [NakatsukasaAAAAlgorithm2018](@cite), [DriscollAAARational2024](@cite) (or the related arXiv version [DriscollAAARational2023a](@cite)), [CostaAAAleastSquares2023](@cite), and [SalazarCelisNumericalContinued2024](@cite). + +## Basic walkthrough Here's a smooth, gentle function on the interval $[-1, 1]$: diff --git a/docs/src/install.md b/docs/src/install.md index 2cb3712e..920f20d9 100644 --- a/docs/src/install.md +++ b/docs/src/install.md @@ -12,3 +12,11 @@ If you are going to use domains other than $[-1,1]$ and the unit circle, you sho Pkg.add("ComplexRegions") ``` +## Running the examples + +To run all this examples in this documentation, you should install these packages as well: + +- CairoMakie +- ComplexRegions +- DomainColoring +- DoubleFloats diff --git a/docs/src/refs.bib b/docs/src/refs.bib index da970212..bf8ef113 100644 --- a/docs/src/refs.bib +++ b/docs/src/refs.bib @@ -1309,6 +1309,23 @@ @article{NakatsukasaAlgorithmReal2020 file = {/Users/driscoll/Dropbox/library/Journal Article/Nakatsukasa_Trefethen_2020_An Algorithm for Real and Complex Rational Minimax Approximation2.pdf} } +@article{NakatsukasaApplicationsAAA2026, + title = {Applications of {{AAA}} Rational Approximation}, + author = {Nakatsukasa, Yuji and Trefethen, Lloyd N.}, + year = 2026, + month = jul, + journal = {Acta Numerica}, + volume = {35}, + pages = {459--601}, + issn = {0962-4929, 1474-0508}, + doi = {10.1017/S0962492925100263}, + urldate = {2026-06-29}, + abstract = {The AAA algorithm for rational approximation is employed to illustrate applications of rational functions all across numerical analysis. For example, rational functions enable strikingly effective methods for numerical differentiation and integration; interpolation of equispaced data; locating zeros, poles, and branch points; analytic continuation; computation of inverse functions; imputation of missing data; computation of nonlinear eigenvalues and resonances; model order reduction/reduced order modelling; solution of Wiener--Hopf and Hilbert transform problems; conformal mapping; and solution of the two-dimensional Laplace, biharmonic and Helmholtz equations. The paper also surveys various improvements and generalizations of the AAA algorithm that have developed since its original appearance in 2018.}, + langid = {english}, + keywords = {30B40,41A20,65D15,65N35}, + file = {/Users/driscoll/Dropbox/library/Nakatsukasa and Trefethen_2026_Applications of AAA rational approximation.pdf} +} + @article{NewmanRationalApproximation1964, title = {Rational Approximation to |x|}, author = {Newman, D. J.}, From 81b4b5650f8fb465d493312ec04fff925a52e4ab Mon Sep 17 00:00:00 2001 From: Toby Driscoll Date: Mon, 29 Jun 2026 14:19:12 -0400 Subject: [PATCH 5/5] add note about ill-posed pole labels --- docs/src/algorithms.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/src/algorithms.md b/docs/src/algorithms.md index 4b89275f..941afc76 100644 --- a/docs/src/algorithms.md +++ b/docs/src/algorithms.md @@ -33,7 +33,7 @@ convergenceplot(r) (The plots in this documentation are made using `CairoMakie`, but the same functions are made available for `Plots`.) In the plot above, the markers show the estimated max-norm error of the AAA rational approximant over the domain as a function of the denominator degree; each iteration adds one degree to both the numerator and denominator. The gold halo indicates the final approximation chosen by the algorithm. -The red dots in the convergence plot indicate that a genuine pole—that is, one with residue larger than machine precision—of the rational approximant lies in the approximation domain. We can verify this fact here by using `rewind` to recover the approximation from iteration 9: +The red dots in the convergence plot indicate that a genuine pole—that is, one with residue larger than machine precision—of the rational approximant lies in the approximation domain.[^genuine] We can verify this fact here by using `rewind` to recover the approximation from iteration 9: ```@repl convergence r9 = rewind(r, 9) @@ -55,6 +55,8 @@ filter(<(-0.75), test_points(r9)) That's why the reported error is not very large. It's worth keeping in mind that the convergence plot shows the algorithm's estimate of the error, not the true error. Guarantees are hard to come by; no matter how carefully we told the algorithm to look within the interval, some cases would fall through the cracks. Fortunately, both the pole check and the overall error indicate that the approximation is not yet fully baked, and the iteration continues. +[^genuine]: The notion of a "genuine" pole is not well-posed. Both pole locations and residue magnitudes could be reclassified after arbitrarily small perturbations, and thus the label is sensitive to roundoff. + It is possible for the iteration to stagnate with bad poles if the original function has a singularity very close to the domain. ```@example convergence