Skip to content

Commit 62a0399

Browse files
TSGut=TSGut
and
=TSGut
authored
Use contiguous recurrence for c-related sum computations (#90)
* begin implementing * improvements * add test * mark unrelated failing tests as broken * fix an unrelated test failing * increase coverage * major speedup * Update Project.toml * flexibility improvements * bugfix * bug fix and add test * remove broken from passing test * change BigFloat conversion * some style fixes + docs * add an extra consistency test just in case --------- Co-authored-by: =TSGut <[email protected]>
1 parent fd1ec4f commit 62a0399

File tree

2 files changed

+134
-27
lines changed

2 files changed

+134
-27
lines changed

src/SemiclassicalOrthogonalPolynomials.jl

+107-24
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,9 @@ function getindex(P::SemiclassicalJacobiWeight, x::Real)
4848
end
4949

5050
function sum(P::SemiclassicalJacobiWeight{T}) where T
51-
(t,a,b,c) = map(big, map(float, (P.t,P.a,P.b,P.c)))
52-
# (t,a,b,c) = P.t, P.a, P.b, P.c
53-
return convert(T, t^c * beta(1+a,1+b) * _₂F₁general2(1+a,-c,2+a+b,1/t))
51+
(t,a,b,c) = (P.t, P.a, P.b, P.c)
52+
t,a,b,c = convert(BigFloat,t),convert(BigFloat,a),convert(BigFloat,b),convert(BigFloat,c) # This is needed at high parameter values
53+
return abs(convert(T, t^c*exp(loggamma(a+1)+loggamma(b+1)-loggamma(a+b+2)) * pFq((a+1,-c),(a+b+2, ), 1/t)))
5454
end
5555

5656
function summary(io::IO, P::SemiclassicalJacobiWeight)
@@ -258,14 +258,15 @@ function semijacobi_ldiv(P::SemiclassicalJacobi, Q)
258258
end
259259

260260
# returns conversion operator from SemiclassicalJacobi P to SemiclassicalJacobi Q in a single step via decomposition.
261-
function semijacobi_ldiv_direct(Q::SemiclassicalJacobi, P::SemiclassicalJacobi)
262-
(Q == P) && return SymTridiagonal(Ones(∞),Zeros(∞))
263-
Δa = Q.a-P.a
264-
Δb = Q.b-P.b
265-
Δc = Q.c-P.c
261+
semijacobi_ldiv_direct(Q::SemiclassicalJacobi, P::SemiclassicalJacobi) = semijacobi_ldiv_direct(Q.t, Q.a, Q.b, Q.c, P)
262+
function semijacobi_ldiv_direct(Qt, Qa, Qb, Qc, P::SemiclassicalJacobi)
263+
(Qt P.t) && (Qa P.a) && (Qb P.b) && (Qc P.c) && return SymTridiagonal(Ones(∞),Zeros(∞))
264+
Δa = Qa-P.a
265+
Δb = Qb-P.b
266+
Δc = Qc-P.c
266267
M = Diagonal(Ones(∞))
267268
if iseven(Δa) && iseven(Δb) && iseven(Δc)
268-
M = qr(P.X^(Δa÷2)*(I-P.X)^(Δb÷2)*(Q.t*I-P.X)^(Δc÷2)).R
269+
M = qr(P.X^(Δa÷2)*(I-P.X)^(Δb÷2)*(Qt*I-P.X)^(Δc÷2)).R
269270
return ApplyArray(*, Diagonal(sign.(view(M,band(0))).*Fill(abs.(1/M[1]),∞)), M)
270271
elseif isone(Δa) && iszero(Δb) && iszero(Δc) # special case (Δa,Δb,Δc) = (1,0,0)
271272
M = cholesky(P.X).U
@@ -274,37 +275,38 @@ function semijacobi_ldiv_direct(Q::SemiclassicalJacobi, P::SemiclassicalJacobi)
274275
M = cholesky(I-P.X).U
275276
return ApplyArray(*, Diagonal(Fill(1/M[1],∞)), M)
276277
elseif iszero(Δa) && iszero(Δb) && isone(Δc) # special case (Δa,Δb,Δc) = (0,0,1)
277-
M = cholesky(Q.t*I-P.X).U
278+
M = cholesky(Qt*I-P.X).U
278279
return ApplyArray(*, Diagonal(Fill(1/M[1],∞)), M)
279280
elseif isinteger(Δa) && isinteger(Δb) && isinteger(Δc)
280-
M = cholesky(Symmetric(P.X^(Δa)*(I-P.X)^(Δb)*(Q.t*I-P.X)^(Δc))).U
281+
M = cholesky(Symmetric(P.X^(Δa)*(I-P.X)^(Δb)*(Qt*I-P.X)^(Δc))).U
281282
return ApplyArray(*, Diagonal(Fill(1/M[1],∞)), M) # match normalization choice P_0(x) = 1
282283
else
283284
error("Implement")
284285
end
285286
end
286287

287288
# returns conversion operator from SemiclassicalJacobi P to SemiclassicalJacobi Q.
288-
function semijacobi_ldiv(Q::SemiclassicalJacobi, P::SemiclassicalJacobi)
289-
@assert Q.t P.t
290-
(Q == P) && return SymTridiagonal(Ones(∞),Zeros(∞))
291-
Δa = Q.a-P.a
292-
Δb = Q.b-P.b
293-
Δc = Q.c-P.c
289+
semijacobi_ldiv(Q::SemiclassicalJacobi, P::SemiclassicalJacobi) = semijacobi_ldiv(Q.t, Q.a, Q.b, Q.c, P)
290+
function semijacobi_ldiv(Qt, Qa, Qb, Qc, P::SemiclassicalJacobi)
291+
@assert Qt P.t
292+
(Qt P.t) && (Qa P.a) && (Qb P.b) && (Qc P.c) && return SymTridiagonal(Ones(∞),Zeros(∞))
293+
Δa = Qa-P.a
294+
Δb = Qb-P.b
295+
Δc = Qc-P.c
294296
if isinteger(Δa) && isinteger(Δb) && isinteger(Δc) # (Δa,Δb,Δc) are integers -> use QR/Cholesky iteratively
295297
if ((isone(Δa)||isone(Δa/2)) && iszero(Δb) && iszero(Δc)) || (iszero(Δa) && (isone(Δb)||isone(Δb/2)) && iszero(Δc)) || (iszero(Δa) && iszero(Δb) && (isone(Δc)||isone(Δc/2)))
296-
M = semijacobi_ldiv_direct(Q, P)
298+
M = semijacobi_ldiv_direct(Qt, Qa, Qb, Qc, P)
297299
elseif Δa > 0 # iterative modification by 1
298-
M = ApplyArray(*,semijacobi_ldiv_direct(Q, SemiclassicalJacobi(Q.t, Q.a-1-iseven(Δa), Q.b, Q.c)),semijacobi_ldiv(SemiclassicalJacobi(Q.t, Q.a-1-iseven(Δa), Q.b, Q.c), P))
300+
M = ApplyArray(*,semijacobi_ldiv_direct(Qt, Qa, Qb, Qc, SemiclassicalJacobi(Qt, Qa-1-iseven(Δa), Qb, Qc, P)),semijacobi_ldiv(Qt, Qa-1-iseven(Δa), Qb, Qc, P))
299301
elseif Δb > 0
300-
M = ApplyArray(*,semijacobi_ldiv_direct(Q, SemiclassicalJacobi(Q.t, Q.a, Q.b-1-iseven(Δb), Q.c)),semijacobi_ldiv(SemiclassicalJacobi(Q.t, Q.a, Q.b-1-iseven(Δb), Q.c), P))
302+
M = ApplyArray(*,semijacobi_ldiv_direct(Qt, Qa, Qb, Qc, SemiclassicalJacobi(Qt, Qa, Qb-1-iseven(Δb), Qc, P)),semijacobi_ldiv(Qt, Qa, Qb-1-iseven(Δb), Qc, P))
301303
elseif Δc > 0
302-
M = ApplyArray(*,semijacobi_ldiv_direct(Q, SemiclassicalJacobi(Q.t, Q.a, Q.b, Q.c-1-iseven(Δc))),semijacobi_ldiv(SemiclassicalJacobi(Q.t, Q.a, Q.b, Q.c-1-iseven(Δc)), P))
304+
M = ApplyArray(*,semijacobi_ldiv_direct(Qt, Qa, Qb, Qc, SemiclassicalJacobi(Qt, Qa, Qb, Qc-1-iseven(Δc), P)),semijacobi_ldiv(Qt, Qa, Qb, Qc-1-iseven(Δc), P))
303305
end
304306
else # fallback to Lancos
305307
R = SemiclassicalJacobi(P.t, mod(P.a,-1), mod(P.b,-1), mod(P.c,-1))
306308
= toclassical(R)
307-
return (P \ R) * _p0(R̃) * (R̃ \ Q)
309+
return (P \ R) * _p0(R̃) * (R̃ \ SemiclassicalJacobi(Qt, Qa, Qb, Qc))
308310
end
309311
end
310312

@@ -343,7 +345,7 @@ end
343345
\(A::SemiclassicalJacobi, B::SemiclassicalJacobi) = semijacobi_ldiv(A, B)
344346
\(A::LanczosPolynomial, B::SemiclassicalJacobi) = semijacobi_ldiv(A, B)
345347
\(A::SemiclassicalJacobi, B::LanczosPolynomial) = semijacobi_ldiv(A, B)
346-
function \(w_A::WeightedSemiclassicalJacobi, w_B::WeightedSemiclassicalJacobi)
348+
function \(w_A::WeightedSemiclassicalJacobi{T}, w_B::WeightedSemiclassicalJacobi{T}) where T
347349
wA,A = w_A.args
348350
wB,B = w_B.args
349351
@assert wA.t == wB.t == A.t == B.t
@@ -352,7 +354,8 @@ function \(w_A::WeightedSemiclassicalJacobi, w_B::WeightedSemiclassicalJacobi)
352354
Δc = B.c-A.c
353355

354356
if (wA.a == A.a) && (wA.b == A.b) && (wA.c == A.c) && (wB.a == B.a) && (wB.b == B.b) && (wB.c == B.c) && isinteger(A.a) && isinteger(A.b) && isinteger(A.c) && isinteger(B.a) && isinteger(B.b) && isinteger(B.c)
355-
k = sum(SemiclassicalJacobiWeight(B.t,B.a,B.b,B.c))/sum(SemiclassicalJacobiWeight(A.t,A.a,A.b,A.c)) # = (A \ SemiclassicalJacobiWeight(A.t,Δa,Δb,Δc))[1]
357+
# k = (A \ SemiclassicalJacobiWeight(A.t,Δa,Δb,Δc))[1]
358+
k = sumquotient(SemiclassicalJacobiWeight(B.t,B.a,B.b,B.c),SemiclassicalJacobiWeight(A.t,A.a,A.b,A.c))
356359
return (ApplyArray(*,Diagonal(Fill(k,∞)),(B \ A)))'
357360
elseif wA.a == wB.a && wA.b == wB.b && wA.c == wB.c # fallback to Christoffel–Darboux
358361
A \ B
@@ -509,6 +512,86 @@ function LazyArrays.cache_filldata!(P::SemiclassicalJacobiFamily, inds::Abstract
509512
P
510513
end
511514

515+
###
516+
# here we construct hierarchies of c weight sums by means of contiguous recurrence relations
517+
###
518+
519+
""""
520+
A SemiclassicalJacobiCWeightFamily
521+
522+
is a vector containing a sequence of weights of the form `x^a * (1-x)^b * (t-x)^c` where `a` and `b` are scalars and `c` is a range of values with integer spacing; where `x in 0..1`. It is automatically generated when calling `SemiclassicalJacobiWeight.(t,a,b,cmin:cmax)`.
523+
"""
524+
struct SemiclassicalJacobiCWeightFamily{T, C} <: AbstractVector{SemiclassicalJacobiWeight{T}}
525+
data::Vector{SemiclassicalJacobiWeight{T}}
526+
t::T
527+
a::T
528+
b::T
529+
c::C
530+
datasize::Tuple{Int}
531+
end
532+
533+
getindex(W::SemiclassicalJacobiCWeightFamily, inds) = getindex(W.data, inds)
534+
535+
size(W::SemiclassicalJacobiCWeightFamily) = (length(W.c),)
536+
537+
function SemiclassicalJacobiCWeightFamily{T}(data::Vector, t, a, b, c) where T
538+
checkrangesizes(a, b, c)
539+
SemiclassicalJacobiCWeightFamily{T,typeof(c)}(data, t, a, b, c, (length(data),))
540+
end
541+
542+
SemiclassicalJacobiCWeightFamily(t, a, b, c) = SemiclassicalJacobiCWeightFamily{float(promote_type(typeof(t),eltype(a),eltype(b),eltype(c)))}(t, a, b, c)
543+
function SemiclassicalJacobiCWeightFamily{T}(t::Number, a::Number, b::Number, c::Union{AbstractUnitRange,Number}) where T
544+
return SemiclassicalJacobiCWeightFamily{T}(SemiclassicalJacobiWeight.(t,a:a,b:b,c), t, a, b, c)
545+
end
546+
547+
Base.broadcasted(::Type{SemiclassicalJacobiWeight}, t::Number, a::Number, b::Number, c::Union{AbstractUnitRange,Number}) =
548+
SemiclassicalJacobiCWeightFamily(t, a, b, c)
549+
550+
_unweightedsemiclassicalsum = (a,b,c,t) -> pFq((a+1,-c),(a+b+2, ), 1/t)
551+
552+
function Base.broadcasted(::typeof(sum), W::SemiclassicalJacobiCWeightFamily{T}) where T
553+
a = W.a; b = W.b; c = W.c; t = W.t;
554+
cmin = minimum(c); cmax = maximum(c);
555+
@assert isinteger(cmax) && isinteger(cmin)
556+
# This is needed at high parameter values.
557+
# Manually setting setprecision(2048) allows accurate computation even for very high c.
558+
t,a,b = convert(BigFloat,t),convert(BigFloat,a),convert(BigFloat,b)
559+
F = zeros(BigFloat,cmax+1)
560+
F[1] = _unweightedsemiclassicalsum(a,b,0,t) # c=0
561+
cmax == 0 && return abs.(convert.(T,t.^c.*exp(loggamma(a+1)+loggamma(b+1)-loggamma(a+b+2)).*getindex(F,1:1)))
562+
F[2] = _unweightedsemiclassicalsum(a,b,1,t) # c=1
563+
@inbounds for n in 1:cmax-1
564+
F[n+2] = ((n-1)/t+1/t-n)/(n+a+b+2)*F[n]+(a+b+4+2*n-2-(n+a+1)/t)/(n+a+b+2)*F[n+1]
565+
end
566+
return abs.(convert.(T,t.^c.*exp(loggamma(a+1)+loggamma(b+1)-loggamma(a+b+2)).*getindex(F,W.c.+1)))
567+
end
568+
569+
""""
570+
sumquotient(wP, wQ) computes sum(wP)/sum(wQ) by taking into account cancellations, allowing more stable computations for high weight parameters.
571+
"""
572+
function sumquotient(wP::SemiclassicalJacobiWeight{T},wQ::SemiclassicalJacobiWeight{T}) where T
573+
@assert wP.t wQ.t
574+
@assert isinteger(wP.c) && isinteger(wQ.c)
575+
a = wP.a; b = wP.b; c = Int(wP.c); t = wP.t;
576+
# This is needed at high parameter values.
577+
t,a,b = convert(BigFloat,t),convert(BigFloat,a),convert(BigFloat,b)
578+
F = zeros(BigFloat,max(2,c+1))
579+
F[1] = _unweightedsemiclassicalsum(a,b,0,t) # c=0
580+
F[2] = _unweightedsemiclassicalsum(a,b,1,t) # c=1
581+
@inbounds for n in 1:c-1
582+
F[n+2] = ((n-1)/t+1/t-n)/(n+a+b+2)*F[n]+(a+b+4+2*n-2-(n+a+1)/t)/(n+a+b+2)*F[n+1]
583+
end
584+
a = wQ.a; b = wQ.b; c = Int(wQ.c);
585+
t,a,b = convert(BigFloat,t),convert(BigFloat,a),convert(BigFloat,b)
586+
G = zeros(BigFloat,max(2,c+1))
587+
G[1] = _unweightedsemiclassicalsum(a,b,0,t) # c=0
588+
G[2] = _unweightedsemiclassicalsum(a,b,1,t) # c=1
589+
@inbounds for n in 1:c-1
590+
G[n+2] = ((n-1)/t+1/t-n)/(n+a+b+2)*G[n]+(a+b+4+2*n-2-(n+a+1)/t)/(n+a+b+2)*G[n+1]
591+
end
592+
return abs.(convert.(T,t.^(Int(wP.c)-c).*exp(loggamma(wP.a+1)+loggamma(wP.b+1)-loggamma(wP.a+wP.b+2)-loggamma(a+1)-loggamma(b+1)+loggamma(a+b+2))*F[Int(wP.c)+1]/G[c+1]))
593+
end
594+
512595
include("neg1c.jl")
513596

514597
end

test/runtests.jl

+27-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
using SemiclassicalOrthogonalPolynomials
2-
using ClassicalOrthogonalPolynomials, ContinuumArrays, BandedMatrices, QuasiArrays, Test, LazyArrays, FillArrays, LinearAlgebra
2+
using ClassicalOrthogonalPolynomials, ContinuumArrays, BandedMatrices, QuasiArrays, Test, LazyArrays, FillArrays, LinearAlgebra, SpecialFunctions, HypergeometricFunctions
33
import BandedMatrices: _BandedMatrix
44
import SemiclassicalOrthogonalPolynomials: op_lowering, RaisedOP, jacobiexpansion, semijacobi_ldiv_direct
55
import ClassicalOrthogonalPolynomials: recurrencecoefficients, orthogonalityweight, symtridiagonalize
@@ -354,8 +354,7 @@ end
354354
end
355355

356356
@testset "conversion" begin
357-
D = legendre(0..1) \ P
358-
@test (P \ legendre(0..1))[1:10,1:10] == inv(Matrix(D[1:10,1:10]))
357+
@test (P \ legendre(0..1))[1:10,1:10] == inv(Matrix((legendre(0..1) \ P)[1:10,1:10]))
359358
@test_broken (P \ Weighted(P)) == (Weighted(P) \ P) == Eye(∞)
360359
end
361360
end
@@ -586,5 +585,30 @@ end
586585
L'L
587586
end
588587

588+
@testset "Contiguous computation of sums of weights" begin
589+
@testset "basics" begin
590+
W = SemiclassicalJacobiWeight.(1.1,2,3,0:100)
591+
@test W isa SemiclassicalOrthogonalPolynomials.SemiclassicalJacobiCWeightFamily
592+
@test W[1] isa SemiclassicalJacobiWeight
593+
@test size(W) == (101,)
594+
end
595+
@testset "consistency" begin
596+
# this computes it in the explicit way
597+
Wcomp = SemiclassicalJacobiWeight.(1.1,2:2,3:3,3:100);
598+
@time scomp = sum.(Wcomp);
599+
# this uses the contiguous recurrence
600+
W = SemiclassicalJacobiWeight.(1.1,2,3,3:100);
601+
@time s = sum.(W);
602+
# consistency
603+
@test maximum(abs.(s - scomp)) 1e-15
604+
# c = 0
605+
t, a, b, c = 1.1, 1, 1, 0
606+
@test sum.(SemiclassicalJacobiWeight.(t,a,b,c:c))[1] t^c * beta(1+a,1+b) * _₂F₁(1+a,-c,2+a+b,1/t)
607+
# c = 30
608+
t, a, b, c = 1.23, 2, 3, 30
609+
@test sum.(SemiclassicalJacobiWeight.(t,a,b,c:c))[1] t^c * beta(1+a,1+b) * _₂F₁(1+a,-c,2+a+b,1/t)
610+
end
611+
end
612+
589613
include("test_derivative.jl")
590614
include("test_neg1c.jl")

0 commit comments

Comments
 (0)