Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
tobydriscoll committed Sep 14, 2023
1 parent 7f0d5f1 commit f34bc64
Show file tree
Hide file tree
Showing 14 changed files with 1,344 additions and 6 deletions.
10 changes: 9 additions & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,19 @@ uuid = "c92886a3-cb30-4e18-b84f-372dde475ecc"
authors = ["Toby Driscoll <[email protected]> and contributors"]
version = "1.0.0-DEV"

[deps]
ComplexRegions = "c64915e2-6c82-11e9-38e9-1f159a780463"
ComplexValues = "41a84b80-6cf2-11e9-379d-9df124847946"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
PyFormattedStrings = "5f89f4a4-a228-4886-b223-c468a82ed5b9"

[compat]
julia = "1.6"

[extras]
ComplexRegions = "c64915e2-6c82-11e9-38e9-1f159a780463"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["Test"]
test = ["Test", "LinearAlgebra", "ComplexRegions"]
23 changes: 22 additions & 1 deletion src/RationalFunctionApproximation.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
module RationalFunctionApproximation

# Write your package code here.
using LinearAlgebra, ComplexRegions
using PyFormattedStrings

export Barycentric, nodes, weights, degree, rewind,
unit_interval, unit_circle, unit_disk, isclosed
include("types.jl")

export poles, residues, roots
include("methods.jl")

export aaa
include("aaa.jl")

export approximate, check
include("approximate.jl")

export minimax
include("lawson.jl")

include("operations.jl")

# include("plotrecipes.jl")

end
192 changes: 192 additions & 0 deletions src/aaa.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
#####
##### Discrete AAA
#####

"""
aaa(z, y)
aaa(f)
Adaptively compute a rational interpolant.
# Arguments
## discrete mode
- `z::AbstractVector{<:Number}`: interpolation nodes
- `y::AbstractVector{<:Number}`: values at nodes
## continuous mode
- `f::Function`: function to approximate on the interval [-1,1]
# Keyword arguments
- `degree::Integer=150`: maximum numerator/denominator degree to use
- `float_type::Type=Float64`: floating point type to use for the computation
- `tol::Real=1000*eps(float_type)`: tolerance for stopping
- `lookahead::Integer=10`: number of iterations to determines stagnation
- `stats::Bool=false`: return convergence statistics
# Returns
- `r::Barycentric`: the rational interpolant
- `stats::NamedTuple`: convergence statistics, if keyword `stats=true`
See also [`approximate`](@ref) for approximating a function on a region.
"""
function aaa(z::AbstractVector{<:Number}, y::AbstractVector{<:Number};
degree = 150, float_type = Float64, tol = 1000*eps(float_type),
lookahead = 10, stats = false
)

@assert float_type <: AbstractFloat
T = float_type
fmax = norm(y, Inf) # for scaling
m = length(z)
iteration = NamedTuple[]
err = T[]
besterr, bestidx, best = Inf, NaN, nothing

# Allocate space for Cauchy matrix, Loewner matrix, and residual
C = similar(z, (m, m))
L = similar(z, (m, m))
R = complex(zeros(size(z)))

= sum(y) / m
s, idx = findmax(abs(y - ȳ) for y in y)
push!(err, s)

# The ordering of nodes matters, while the order of test points does not.
node_index = Int[]
push!(node_index, idx)
test_index = Set(1:m)
delete!(test_index, idx)

n = 0 # number of poles
while true
n += 1
σ = view(z, node_index)
= view(y, node_index)
# Fill in matrices for the latest node
@inbounds @fastmath for i in test_index
C[i, n] = 1 / (z[i] - σ[n])
L[i, n] = (y[i] - fσ[n]) * C[i, n]
end

istest = collect(test_index)
_, _, V = svd( view(L, istest, 1:n) )
w = V[:, end] # barycentric weights

CC = view(C, istest, 1:n)
num = CC * (w.*fσ)
den = CC * w
@. R[istest] = y[istest] - num / den
push!(err, norm(R, Inf))
push!(iteration, (; weights=w, active=copy(node_index)))

if (last(err) < besterr)
besterr, bestidx, best = last(err), length(iteration), last(iteration)
end

# Are we done?
if (besterr <= tol*fmax) ||
(n == degree + 1) ||
((length(iteration) - bestidx >= lookahead) && (besterr < 1e-2*fmax))
break
end

_, j = findmax(abs, R)
push!(node_index, j)
delete!(test_index, j)
R[j] = 0
end

idx, w = best.active, best.weights
r = Barycentric(z[idx], y[idx], w)
if stats
return r, (;err, iteration)
else
return r
end
end

#####
##### Adaptive AAA on [-1, 1] only
#####
# refinement in parameter space
function refine(t, N)
x = sort(t)
Δx = diff(x)
d = eltype(x).((1:N) / (N+1))
return vec( x[1:end-1] .+ (d' .* Δx) )
end

function aaa(
f::Function;
degree=150, float_type=Float64, tol=1000*eps(float_type),
refinement=3, lookahead=10, stats=false
)
@assert float_type <: AbstractFloat
T = float_type
CT = Complex{T}
# arrays for tracking convergence progress
err, nbad = T[], Int[]
nodes, vals, pol, weights = Vector{T}[], Vector{CT}[], Vector{CT}[], Vector{CT}[]

S = [-one(T), one(T)] # initial nodes
fS = f.(S)
besterr, bestm = Inf, NaN
while true # main loop
m = length(S)
push!(nodes, copy(S))
X = refine(S, max(refinement, ceil(16-m))) # test points
fX = f.(X)
push!(vals, copy(fS))
C = [ 1/(x-s) for x in X, s in S ]
L = [a-b for a in fX, b in fS] .* C
_, _, V = svd(L)
w = V[:,end]
push!(weights, w)
R = (C*(w.*fS)) ./ (C*w) # values of the rational interpolant
push!(err, norm(fX - R, Inf) )

zp = poles(Barycentric(S, fS, w))
push!(pol, zp)
I = (imag(zp).==0) .& (abs.(zp).<=1) # bad poles indicator
push!(nbad, sum(I))
# If valid and the best yet, save it:
if (last(nbad) == 0) && (last(err) < besterr)
besterr, bestm = last(err), m
end

fmax = max( norm(fS, Inf), norm(fX, Inf) ) # scale of f
# Check stopping:
if (besterr <= tol*fmax) || # goal met
(m == degree + 1) || # max degree reached
((m - bestm >= lookahead) && (besterr < 1e-2*fmax)) # stagnation
break
end

# We're continuing the iteration, so add the worst test point to the nodes:
_, j = findmax(abs, fX - R)
push!(S, X[j])
push!(fS, fX[j])
end

# Use the best result found:
S, y, w = nodes[bestm-1], vals[bestm-1], weights[bestm-1]
idx = sortperm(S)
x, y, w = S[idx], y[idx], w[idx]
if isreal(w) && isreal(y)
y, w = real(y), real(w)
end

if stats
if isreal(w) && isreal(y)
weights = real.(weights)
vals = real.(vals)
end
st = ConvergenceStats(bestm-1, err, nbad, nodes, vals, weights, pol)
r = Barycentric(x, y, w; stats=st)
else
r = Barycentric(x, y, w)
end

return r
end
Loading

0 comments on commit f34bc64

Please sign in to comment.