Skip to content

Commit 79f670d

Browse files
authored
Use Half{Odd{Int}} in Jacobi-Ultraspherical conversions (#249)
* initial attempt, but tests fail * specialize isapproxinteger_addhalf * half integers in isequalminhalf/isapproxminhalf * simplify chebyshev/ultraspherical conversions * type-inference improvement using decreasingunitsteprange * bump version to v0.6.29 * isequalhalf and isapproxinteger_addhalf for HalfOddInteger * inference tests using HalfOddInteger * Move helper methods to top level file * use _onehalf in more places * Jacobi <-> Ultraspherical preserves types * improvements in inference in Conversion * remove redundant spaces * Add tests * Ultraspherical to Jacobi inference * Ultraspherical constructor in Conversion * more Ultraspherical constructors
1 parent 975015b commit 79f670d

8 files changed

+165
-54
lines changed

Project.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name = "ApproxFunOrthogonalPolynomials"
22
uuid = "b70543e2-c0d9-56b8-a290-0d4d6d4de211"
3-
version = "0.6.28"
3+
version = "0.6.29"
44

55
[deps]
66
ApproxFunBase = "fbd15aa5-315a-5a7d-a8a4-24992e37be05"
@@ -11,8 +11,10 @@ DomainSets = "5b8099bc-c8ec-5219-889f-1d9e522a28bf"
1111
FastGaussQuadrature = "442a2c76-b920-505d-bb47-c5924d526838"
1212
FastTransforms = "057dd010-8810-581a-b7be-e3fc3b93f78c"
1313
FillArrays = "1a297f60-69ca-5386-bcde-b61e274b549b"
14+
HalfIntegers = "f0d1745a-41c9-11e9-1dd9-e5d34d218721"
1415
IntervalSets = "8197267c-284f-5f27-9208-e0e47529a953"
1516
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
17+
OddEvenIntegers = "8d37c425-f37a-4ca2-9b9d-a61bc06559d2"
1618
Reexport = "189a3867-3050-52da-a836-e630ba90ab69"
1719
SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b"
1820
Static = "aedffcd0-7271-4cad-89d0-dc628f76c6d3"
@@ -30,8 +32,10 @@ DomainSets = "0.5, 0.6"
3032
FastGaussQuadrature = "0.4, 0.5"
3133
FastTransforms = "0.12, 0.13, 0.14, 0.15.1"
3234
FillArrays = "0.11, 0.12, 0.13, 1"
35+
HalfIntegers = "1.5"
3336
IntervalSets = "0.5, 0.6, 0.7"
3437
LazyArrays = "0.22, 1"
38+
OddEvenIntegers = "0.1"
3539
Reexport = "0.2, 1"
3640
SpecialFunctions = "0.10, 1.0, 2"
3741
Static = "0.8"

src/ApproxFunOrthogonalPolynomials.jl

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ using StaticArrays: SVector
8585

8686
import LinearAlgebra: isdiag, eigvals, eigen
8787

88+
using OddEvenIntegers
89+
using HalfIntegers
90+
8891
export bandmatrices_eigen, SymmetricEigensystem, SkewSymmetricEigensystem
8992

9093
points(d::IntervalOrSegmentDomain{T},n::Integer) where {T} =
@@ -134,6 +137,44 @@ compare_op() = ≈
134137
compare_op(::Any, args...) = compare_op(args...)
135138
compare_orders(a::Number, b::Number) = compare_op(a, b)(a, b)
136139

140+
# work around type promotions to preserve types for StepRanges involving HalfOddIntegers with a unit step
141+
const HalfOddInteger{T<:Integer} = Half{Odd{T}}
142+
decreasingunitsteprange(start, stop) = start:-1:stop
143+
decreasingunitsteprange(start::HalfOddInteger, stop::Integer) = start:-1:oftype(start, stop - half(1))
144+
decreasingunitsteprange(start::Integer, stop::HalfOddInteger) = start:-1:oftype(start, stop - half(1))
145+
146+
147+
# return 1/2, possibly preserving types but not being too fussy
148+
_onehalf(x) = onehalf(x)
149+
_onehalf(::Union{StaticInt, StaticFloat64}) = static(0.5)
150+
_onehalf(::Integer) = half(Odd(1))
151+
152+
# return -1/2, possibly preserving types but not being too fussy
153+
_minonehalf(x) = -onehalf(x)
154+
_minonehalf(@nospecialize(_::Union{StaticInt, StaticFloat64})) = static(-0.5)
155+
_minonehalf(::Integer) = half(Odd(-1))
156+
157+
isapproxminhalf(a) = a _minonehalf(a)
158+
isapproxminhalf(@nospecialize(a::StaticInt)) = isequalminhalf(a)
159+
isapproxminhalf(::Integer) = false
160+
161+
isequalminhalf(x) = x == _minonehalf(x)
162+
isequalminhalf(@nospecialize(a::StaticInt)) = false
163+
isequalminhalf(::Integer) = false
164+
165+
isequalhalf(x) = x == _onehalf(x)
166+
isequalhalf(@nospecialize(a::StaticInt)) = false
167+
isequalhalf(x::Integer) = false
168+
169+
isapproxinteger_addhalf(a) = !isapproxinteger(a) && isapproxinteger(a+_onehalf(a))
170+
isapproxinteger_addhalf(a::HalfOddInteger) = true
171+
isapproxinteger_addhalf(@nospecialize(a::StaticInt)) = false
172+
function isapproxinteger_addhalf(a::StaticFloat64)
173+
x = mod(a, static(1))
174+
x == _onehalf(x) || dynamic(x) 0.5
175+
end
176+
isapproxinteger_addhalf(::Integer) = false
177+
137178
include("bary.jl")
138179

139180

src/Spaces/Jacobi/Jacobi.jl

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
export Jacobi, NormalizedJacobi, Legendre, NormalizedLegendre
22

3-
43
"""
54
`Jacobi(b,a)` represents the space spanned by Jacobi polynomials `P_k^{(a,b)}`,
65
which are orthogonal with respect to the weight `(1+x)^β*(1-x)^α`
@@ -17,8 +16,15 @@ Legendre(domain = ChebyshevInterval()) = Jacobi(0,0,Domain(domain)::Domain)
1716
Legendre(s::PolynomialSpace) = Legendre(Jacobi(s))
1817
Legendre(s::Jacobi) = s.a == s.b == 0 ? s : throw(ArgumentError("can't convert $s to Legendre"))
1918
Jacobi(b::Number,a::Number,d=ChebyshevInterval()) = Jacobi(promote(b, a)..., Domain(d)::Domain)
20-
Jacobi(A::Ultraspherical) = Jacobi(order(A)-0.5,order(A)-0.5,domain(A))
21-
Jacobi(A::Chebyshev) = Jacobi(-0.5,-0.5,domain(A))
19+
function Jacobi(A::Ultraspherical)
20+
m = order(A)
21+
n = m + half(Odd(-1))
22+
Jacobi(n,n,domain(A))
23+
end
24+
function Jacobi(A::Chebyshev)
25+
n = half(Odd(-1))
26+
Jacobi(n,n,domain(A))
27+
end
2228
Jacobi(A::Jacobi) = A
2329

2430
const NormalizedJacobi{D<:Domain,R,T} = NormalizedPolynomialSpace{Jacobi{D,R,T},D,R}
@@ -44,7 +50,7 @@ end
4450

4551
function Ultraspherical(J::Jacobi)
4652
if J.a == J.b
47-
Ultraspherical(J.a+0.5,domain(J))
53+
Ultraspherical(J.a+_onehalf(J.a),domain(J))
4854
else
4955
error("Cannot construct Ultraspherical with a=$(J.a) and b=$(J.b)")
5056
end
@@ -62,8 +68,6 @@ convert(::Type{Jacobi{D,R1,T1}},J::Jacobi{D,R2,T2}) where {D,R1,R2,T1,T2} =
6268
compare_orders((Aa, Ba)::NTuple{2,Number}, (Ab, Bb)::NTuple{2,Number}) = compare_orders(Aa, Ba) && compare_orders(Ab, Bb)
6369
spacescompatible(a::Jacobi, b::Jacobi) = compare_orders((a.a, b.a), (a.b, b.b)) && domainscompatible(a,b)
6470

65-
isapproxinteger_addhalf(a) = !isapproxinteger(a) && isapproxinteger(a+0.5)
66-
isapproxinteger_addhalf(::Integer) = false
6771
function canonicalspace(S::Jacobi)
6872
if isapproxinteger_addhalf(S.a) && isapproxinteger_addhalf(S.b)
6973
Chebyshev(domain(S))

src/Spaces/Jacobi/JacobiOperators.jl

Lines changed: 33 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -158,8 +158,8 @@ function Conversion(L::Jacobi,M::Jacobi)
158158
# Conversion(L, M) == Conversion(J, M) * Conversion(L, J)
159159
# Conversion(L, J) = Conversion(Jacobi(L.b, L.a, dm), Jacobi(M.b, L.a, dm))
160160
# Conversion(J, M) = Conversion(Jacobi(M.b, L.a, dm), Jacobi(M.b, M.a, dm))
161-
CLJ = [ConcreteConversion(Jacobi(b-1,L.a,dm), Jacobi(b, L.a, dm)) for b in M.b:-1:L.b+1]
162-
CJM = [ConcreteConversion(Jacobi(M.b,a-1,dm), Jacobi(M.b, a, dm)) for a in M.a:-1:L.a+1]
161+
CLJ = [ConcreteConversion(Jacobi(b-1,L.a,dm), Jacobi(b, L.a, dm)) for b in decreasingunitsteprange(M.b, L.b+1)]
162+
CJM = [ConcreteConversion(Jacobi(M.b,a-1,dm), Jacobi(M.b, a, dm)) for a in decreasingunitsteprange(M.a, L.a+1)]
163163
C = [CJM; CLJ]
164164
return ConversionWrapper(TimesOperator(C))
165165
end
@@ -221,102 +221,98 @@ end
221221

222222
# Assume m is compatible
223223

224-
function Conversion(A::PolynomialSpace,B::Jacobi)
224+
function Conversion(A::PolynomialSpace, B::Jacobi)
225225
@assert domain(A) == domain(B)
226226
J = Jacobi(A)
227227
J == B ? ConcreteConversion(A,B) :
228-
ConversionWrapper(TimesOperator(Conversion(J,B),Conversion(A,J)))
228+
ConversionWrapper(SpaceOperator(TimesOperator(Conversion(J,B),Conversion(A,J)), A, B))
229229
end
230230

231-
function Conversion(A::Jacobi,B::PolynomialSpace)
231+
function Conversion(A::Jacobi, B::PolynomialSpace)
232232
@assert domain(A) == domain(B)
233233
J = Jacobi(B)
234234
J == A ? ConcreteConversion(A,B) :
235-
ConversionWrapper(TimesOperator(Conversion(J,B),Conversion(A,J)))
235+
ConversionWrapper(SpaceOperator(TimesOperator(Conversion(J,B),Conversion(A,J)), A, B))
236236
end
237237

238-
isequalminhalf(x) = x == -0.5
239-
isequalminhalf(@nospecialize ::Integer) = false
240-
241-
function Conversion(A::Jacobi,B::Chebyshev)
238+
function Conversion(A::Jacobi, B::Chebyshev)
242239
@assert domain(A) == domain(B)
243240
if isequalminhalf(A.a) && isequalminhalf(A.b)
244241
ConcreteConversion(A,B)
245242
elseif A.a == A.b == 0
246-
ConversionWrapper(
247-
SpaceOperator(
248-
ConcreteConversion(Ultraspherical(1//2),B),
249-
A,B))
243+
ConversionWrapper(SpaceOperator(ConcreteConversion(Ultraspherical(A), B), A, B))
250244
elseif A.a == A.b
251245
US = Ultraspherical(A)
252-
ConversionWrapper(Conversion(US,B)*ConcreteConversion(A,US))
246+
ConversionWrapper(SpaceOperator(TimesOperator(Conversion(US,B), ConcreteConversion(A,US)), A, B))
253247
else
254248
J = Jacobi(B)
255249
ConcreteConversion(J,B)*Conversion(A,J)
256250
end
257251
end
258252

259-
function Conversion(A::Chebyshev,B::Jacobi)
253+
function Conversion(A::Chebyshev, B::Jacobi)
260254
@assert domain(A) == domain(B)
261255
if isequalminhalf(B.a) && isequalminhalf(B.b)
262256
ConcreteConversion(A,B)
263257
elseif B.a == B.b == 0
264-
ConversionWrapper(
265-
SpaceOperator(
266-
ConcreteConversion(A,Ultraspherical(1//2,domain(B))),
267-
A,B))
258+
ConversionWrapper(SpaceOperator(ConcreteConversion(A, Ultraspherical(B)), A, B))
268259
elseif B.a == B.b
269260
US = Ultraspherical(B)
270-
ConcreteConversion(US,B) * Conversion(A,US)
261+
ConversionWrapper(SpaceOperator(TimesOperator(ConcreteConversion(US,B), Conversion(A,US)), A, B))
271262
else
272263
J = Jacobi(A)
273264
Conversion(J,B)*ConcreteConversion(A,J)
274265
end
275266
end
276267

277268

278-
function Conversion(A::Jacobi,B::Ultraspherical)
269+
@inline function _Conversion(A::Jacobi, B::Ultraspherical)
279270
@assert domain(A) == domain(B)
280271
if isequalminhalf(A.a) && isequalminhalf(A.b)
281-
ConversionWrapper(Conversion(Chebyshev(domain(A)),B)*
282-
ConcreteConversion(A,Chebyshev(domain(A))))
272+
C = Chebyshev(domain(A))
273+
ConversionWrapper(SpaceOperator(
274+
TimesOperator(Conversion(C,B), ConcreteConversion(A,C)), A, B))
283275
elseif isequalminhalf(A.a - order(B)) && isequalminhalf(A.b - order(B))
284276
ConcreteConversion(A,B)
285277
elseif A.a == A.b == 0
286-
ConversionWrapper(
287-
SpaceOperator(
288-
Conversion(Ultraspherical(1//2),B),
289-
A,B))
278+
ConversionWrapper(SpaceOperator(Conversion(Ultraspherical(A), B), A, B))
290279
elseif A.a == A.b
291280
US = Ultraspherical(A)
292-
ConversionWrapper(Conversion(US,B)*ConcreteConversion(A,US))
281+
ConversionWrapper(SpaceOperator(
282+
TimesOperator(Conversion(US,B), ConcreteConversion(A,US)), A, B))
293283
else
294284
J = Jacobi(B)
295285
ConcreteConversion(J,B)*Conversion(A,J)
296286
end
297287
end
298288

299-
function Conversion(A::Ultraspherical,B::Jacobi)
289+
@inline function _Conversion(A::Ultraspherical, B::Jacobi)
300290
@assert domain(A) == domain(B)
301291
if isequalminhalf(B.a) && isequalminhalf(B.b)
302-
ConversionWrapper(ConcreteConversion(Chebyshev(domain(A)),B)*
303-
Conversion(A,Chebyshev(domain(A))))
292+
C = Chebyshev(domain(B))
293+
ConversionWrapper(SpaceOperator(
294+
TimesOperator(ConcreteConversion(C, B), Conversion(A, C)), A, B))
304295
elseif isequalminhalf(B.a - order(A)) && isequalminhalf(B.b - order(A))
305296
ConcreteConversion(A,B)
306297
elseif B.a == B.b == 0
307-
ConversionWrapper(
308-
SpaceOperator(
309-
Conversion(A,Ultraspherical(1//2,domain(B))),
310-
A,B))
298+
ConversionWrapper(SpaceOperator(Conversion(A, Ultraspherical(B)), A, B))
311299
elseif B.a == B.b
312300
US = Ultraspherical(B)
313-
ConversionWrapper(ConcreteConversion(US,B)*Conversion(A,US))
301+
ConversionWrapper(SpaceOperator(
302+
TimesOperator(ConcreteConversion(US,B), Conversion(A,US)), A, B))
314303
else
315304
J = Jacobi(A)
316305
Conversion(J,B)*ConcreteConversion(A,J)
317306
end
318307
end
319308

309+
@static if VERSION >= v"1.8"
310+
Base.@constprop :aggressive Conversion(A::Jacobi, B::Ultraspherical) = _Conversion(A, B)
311+
Base.@constprop :aggressive Conversion(A::Ultraspherical, B::Jacobi) = _Conversion(A, B)
312+
else
313+
Conversion(A::Jacobi, B::Ultraspherical) = _Conversion(A, B)
314+
Conversion(A::Ultraspherical, B::Jacobi) = _Conversion(A, B)
315+
end
320316

321317

322318

@@ -420,10 +416,6 @@ function BandedMatrix(S::SubOperator{T,ConcreteConversion{J,US,T},NTuple{2,UnitR
420416
ret
421417
end
422418

423-
424-
isapproxminhalf(a) = a -0.5
425-
isapproxminhalf(::Integer) = false
426-
427419
function union_rule(A::Jacobi,B::Jacobi)
428420
if domainscompatible(A,B)
429421
Jacobi(min(A.b,B.b),min(A.a,B.a),domain(A))

src/Spaces/Ultraspherical/UltrasphericalOperators.jl

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -133,9 +133,6 @@ end
133133

134134
## Conversion Operator
135135

136-
isequalhalf(x) = x == 0.5
137-
isequalhalf(::Integer) = false
138-
139136
function Conversion(A::Chebyshev, B::Ultraspherical)
140137
@assert domain(A) == domain(B)
141138
mB = order(B)
@@ -177,14 +174,16 @@ function Conversion(A::Ultraspherical,B::Ultraspherical)
177174
if -1 b-a 1 && (a,b) (2,1)
178175
return ConcreteConversion(A,B)
179176
elseif b-a > 1
180-
r = b:-1:a+1
177+
r = decreasingunitsteprange(b, a+1)
181178
v = [ConcreteConversion(Ultraspherical(i-1,d), Ultraspherical(i,d)) for i in r]
182179
if !(last(r) a+1)
183180
vlast = ConcreteConversion(A, Ultraspherical(last(r)-1, d))
184-
v = [v; vlast]
181+
v2 = Union{eltype(v), typeof(vlast)}[v; vlast]
182+
else
183+
v2 = v
185184
end
186185
bwsum = isapproxinteger(b-a) ? (0, 2length(v)) : (0,ℵ₀)
187-
return ConversionWrapper(TimesOperator(v, bwsum, (ℵ₀,ℵ₀), bwsum), A, B)
186+
return ConversionWrapper(TimesOperator(v2, bwsum, (ℵ₀,ℵ₀), bwsum), A, B)
188187
end
189188
end
190189
throw(ArgumentError("please implement $A$B"))

test/JacobiTest.jl

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,12 @@ using Static
122122
@test Conversion(S1, S2) * Fun(x->x^4, S1) Fun(x->x^4, S2)
123123
end
124124

125+
C = Conversion(Jacobi(Chebyshev()), Ultraspherical(1))
126+
@test C * Fun(x->x^2, Jacobi(-0.5, -0.5)) Fun(x->x^2, Ultraspherical(1))
127+
128+
C = Conversion(Ultraspherical(0.5), Jacobi(Chebyshev()))
129+
@test C * Fun(x->x^2, Ultraspherical(0.5)) Fun(x->x^2, Jacobi(Chebyshev()))
130+
125131
@testset "inference tests" begin
126132
#= Note all cases are inferred as of now,
127133
but as the situation eveolves in the future, more @inferred tests
@@ -165,6 +171,11 @@ using Static
165171

166172
CCJmix = Conversion(Chebyshev(), Jacobi(0.5,1.5))
167173
@test CCJmix * Fun(Chebyshev()) Fun(Jacobi(0.5,1.5))
174+
175+
@inferred (P -> Conversion(P, Jacobi(1,1)))(Chebyshev());
176+
if VERSION >= v"1.8"
177+
@inferred (P -> Conversion(Jacobi(1,1), P))(Ultraspherical(3));
178+
end
168179
end
169180

170181
@testset "Ultraspherical" begin
@@ -206,6 +217,12 @@ using Static
206217
@test Legendre(Ultraspherical(0.5)) == Legendre()
207218
@test_throws ArgumentError Legendre(Chebyshev())
208219
end
220+
221+
@test Jacobi(Ultraspherical(Jacobi(1,1))) === Jacobi(1,1)
222+
223+
@inferred (U -> Val(isinteger(Jacobi(U).a)))(Ultraspherical(half(Odd(1))))
224+
@inferred (U -> Val(isinteger(Jacobi(U).a)))(Ultraspherical(1))
225+
@inferred (U -> Val(isinteger(Jacobi(U).a)))(Ultraspherical(static(1)))
209226
end
210227

211228
@test ApproxFunOrthogonalPolynomials.normalization(ComplexF64, Jacobi(-0.5, -0.5), 0) pi

test/UltrasphericalTest.jl

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ using Test
44
using SpecialFunctions
55
using LinearAlgebra
66
using Static
7+
using OddEvenIntegers
8+
using HalfIntegers
79

810
@verbose @testset "Ultraspherical" begin
911
@testset "promotion" begin
@@ -40,13 +42,27 @@ using Static
4042
@inferred (() -> Conversion(Chebyshev(), Ultraspherical(2.5)))();
4143
end
4244

45+
Tallowed = Union{
46+
typeof(Conversion(Chebyshev(), Ultraspherical(half(Odd(1))))),
47+
typeof(Conversion(Chebyshev(), Ultraspherical(half(Odd(3)))))
48+
}
49+
@inferred Tallowed Conversion(Chebyshev(), Ultraspherical(half(Odd(1))));
50+
@inferred Tallowed Conversion(Chebyshev(), Ultraspherical(half(Odd(3))));
51+
4352
# Conversions between Ultraspherical should lead to a small union of types
4453
Tallowed = Union{
4554
typeof(Conversion(Ultraspherical(1), Ultraspherical(2))),
4655
typeof(Conversion(Ultraspherical(1), Ultraspherical(3)))}
4756
@inferred Tallowed Conversion(Ultraspherical(1), Ultraspherical(2));
4857
@inferred Tallowed Conversion(Ultraspherical(1), Ultraspherical(3));
4958

59+
Tallowed = Union{
60+
typeof(Conversion(Ultraspherical(1), Ultraspherical(half(Odd(3))))),
61+
typeof(Conversion(Ultraspherical(1), Ultraspherical(half(Odd(5)))))}
62+
63+
@inferred Tallowed Conversion(Ultraspherical(1), Ultraspherical(half(Odd(3))));
64+
@inferred Tallowed Conversion(Ultraspherical(1), Ultraspherical(half(Odd(5))));
65+
5066
@inferred Conversion(Ultraspherical(static(1)), Ultraspherical(static(4)));
5167
@inferred (() -> Conversion(Ultraspherical(1), Ultraspherical(4)))();
5268
@inferred (() -> Conversion(Ultraspherical(1.0), Ultraspherical(4.0)))();
@@ -63,6 +79,10 @@ using Static
6379
@test C1[1:4, 1:4] == C2[1:4, 1:4]
6480
end
6581

82+
C1 = Conversion(Chebyshev(), Ultraspherical(1.5))
83+
C2 = Conversion(Chebyshev(), Ultraspherical(half(Odd(3))))
84+
@test C1[1:4, 1:4] == C2[1:4, 1:4]
85+
6686
f = Fun(x->x^2, Ultraspherical(0.5)) # Legendre
6787
CLC = Conversion(Ultraspherical(0.5), Chebyshev())
6888
@test !isdiag(CLC)

0 commit comments

Comments
 (0)