Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -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.
1 change: 1 addition & 0 deletions docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ makedocs(;
),
pages=[
"Introduction" => "index.md",
"Installation" => "install.md",
"Algorithms" => "algorithms.md",
"Domains" => "domains.md",
"Discrete data" => "discrete.md",
Expand Down
4 changes: 3 additions & 1 deletion docs/src/algorithms.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand Down
7 changes: 5 additions & 2 deletions docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]$:

Expand Down Expand Up @@ -108,6 +110,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.
Expand Down
22 changes: 22 additions & 0 deletions docs/src/install.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# 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")
```

## Running the examples

To run all this examples in this documentation, you should install these packages as well:

- CairoMakie
- ComplexRegions
- DomainColoring
- DoubleFloats
2 changes: 1 addition & 1 deletion docs/src/python.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
17 changes: 17 additions & 0 deletions docs/src/refs.bib
Original file line number Diff line number Diff line change
Expand Up @@ -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.},
Expand Down
6 changes: 3 additions & 3 deletions src/barycentric.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/parfrac.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
12 changes: 6 additions & 6 deletions src/thiele.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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

Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
Loading