Skip to content

Commit 4cc6391

Browse files
authored
Restore Toeplitz-dot-Hankel (#198)
* Restore ToeplitzHankel * Restore TH * Toeplitz Plans * Update toeplitzplans.jl * add ToeplitzPlans tests * Use ToeplitzPlan * Make planned transforms allocation-free * Start removing Hankel * Remove usage of Hankel * Start making plans multidim * fix vector transforms * at tests for ultra2ultra and jac2jac (the latter of which fails) * 2D leg2cheb on one dimensio * work on multidims * Make C a matrix * use 2D FFT for Leg2Cheb * Combine DL, DR and C * Update transforms * dims=1 leg2cheb * dims=2 works * 2D leg2cheb * Simplify setup * work on cheb2leg * some cheb2leg 2D transforms work * 2d cheb2leg works * need to speed-up maybereal! * simplify cheb2leg * Add complex tests * Remove unused code * Default to toeplitz-dot-hankel for one-off transforms to avoid expensive plans * add plans with range dims * unify code * Update toeplitzhankel.jl * add BigFloat tests * BIgFloat tests/bug fixes * Fix Λ sign for bigfloats * Fix Λ def * support empty case
1 parent b6a1a35 commit 4cc6391

12 files changed

+658
-35
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name = "FastTransforms"
22
uuid = "057dd010-8810-581a-b7be-e3fc3b93f78c"
3-
version = "0.14.12"
3+
version = "0.15"
44

55
[deps]
66
AbstractFFTs = "621f4979-c628-5d54-868e-fcf4e3e8185c"

src/FastTransforms.jl

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,4 +109,25 @@ export sphones, sphzeros, sphrand, sphrandn, sphevaluate,
109109

110110
include("specialfunctions.jl")
111111

112+
include("toeplitzplans.jl")
113+
include("toeplitzhankel.jl")
114+
115+
# following use libfasttransforms by default
116+
for f in (:jac2jac,
117+
:lag2lag, :jac2ultra, :ultra2jac, :jac2cheb,
118+
:cheb2jac, :ultra2cheb, :cheb2ultra, :associatedjac2jac,
119+
:modifiedjac2jac, :modifiedlag2lag, :modifiedherm2herm,
120+
:sph2fourier, :sphv2fourier, :disk2cxf, :ann2cxf,
121+
:rectdisk2cheb, :tri2cheb, :tet2cheb)
122+
lib_f = Symbol("lib_", f)
123+
@eval $f(x::AbstractArray, y...; z...) = $lib_f(x::AbstractArray, y...; z...)
124+
end
125+
126+
# following use Toeplitz-Hankel to avoid expensive plans
127+
for f in (:leg2cheb, :cheb2leg, :ultra2ultra)
128+
th_f = Symbol("th_", f)
129+
@eval $f(x::AbstractArray, y...; z...) = $th_f(x::AbstractArray, y...; z...)
130+
end
131+
132+
112133
end # module

src/libfasttransforms.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -435,10 +435,11 @@ for f in (:leg2cheb, :cheb2leg, :ultra2ultra, :jac2jac,
435435
:sph2fourier, :sphv2fourier, :disk2cxf, :ann2cxf,
436436
:rectdisk2cheb, :tri2cheb, :tet2cheb)
437437
plan_f = Symbol("plan_", f)
438+
lib_f = Symbol("lib_", f)
438439
@eval begin
439440
$plan_f(x::AbstractArray{T}, y...; z...) where T = $plan_f(T, size(x, 1), y...; z...)
440441
$plan_f(::Type{Complex{T}}, y...; z...) where T <: Real = $plan_f(T, y...; z...)
441-
$f(x::AbstractArray, y...; z...) = $plan_f(x, y...; z...)*x
442+
$lib_f(x::AbstractArray, y...; z...) = $plan_f(x, y...; z...)*x
442443
end
443444
end
444445

src/specialfunctions.jl

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,8 @@ end
135135
"""
136136
The Lambda function ``\\Lambda(z) = \\frac{\\Gamma(z+\\frac{1}{2})}{\\Gamma(z+1)}`` for the ratio of gamma functions.
137137
"""
138-
Λ(z::Number) = exp(lgamma(z+half(z))-lgamma(z+one(z)))
138+
Λ(z::Number) = Λ(z, half(z), one(z))
139+
139140
"""
140141
For 64-bit floating-point arithmetic, the Lambda function uses the asymptotic series for ``\\tau`` in Appendix B of
141142
@@ -153,12 +154,18 @@ end
153154
"""
154155
The Lambda function ``\\Lambda(z,λ₁,λ₂) = \\frac{\\Gamma(z+\\lambda_1)}{Γ(z+\\lambda_2)}`` for the ratio of gamma functions.
155156
"""
156-
Λ(z::Number,λ₁::Number,λ₂::Number) = exp(lgamma(z+λ₁)-lgamma(z+λ₂))
157-
function Λ(x::Float64,λ₁::Float64,λ₂::Float64)
157+
function Λ(z::Real, λ₁::Real, λ₂::Real)
158+
if z+λ₁ > 0 && z+λ₂ > 0
159+
exp(lgamma(z+λ₁)-lgamma(z+λ₂))
160+
else
161+
gamma(z+λ₁)/gamma(z+λ₂)
162+
end
163+
end
164+
function Λ(x::Float64, λ₁::Float64, λ₂::Float64)
158165
if min(x+λ₁,x+λ₂) 8.979120323411497
159166
exp(λ₂-λ₁+(x-.5)*log1p((λ₁-λ₂)/(x+λ₂)))*(x+λ₁)^λ₁/(x+λ₂)^λ₂*stirlingseries(x+λ₁)/stirlingseries(x+λ₂)
160167
else
161-
(x+λ₂)/(x+λ₁)*Λ(x+1.,λ₁,λ₂)
168+
(x+λ₂)/(x+λ₁)*Λ(x + 1.0, λ₁, λ₂)
162169
end
163170
end
164171

src/toeplitzhankel.jl

Lines changed: 287 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
1+
"""
2+
Store a diagonally-scaled Toeplitz∘Hankel matrix:
3+
DL(T∘H)DR
4+
where the Hankel matrix `H` is non-negative definite. This allows a Cholesky decomposition in 𝒪(K²N) operations and 𝒪(KN) storage, K = log N log ɛ⁻¹.
5+
"""
6+
struct ToeplitzHankelPlan{S, N, M, N1, TP<:ToeplitzPlan{S,N1}} <: Plan{S}
7+
T::NTuple{M,TP}
8+
L::NTuple{M,Matrix{S}}
9+
R::NTuple{M,Matrix{S}}
10+
tmp::Array{S,N1}
11+
dims::NTuple{M,Int}
12+
function ToeplitzHankelPlan{S,N,M,N1,TP}(T::NTuple{M,TP}, L, R, dims) where {S,TP,N,N1,M}
13+
tmp = Array{S}(undef, max.(size.(T)...)...)
14+
new{S,N,M,N1,TP}(T, L, R, tmp, dims)
15+
end
16+
ToeplitzHankelPlan{S,N,M,N1,TP}(T::NTuple{M,TP}, L, R, dims::Int) where {S,TP,N,N1,M} =
17+
ToeplitzHankelPlan{S,N,M,N1,TP}(T, L, R, (dims,))
18+
end
19+
20+
ToeplitzHankelPlan(T::ToeplitzPlan{S,2}, L::Matrix, R::Matrix, dims=1) where S =
21+
ToeplitzHankelPlan{S, 1, 1, 2, typeof(T)}((T,), (L,), (R,), dims)
22+
23+
ToeplitzHankelPlan(T::ToeplitzPlan{S,3}, L::Matrix, R::Matrix, dims) where S =
24+
ToeplitzHankelPlan{S, 2, 1,3, typeof(T)}((T,), (L,), (R,), dims)
25+
26+
ToeplitzHankelPlan(T::NTuple{2,TP}, L::Tuple, R::Tuple, dims) where {S,TP<:ToeplitzPlan{S,3}} =
27+
ToeplitzHankelPlan{S, 2,2,3, TP}(T, L, R, dims)
28+
29+
30+
function *(P::ToeplitzHankelPlan{<:Any,1}, v::AbstractVector)
31+
(R,),(L,),(T,),tmp = P.R,P.L,P.T,P.tmp
32+
tmp .= R .* v
33+
T * tmp
34+
tmp .= L .* tmp
35+
sum!(v, tmp)
36+
end
37+
38+
function _th_applymul1!(v, T, L, R, tmp)
39+
N = size(R,2)
40+
m,n = size(v)
41+
tmp[1:m,1:n,1:N] .= reshape(R,size(R,1),1,N) .* v
42+
T * view(tmp,1:m,1:n,1:N)
43+
view(tmp,1:m,1:n,1:N) .*= reshape(L,size(L,1),1,N)
44+
sum!(v, view(tmp,1:m,1:n,1:N))
45+
end
46+
47+
function _th_applymul2!(v, T, L, R, tmp)
48+
N = size(R,2)
49+
m,n = size(v)
50+
tmp[1:m,1:n,1:N] .= reshape(R,1,size(R,1),N) .* v
51+
T * view(tmp,1:m,1:n,1:N)
52+
view(tmp,1:m,1:n,1:N) .*= reshape(L,1,size(L,1),N)
53+
sum!(v, view(tmp,1:m,1:n,1:N))
54+
end
55+
56+
57+
function *(P::ToeplitzHankelPlan{<:Any,2,1}, v::AbstractMatrix)
58+
(R,),(L,),(T,),tmp = P.R,P.L,P.T,P.tmp
59+
if P.dims == (1,)
60+
_th_applymul1!(v, T, L, R, tmp)
61+
else
62+
_th_applymul2!(v, T, L, R, tmp)
63+
end
64+
v
65+
end
66+
67+
function *(P::ToeplitzHankelPlan{<:Any,2,2}, v::AbstractMatrix)
68+
(R1,R2),(L1,L2),(T1,T2),tmp = P.R,P.L,P.T,P.tmp
69+
70+
_th_applymul1!(v, T1, L1, R1, tmp)
71+
_th_applymul2!(v, T2, L2, R2, tmp)
72+
73+
v
74+
end
75+
76+
# partial cholesky for a Hankel matrix
77+
78+
function hankel_partialchol(v::Vector{T}) where T
79+
# Assumes positive definite
80+
σ = T[]
81+
n = isempty(v) ? 0 : (length(v)+2) ÷ 2
82+
C = Matrix{T}(undef, n, n)
83+
d = v[1:2:end] # diag of H
84+
@assert length(v) 2n-1
85+
reltol = maximum(abs,d)*eps(T)*log(n)
86+
r = 0
87+
for k = 1:n
88+
mx,idx = findmax(d)
89+
if mx reltol break end
90+
push!(σ, inv(mx))
91+
C[:,k] .= view(v,idx:n+idx-1)
92+
for j = 1:k-1
93+
nCjidxσj = -C[idx,j]*σ[j]
94+
LinearAlgebra.axpy!(nCjidxσj, view(C,:,j), view(C,:,k))
95+
end
96+
@inbounds for p=1:n
97+
d[p] -= C[p,k]^2/mx
98+
end
99+
r += 1
100+
end
101+
for k=1:length(σ) rmul!(view(C,:,k), sqrt(σ[k])) end
102+
C[:,1:r]
103+
end
104+
105+
106+
# Diagonally-scaled Toeplitz∘Hankel polynomial transforms
107+
108+
109+
110+
struct ChebyshevToLegendrePlanTH{TH}
111+
toeplitzhankel::TH
112+
end
113+
114+
function *(P::ChebyshevToLegendrePlanTH, v::AbstractVector{S}) where S
115+
n = length(v)
116+
ret = zero(S)
117+
@inbounds for k = 1:2:n
118+
ret += -v[k]/(k*(k-2))
119+
end
120+
v[1] = ret
121+
P.toeplitzhankel*view(v,2:n)
122+
v
123+
end
124+
125+
function _cheb2leg_rescale1!(V::AbstractMatrix{S}) where S
126+
m,n = size(V)
127+
for j = 1:n
128+
ret = zero(S)
129+
@inbounds for k = 1:2:m
130+
ret += -V[k,j]/(k*(k-2))
131+
end
132+
V[1,j] = ret
133+
end
134+
V
135+
end
136+
137+
138+
function *(P::ChebyshevToLegendrePlanTH, V::AbstractMatrix)
139+
m,n = size(V)
140+
dims = P.toeplitzhankel.dims
141+
if dims == (1,)
142+
_cheb2leg_rescale1!(V)
143+
P.toeplitzhankel*view(V,2:m,:)
144+
elseif dims == (2,)
145+
_cheb2leg_rescale1!(transpose(V))
146+
P.toeplitzhankel*view(V,:,2:n)
147+
else
148+
@assert dims == (1,2)
149+
(R1,R2),(L1,L2),(T1,T2),tmp = P.toeplitzhankel.R,P.toeplitzhankel.L,P.toeplitzhankel.T,P.toeplitzhankel.tmp
150+
151+
_cheb2leg_rescale1!(V)
152+
_th_applymul1!(view(V,2:m,:), T1, L1, R1, tmp)
153+
_cheb2leg_rescale1!(transpose(V))
154+
_th_applymul2!(view(V,:,2:n), T2, L2, R2, tmp)
155+
end
156+
V
157+
end
158+
159+
160+
161+
function _leg2chebTH_TLC(::Type{S}, mn, d) where S
162+
n = mn[d]
163+
λ = Λ.(0:half(real(S)):n-1)
164+
t = zeros(S,n)
165+
t[1:2:end] .= 2 .* view(λ, 1:2:n) ./ π
166+
C = hankel_partialchol(λ)
167+
T = plan_uppertoeplitz!(t, (mn..., size(C,2)), d)
168+
L = copy(C)
169+
L[1,:] ./= 2
170+
T,L,C
171+
end
172+
173+
function _leg2chebuTH_TLC(::Type{S}, mn, d) where {S}
174+
n = mn[d]
175+
= real(S)
176+
λ = Λ.(0:half(S̃):n-1)
177+
t = zeros(S,n)
178+
t[1:2:end] = λ[1:2:n]./(((1:2:n).-2))
179+
h = λ./((1:2n-1).+1)
180+
C = hankel_partialchol(h)
181+
T = plan_uppertoeplitz!(-2t/π, (length(t), size(C,2)), 1)
182+
(T, (1:n) .* C, C)
183+
end
184+
185+
186+
for f in (:leg2cheb, :leg2chebu)
187+
plan = Symbol("plan_th_", f, "!")
188+
TLC = Symbol("_", f, "TH_TLC")
189+
@eval begin
190+
$plan(::Type{S}, mn::Tuple, dims::Int) where {S} = ToeplitzHankelPlan($TLC(S, mn, dims)..., dims)
191+
192+
function $plan(::Type{S}, mn::NTuple{2,Int}, dims::NTuple{2,Int}) where {S}
193+
@assert dims == (1,2)
194+
T1,L1,C1 = $TLC(S, mn, 1)
195+
T2,L2,C2 = $TLC(S, mn, 2)
196+
ToeplitzHankelPlan((T1,T2), (L1,L2), (C1,C2), dims)
197+
end
198+
end
199+
end
200+
201+
_sub_dim_by_one(d) = ()
202+
_sub_dim_by_one(d, m, n...) = (isone(d) ? m-1 : m, _sub_dim_by_one(d-1, n...)...)
203+
204+
function _cheb2legTH_TLC(::Type{S}, mn, d) where S
205+
n = mn[d]
206+
t = zeros(S,n-1)
207+
= real(S)
208+
if n > 1
209+
t[1:2:end] = Λ.(0:one(S̃):div(n-2,2), -half(S̃), one(S̃))
210+
end
211+
h = Λ.(1:half(S̃):n-1, zero(S̃), 3half(S̃))
212+
DL = (3half(S̃):n-half(S̃))
213+
DR = -(one(S̃):n-one(S̃))./4
214+
C = hankel_partialchol(h)
215+
T = plan_uppertoeplitz!(t, (_sub_dim_by_one(d, mn...)..., size(C,2)), d)
216+
T, DL .* C, DR .* C
217+
end
218+
219+
plan_th_cheb2leg!(::Type{S}, mn::Tuple, dims::Int) where {S} = ChebyshevToLegendrePlanTH(ToeplitzHankelPlan(_cheb2legTH_TLC(S, mn, dims)..., dims))
220+
221+
function plan_th_cheb2leg!(::Type{S}, mn::NTuple{2,Int}, dims::NTuple{2,Int}) where {S}
222+
@assert dims == (1,2)
223+
T1,L1,C1 = _cheb2legTH_TLC(S, mn, 1)
224+
T2,L2,C2 = _cheb2legTH_TLC(S, mn, 2)
225+
ChebyshevToLegendrePlanTH(ToeplitzHankelPlan((T1,T2), (L1,L2), (C1,C2), dims))
226+
end
227+
228+
function plan_th_ultra2ultra!(::Type{S}, (n,)::Tuple{Int}, λ₁, λ₂) where {S}
229+
@assert abs(λ₁-λ₂) < 1
230+
= real(S)
231+
DL = (zero(S̃):n-one(S̃)) .+ λ₂
232+
jk = 0:half(S̃):n-1
233+
t = zeros(S,n)
234+
t[1:2:n] = Λ.(jk,λ₁-λ₂,one(S̃))[1:2:n]
235+
h = Λ.(jk,λ₁,λ₂+one(S̃))
236+
lmul!(gamma(λ₂)/gamma(λ₁),h)
237+
C = hankel_partialchol(h)
238+
T = plan_uppertoeplitz!(lmul!(inv(gamma(λ₁-λ₂)),t), (length(t), size(C,2)), 1)
239+
ToeplitzHankelPlan(T, DL .* C, C)
240+
end
241+
242+
function alternatesign!(v)
243+
@inbounds for k = 2:2:length(v)
244+
v[k] = -v[k]
245+
end
246+
v
247+
end
248+
249+
function plan_th_jac2jac!(::Type{S}, (n,), α, β, γ, δ) where {S}
250+
if β == δ
251+
@assert abs-γ) < 1
252+
@assert α+β > -1
253+
jk = 0:n-1
254+
DL = (2jk .+ γ .+ β .+ 1).*Λ.(jk,γ+β+1+1)
255+
t = convert(AbstractVector{S}, Λ.(jk, α-γ,1))
256+
h = Λ.(0:2n-2+β+1+β+2)
257+
DR = Λ.(jk,β+1+β+1)./gamma-γ)
258+
C = hankel_partialchol(h)
259+
T = plan_uppertoeplitz!(t, (length(t), size(C,2)), 1)
260+
elseif α == γ
261+
jk = 0:n-1
262+
DL = (2jk .+ δ .+ α .+ 1).*Λ.(jk,δ+α+1+1)
263+
h = Λ.(0:2n-2+β+1+α+2)
264+
DR = Λ.(jk,α+1+β+1)./gamma-δ)
265+
t = alternatesign!(convert(AbstractVector{S}, Λ.(jk,β-δ,1)))
266+
C = hankel_partialchol(h)
267+
T = plan_uppertoeplitz!(t, (length(t), size(C,2)), 1)
268+
else
269+
throw(ArgumentError("Cannot create Toeplitz dot Hankel, use a sequence of plans."))
270+
end
271+
272+
ToeplitzHankelPlan(T, DL .* C, DR .* C)
273+
end
274+
275+
for f in (:th_leg2cheb, :th_cheb2leg, :th_leg2chebu)
276+
plan = Symbol("plan_", f, "!")
277+
@eval begin
278+
$plan(::Type{S}, mn::NTuple{N,Int}, dims::UnitRange) where {N,S} = $plan(S, mn, tuple(dims...))
279+
$plan(::Type{S}, mn::Tuple{Int}, dims::Tuple{Int}=(1,)) where {S} = $plan(S, mn, dims...)
280+
$plan(::Type{S}, (m,n)::NTuple{2,Int}) where {S} = $plan(S, (m,n), (1,2))
281+
$plan(arr::AbstractArray{T}, dims...) where T = $plan(T, size(arr), dims...)
282+
$f(v, dims...) = $plan(eltype(v), size(v), dims...)*copy(v)
283+
end
284+
end
285+
286+
th_ultra2ultra(v, λ₁, λ₂, dims...) = plan_th_ultra2ultra!(eltype(v),size(v),λ₁,λ₂, dims...)*copy(v)
287+
th_jac2jac(v, α, β, γ, δ, dims...) = plan_th_jac2jac!(eltype(v),size(v),α,β,γ,δ, dims...)*copy(v)

0 commit comments

Comments
 (0)