Skip to content

Commit 4bec7f3

Browse files
committed
more conversions, with some bug fixes
1 parent 04115f9 commit 4bec7f3

File tree

11 files changed

+207
-40
lines changed

11 files changed

+207
-40
lines changed

Project.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ authors = ["Daan Huybrechs <daan.huybrechs@kuleuven.be>"]
44
version = "0.7.2"
55

66
[deps]
7+
BandedMatrices = "aae01518-5342-5314-be14-df237901396f"
78
BlockArrays = "8e7c35d0-a365-5155-bbbb-fb81a777f24e"
89
CompositeTypes = "b152e2b5-7a66-4b01-a709-34e65c35f657"
910
DomainIntegrals = "cc6bae93-f070-4015-88fd-838f9505a86c"
@@ -33,10 +34,11 @@ PGFPlotsX = "8314cec4-20b6-5062-9cdb-752b83310925"
3334
RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"
3435

3536
[extensions]
36-
BasisFunctionsRecipesBaseExt = "RecipesBase"
3737
BasisFunctionsPGFPlotsXExt = "PGFPlotsX"
38+
BasisFunctionsRecipesBaseExt = "RecipesBase"
3839

3940
[compat]
41+
BandedMatrices = "1.7.6"
4042
BlockArrays = "1"
4143
Calculus = "0.5"
4244
CompositeTypes = "0.1.3"

src/BasisFunctions.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ module BasisFunctions
44

55
## Dependencies
66

7-
using StaticArrays, BlockArrays, SparseArrays, FillArrays
7+
using StaticArrays, BandedMatrices, BlockArrays, SparseArrays, FillArrays
88
using ToeplitzMatrices, LinearAlgebra, GenericLinearAlgebra
99
using FFTW, GenericFFT
1010
using DomainSets, DomainIntegrals

src/bases/poly/ops/connections.jl

Lines changed: 129 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11

2-
const JACOBI_OR_LEGENDRE = Union{Legendre,AbstractJacobi}
2+
isequaldict1(b1::Legendre, b2::AbstractJacobi) =
3+
length(b1)==length(b2) && jacobi_α(b2) == 0 && jacobi_β(b2) == 0
4+
isequaldict2(b1::AbstractJacobi, b2::Legendre) = isequaldict1(b2, b1)
35

4-
isequaldict(b1::JACOBI_OR_LEGENDRE, b2::JACOBI_OR_LEGENDRE) =
5-
length(b1)==length(b2) &&
6-
jacobi_α(b1) == jacobi_α(b2) &&
7-
jacobi_β(b1) == jacobi_β(b2)
6+
isequaldict1(b1::ChebyshevU, b2::Ultraspherical) = length(b1)==length(b2) && ultraspherical_λ(b2) == 1
7+
isequaldict2(b1::Ultraspherical, b2::ChebyshevU) = isequaldict1(b2, b1)
8+
9+
isequaldict1(b1::Ultraspherical{T}, b2::Jacobi) where T =
10+
length(b1)==length(b2) && ultraspherical_λ(b1) == one(T)/2 && (jacobi_α(b2) == jacobi_β(b2) == 0)
11+
isequaldict2(b1::Jacobi, b2::Ultraspherical) = isequaldict1(b2, b1)
812

913
const RECURRENCEBASIS = Union{Monomials,OrthogonalPolynomials}
1014

@@ -18,7 +22,11 @@ function conversion1(::Type{T}, src::RECURRENCEBASIS, dest::RECURRENCEBASIS; opt
1822
E = extension_operator(T, dest1, dest)
1923
E * conversion(T, src, dest1; options...)
2024
else
21-
conversion_using_recurrences(T, src, dest; options...)
25+
if isequaldict(src, dest)
26+
IdentityOperator(src)
27+
else
28+
conversion_using_recurrences(T, src, dest; options...)
29+
end
2230
end
2331
end
2432

@@ -82,9 +90,121 @@ function conversion_using_recurrences(::Type{T}, src::Jacobi, dest::Ultraspheric
8290
diag = T[jacobi_to_ultraspherical(k-1, jacobi_α(src)) for k in 1:length(src)]
8391
ArrayOperator(Diagonal(diag), src, dest)
8492
else
85-
generic_conversion_using_recurrences(T, src, dest)
93+
generic_conversion_using_recurrences(T, src, dest; options...)
94+
end
95+
end
96+
97+
function conversion_using_recurrences(::Type{T}, src::Ultraspherical, dest::Jacobi; options...) where T
98+
if (jacobi_α(dest) == jacobi_β(dest)) && (jacobi_α(src) jacobi_α(dest))
99+
inv(conversion_using_recurrences(T, dest, src); options...)
100+
else
101+
generic_conversion_using_recurrences(T, src, dest; options...)
102+
end
103+
end
104+
105+
106+
# Conversion from one ultraspherical basis to the ultraspherical basis with lambda+1
107+
# Implementation of formula (3.3) of Olver and Townsend, A fast and well-conditioned spectral method,
108+
# SIAM Review, 2013.
109+
function banded_ultraspherical_to_ultraspherical(::Type{T}, n, λ) where T
110+
A = BandedMatrix{T}(undef, (n,n), (0,2))
111+
if n > 0
112+
A[1,1] = 1
113+
end
114+
if n > 1
115+
A[2,2] = λ /+1)
116+
A[1,2] = 0
117+
end
118+
for k in 2:n-1
119+
K = k+1
120+
A[K,K] = λ /+k)
121+
A[K-1,K] = 0
122+
A[K-2,K] = -λ /+k)
123+
end
124+
A
125+
end
126+
127+
# Convert from ChebyshevT expansion to ultraspherical expansion with lambda=1
128+
function banded_chebyshevt_to_ultraspherical(::Type{T}, n) where T
129+
A = BandedMatrix{T}(undef, (n,n), (0,2))
130+
if n > 0
131+
A[1,1] = 1
132+
end
133+
onehalf = one(T)/2
134+
if n > 1
135+
A[1,2] = 0
136+
A[2,2] = onehalf
137+
end
138+
for k in 2:n-1
139+
A[k+1,k+1] = onehalf
140+
A[k-1,k+1] = -onehalf
141+
A[k,k+1] = 0
142+
end
143+
A
144+
end
145+
146+
function conversion_using_recurrences(::Type{T}, src::Ultraspherical, dest::Ultraspherical; options...) where T
147+
@assert length(src) == length(dest)
148+
if ultraspherical_λ(src) ultraspherical_λ(dest)-1
149+
ArrayOperator(banded_ultraspherical_to_ultraspherical(T, length(src), ultraspherical_λ(src)), src, dest)
150+
else
151+
generic_conversion_using_recurrences(T, src, dest; options...)
152+
end
153+
end
154+
155+
function conversion_using_recurrences(::Type{T}, src::ChebyshevT, dest::Ultraspherical; options...) where T
156+
@assert length(src) == length(dest)
157+
if ultraspherical_λ(dest) == 1
158+
ArrayOperator(banded_chebyshevt_to_ultraspherical(T, length(src)), src, dest)
159+
else
160+
generic_conversion_using_recurrences(T, src, dest; options...)
161+
end
162+
end
163+
164+
function diagonal_chebyshevt_to_jacobi(::Type{T}, n::Int) where T
165+
A = T[chebyshevt_to_jacobi(k, T) for k in 0:n-1]
166+
Diagonal(A)
167+
end
168+
169+
function conversion_using_recurrences(::Type{T}, src::ChebyshevT, dest::Jacobi; options...) where T
170+
@assert length(src) == length(dest)
171+
onehalf = one(T)/2
172+
if jacobi_α(dest) -onehalf && jacobi_β(dest) -onehalf
173+
ArrayOperator(diagonal_chebyshevt_to_jacobi(T, length(src)), src, dest)
174+
else
175+
generic_conversion_using_recurrences(T, src, dest; options...)
86176
end
87177
end
88178

89-
conversion_using_recurrences(::Type{T}, src::Ultraspherical, dest::Jacobi; options...) where T =
90-
inv(conversion_using_recurrences(T, dest, src); options...)
179+
function conversion_using_recurrences(::Type{T}, src::Jacobi, dest::ChebyshevT; options...) where T
180+
onehalf = one(T)/2
181+
if jacobi_α(src) -onehalf && jacobi_β(src) -onehalf
182+
inv(conversion_using_recurrences(T, dest, src; options...))
183+
else
184+
generic_conversion_using_recurrences(T, src, dest; options...)
185+
end
186+
end
187+
188+
function diagonal_chebyshevu_to_jacobi(::Type{T}, n::Int) where T
189+
A = T[chebyshevu_to_jacobi(k, T) for k in 0:n-1]
190+
Diagonal(A)
191+
end
192+
193+
function conversion_using_recurrences(::Type{T}, src::ChebyshevU, dest::Jacobi; options...) where T
194+
@assert length(src) == length(dest)
195+
onehalf = one(T)/2
196+
if jacobi_α(dest) onehalf && jacobi_β(dest) onehalf
197+
ArrayOperator(diagonal_chebyshevu_to_jacobi(T, length(src)), src, dest)
198+
else
199+
generic_conversion_using_recurrences(T, src, dest; options...)
200+
end
201+
end
202+
203+
function conversion_using_recurrences(::Type{T}, src::Jacobi, dest::ChebyshevU; options...) where T
204+
onehalf = one(T)/2
205+
if jacobi_α(src) onehalf && jacobi_β(src) onehalf
206+
inv(conversion_using_recurrences(T, dest, src; options...))
207+
else
208+
generic_conversion_using_recurrences(T, src, dest; options...)
209+
end
210+
end

src/bases/poly/ops/dlmf.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ function jacobi_eval(n::Int, x, α, β)
8787
end
8888
z = z1
8989
for i in 2:n
90-
z = (jacobi_rec_An(i, α, β)*x + jacobi_rec_Bn(i, α, β))*z1 - jacobi_rec_Cn(i, α, β)*z0
90+
z = (jacobi_rec_An(i-1, α, β)*x + jacobi_rec_Bn(i-1, α, β))*z1 - jacobi_rec_Cn(i-1, α, β)*z0
9191
z0 = z1
9292
z1 = z
9393
end

src/bases/poly/ops/jacobi.jl

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,6 @@
22
"Abstract supertype of Jacobi polynomials."
33
abstract type AbstractJacobi{T} <: IntervalOPS{T} end
44

5-
iscompatible(b1::AbstractJacobi, b2::AbstractJacobi) =
6-
jacobi_α(b1) == jacobi_α(b2) && jacobi_β(b1) == jacobi_β(b2)
7-
isequaldict(b1::AbstractJacobi, b2::AbstractJacobi) =
8-
length(b1)==length(b2) && iscompatible(b1,b2)
9-
105
isorthogonal(b::AbstractJacobi, μ::DomainIntegrals.AbstractJacobiWeight) =
116
jacobi_α(b) == jacobi_α(μ) && jacobi_β(b) == jacobi_β(μ)
127

@@ -62,6 +57,9 @@ jacobi_β(b::Jacobi) = b.β
6257

6358
measure(b::Jacobi) = JacobiWeight(b.α, b.β)
6459

60+
iscompatible(b1::Jacobi, b2::Jacobi) = jacobi_α(b1) == jacobi_α(b2) && jacobi_β(b1) == jacobi_β(b2)
61+
62+
6563
rec_An(b::Jacobi, n::Int) = jacobi_rec_An(n, jacobi_α(b), jacobi_β(b))
6664
rec_Bn(b::Jacobi, n::Int) = jacobi_rec_Bn(n, jacobi_α(b), jacobi_β(b))
6765
rec_Cn(b::Jacobi, n::Int) = jacobi_rec_Cn(n, jacobi_α(b), jacobi_β(b))
@@ -103,6 +101,10 @@ const Gegenbauer = Ultraspherical
103101
jacobi_α(b::Ultraspherical{T}) where T = b.λ - one(T)/2
104102
jacobi_β(b::Ultraspherical{T}) where T = b.λ - one(T)/2
105103

104+
ultraspherical_λ(b::Ultraspherical) = b.λ
105+
ultraspherical_λ(b::Jacobi{T}) where T =
106+
jacobi_α(b) == jacobi_β(b) ? jacobi_α(b)+one(T)/2 : throw(ArgumentError("Jacobi polynomial is not ultraspherical."))
107+
106108
similar(b::Ultraspherical, ::Type{T}, n::Int) where T = Ultraspherical{T}(n, b.λ)
107109

108110
measure(b::Ultraspherical{T}) where T = UltrasphericalWeight(b.λ)
@@ -113,6 +115,9 @@ rec_Cn(b::Ultraspherical, n::Int) = ultraspherical_rec_Cn(n, b.λ)
113115

114116
same_ops_family(b1::Ultraspherical, b2::Ultraspherical) = b1.λ == b2.λ
115117

118+
iscompatible(b1::Ultraspherical, b2::Ultraspherical) = ultraspherical_λ(b1) == ultraspherical_λ(b2)
119+
120+
116121
## Printing
117122
function show(io::IO, b::Jacobi{Float64})
118123
if jacobi_α(b) == 0 && jacobi_β(b) == 0
@@ -132,4 +137,4 @@ end
132137

133138
show(io::IO, b::Ultraspherical{Float64}) = print(io, "Ultraspherical($(length(b)), $(b.λ))")
134139
show(io::IO, b::Ultraspherical{T}) where T =
135-
print(io, "Ultraspherical{$(T)}($(length(b)), $(b.λ))")
140+
print(io, "Ultraspherical{$(T)}($(length(b)), $(ultraspherical_λ(b)))")

src/computations/conversion.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ projection3(T, src, dest, μ::Measure; options...) =
143143
default_projection(src::Dictionary, dest::Dictionary, μ = measure(dest); options...) =
144144
default_projection(operatoreltype(src,dest), src, dest, μ; options...)
145145
function default_projection(T, src, dest, μ = measure(dest); options...)
146-
if src == dest
146+
if isequaldict(src, dest)
147147
IdentityOperator{T}(src, dest)
148148
else
149149
G = mixedgram(T, dest, src, μ; options...)
@@ -168,7 +168,7 @@ conversion(::Type{T}, src::D, dest::D; options...) where {T,D} =
168168
conversion_same_type(T, src, dest; options...)
169169

170170
function conversion_same_type(::Type{T}, src, dest; options...) where {T}
171-
if src == dest
171+
if isequaldict(src, dest)
172172
IdentityOperator{T}(src)
173173
elseif extensible_dictionaries(src, dest)
174174
extension(T, src, dest; options...)

src/test/Test.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export random_index
1515
export test_tolerance
1616
include("test_util_functions.jl")
1717

18-
export test_generic_dict_interface
18+
export test_generic_dict_interface, test_generic_conversion
1919
export supports_approximation, supports_interpolation
2020
export suitable_interpolation_grid, suitable_function
2121
include("test_dictionary.jl")

src/test/test_dictionary.jl

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -456,7 +456,22 @@ function test_generic_dict_derivative(basis)
456456
# end
457457
end
458458

459-
459+
# Test the conversion from b1 to b2
460+
function test_generic_conversion(b1, b2)
461+
f = random_expansion(b1)
462+
x = fixed_point_in_domain(b1)
463+
C = conversion(b1, b2)
464+
tolerance = sqrt(eps(prectype(b1)))
465+
@test src(C) == b1
466+
@test dest(C) == b2
467+
g = C*f
468+
@test g(x) f(x)
469+
if length(b1) == length(b2)
470+
Cinv = conversion(b2, b1)
471+
f2 = Cinv * (C*f)
472+
@test norm(coefficients(f2-f2)) < tolerance
473+
end
474+
end
460475

461476

462477
function test_generic_dict_interface(@nospecialize basis)

test/dictionaries/test_dictionaries_functionality.jl

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,4 @@
11

2-
function test_generic_conversion(b1, b2)
3-
f = random_expansion(b1)
4-
x = fixed_point_in_domain(b1)
5-
C = conversion(b1, b2)
6-
tolerance = sqrt(eps(prectype(b1)))
7-
@test src(C) == b1
8-
@test dest(C) == b2
9-
g = C*f
10-
@test g(x) f(x)
11-
if length(b1) == length(b2)
12-
Cinv = conversion(b2, b1)
13-
f2 = Cinv * (C*f)
14-
@test norm(coefficients(f2-f2)) < tolerance
15-
end
16-
end
17-
182

193
function test_dictionary_conversions()
204
# OPS are tested separately in test_ops

test/runtests.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ using DoubleFloats,
99
LinearAlgebra,
1010
Random,
1111
StaticArrays,
12+
BandedMatrices,
1213
DomainSets,
1314
DomainIntegrals
1415

0 commit comments

Comments
 (0)