Skip to content

Commit 52bbce9

Browse files
TSGutdlfivefifty
andauthored
Lowering connection cfs from raising connection cfs (#112)
* lowering works * fix bugs, add QR lowering too * change convention, implement lowering * X^1 = X * Update src/SemiclassicalOrthogonalPolynomials.jl Co-authored-by: Sheehan Olver <[email protected]> * Update src/SemiclassicalOrthogonalPolynomials.jl Co-authored-by: Sheehan Olver <[email protected]> * consistency change * bugfix * Update ci.yml * increase test coverage --------- Co-authored-by: Sheehan Olver <[email protected]>
1 parent 54e43e4 commit 52bbce9

File tree

4 files changed

+174
-51
lines changed

4 files changed

+174
-51
lines changed

.github/workflows/ci.yml

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,20 @@ jobs:
2020
fail-fast: false
2121
matrix:
2222
version:
23-
- '1'
23+
- '1.10'
2424
os:
2525
- ubuntu-latest
2626
- macOS-latest
2727
- windows-latest
2828
arch:
2929
- x64
3030
steps:
31-
- uses: actions/checkout@v2
32-
- uses: julia-actions/setup-julia@v1
31+
- uses: actions/checkout@v4
32+
- uses: julia-actions/setup-julia@v2
3333
with:
3434
version: ${{ matrix.version }}
3535
arch: ${{ matrix.arch }}
36-
- uses: actions/cache@v1
36+
- uses: actions/cache@v4
3737
env:
3838
cache-name: cache-artifacts
3939
with:
@@ -46,6 +46,7 @@ jobs:
4646
- uses: julia-actions/julia-buildpkg@v1
4747
- uses: julia-actions/julia-runtest@v1
4848
- uses: julia-actions/julia-processcoverage@v1
49-
- uses: codecov/codecov-action@v1
49+
- uses: codecov/codecov-action@v4
5050
with:
51+
token: ${{ secrets.CODECOV_TOKEN }}
5152
file: lcov.info

src/SemiclassicalOrthogonalPolynomials.jl

Lines changed: 109 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ function semiclassical_jacobimatrix(t, a, b, c)
144144
iszero(c) && return jacobimatrix(P)
145145
if isone(c)
146146
return cholesky_jacobimatrix(Symmetric(P \ ((t.-axes(P,1)).*P)), P)
147-
elseif isone(c/2)
147+
elseif c == 2
148148
return qr_jacobimatrix(Symmetric(P \ ((t.-axes(P,1)).*P)), P)
149149
elseif isinteger(c) && c 0 # reduce other integer c cases to hierarchy
150150
return SemiclassicalJacobi.(t, a, b, 0:Int(c))[end].X
@@ -180,19 +180,27 @@ function semiclassical_jacobimatrix(Q::SemiclassicalJacobi, a, b, c)
180180
cholesky_jacobimatrix(I-Q.X,Q)
181181
elseif iszero(Δa) && iszero(Δb) && isone(Δc)
182182
cholesky_jacobimatrix(Q.t*I-Q.X,Q)
183+
elseif isone(-Δa) && iszero(Δb) && iszero(Δc) # in these cases we currently have to reconstruct
184+
# TODO: This is re-constructing. It should instead use reverse Cholesky (or an alternative)!
185+
semiclassical_jacobimatrix(Q.t,a,b,c)
186+
elseif iszero(Δa) && isone(-Δb) && iszero(Δc)
187+
# TODO: This is re-constructing. It should instead use reverse Cholesky (or an alternative)!
188+
semiclassical_jacobimatrix(Q.t,a,b,c)
189+
elseif iszero(Δa) && iszero(Δb) && isone(-Δc)
190+
# TODO: This is re-constructing. It should instead use reverse Cholesky (or an alternative)!
191+
semiclassical_jacobimatrix(Q.t,a,b,c)
183192
elseif a > Q.a # iterative raising by 1
184193
semiclassical_jacobimatrix(SemiclassicalJacobi(Q.t, Q.a+1, Q.b, Q.c, Q), a, b, c)
185194
elseif b > Q.b
186195
semiclassical_jacobimatrix(SemiclassicalJacobi(Q.t, Q.a, Q.b+1, Q.c, Q), a, b, c)
187196
elseif c > Q.c
188197
semiclassical_jacobimatrix(SemiclassicalJacobi(Q.t, Q.a, Q.b, Q.c+1, Q), a, b, c)
189-
# TODO: Implement lowering via QL/reverse Cholesky or via inverting R?
190-
# elseif b < Q.b # iterative lowering by 1
191-
# semiclassical_jacobimatrix(SemiclassicalJacobi(Q.t, Q.a, Q.b-1, Q.c, Q), a, b, c)
192-
# elseif c < Q.c
193-
# semiclassical_jacobimatrix(SemiclassicalJacobi(Q.t, Q.a, Q.b, Q.c-1, Q), a, b, c)
194-
# elseif a < Q.a
195-
# semiclassical_jacobimatrix(SemiclassicalJacobi(Q.t, Q.a-1, Q.b, Q.c, Q), a, b, c)
198+
elseif a < Q.a # iterative lowering by 1
199+
semiclassical_jacobimatrix(SemiclassicalJacobi(Q.t, Q.a-1, Q.b, Q.c, Q), a, b, c)
200+
elseif b < Q.b
201+
semiclassical_jacobimatrix(SemiclassicalJacobi(Q.t, Q.a, Q.b-1, Q.c, Q), a, b, c)
202+
elseif c < Q.c
203+
semiclassical_jacobimatrix(SemiclassicalJacobi(Q.t, Q.a, Q.b, Q.c-1, Q), a, b, c)
196204
else
197205
error("Not Implemented")
198206
end
@@ -206,7 +214,6 @@ LanczosPolynomial(P::SemiclassicalJacobi{T}) where T =
206214
207215
gives either a mapped `Jacobi` or `LanczosPolynomial` version of `P`.
208216
"""
209-
# TODO: Use ConvertedOPs for integer special cases?
210217
toclassical(P::SemiclassicalJacobi{T}) where T = iszero(P.c) ? Normalized(jacobi(P.b, P.a, UnitInterval{T}())) : LanczosPolynomial(P)
211218

212219
copy(P::SemiclassicalJacobi) = P
@@ -231,6 +238,7 @@ jacobimatrix(P::SemiclassicalJacobi) = P.X
231238

232239
"""
233240
op_lowering(Q, y)
241+
234242
Gives the Lowering operator from OPs w.r.t. (x-y)*w(x) to Q
235243
as constructed from Chistoffel–Darboux
236244
"""
@@ -260,56 +268,111 @@ function semijacobi_ldiv(P::SemiclassicalJacobi, Q)
260268
(P \ R) * _p0(R̃) * (R̃ \ Q)
261269
end
262270

263-
# returns conversion operator from SemiclassicalJacobi P to SemiclassicalJacobi Q in a single step via decomposition.
264-
semijacobi_ldiv_direct(Q::SemiclassicalJacobi, P::SemiclassicalJacobi) = semijacobi_ldiv_direct(Q.t, Q.a, Q.b, Q.c, P)
265-
function semijacobi_ldiv_direct(Qt, Qa, Qb, Qc, P::SemiclassicalJacobi)
266-
(Qt P.t) && (Qa P.a) && (Qb P.b) && (Qc P.c) && return SymTridiagonal(Ones(∞),Zeros(∞))
267-
Δa = Qa-P.a
268-
Δb = Qb-P.b
269-
Δc = Qc-P.c
270-
M = Diagonal(Ones(∞))
271-
if iseven(Δa) && iseven(Δb) && iseven(Δc)
272-
M = qr(P.X^(Δa÷2)*(I-P.X)^(Δb÷2)*(Qt*I-P.X)^(Δc÷2)).R
273-
return ApplyArray(*, Diagonal(sign.(view(M,band(0))).*Fill(abs.(1/M[1]),∞)), M)
274-
elseif isone(Δa) && iszero(Δb) && iszero(Δc) # special case (Δa,Δb,Δc) = (1,0,0)
271+
"""
272+
semijacobi_ldiv_direct(Q::SemiclassicalJacobi, P::SemiclassicalJacobi)
273+
274+
Returns conversion operator from SemiclassicalJacobi `P` to SemiclassicalJacobi `Q` in a single step via decomposition.
275+
Numerically unstable if the parameter modification is large. Typically one should instead use `P \\ Q` which is equivalent to `semijacobi_ldiv(P,Q)` and proceeds step by step.
276+
"""
277+
function semijacobi_ldiv_direct(Q::SemiclassicalJacobi, P::SemiclassicalJacobi)
278+
(Q.t P.t) && (Q.a P.a) && (Q.b P.b) && (Q.c P.c) && return SquareEye{eltype(Q.t)}((axes(P,2),))
279+
Δa = Q.a-P.a
280+
Δb = Q.b-P.b
281+
Δc = Q.c-P.c
282+
# special case (Δa,Δb,Δc) = (2,0,0)
283+
if (Δa == 2) && iszero(Δb) && iszero(Δc)
284+
M = qr(P.X).R
285+
return ApplyArray(*, Diagonal(sign.(view(M,band(0)))/abs(M[1])), M)
286+
# special case (Δa,Δb,Δc) = (0,2,0)
287+
elseif iszero(Δa) && (Δb == 2) && iszero(Δc)
288+
M = qr(I-P.X).R
289+
return ApplyArray(*, Diagonal(sign.(view(M,band(0)))/abs(M[1])), M)
290+
# special case (Δa,Δb,Δc) = (0,0,2)
291+
elseif iszero(Δa) && iszero(Δb) && (Δc == 2)
292+
M = qr(Q.t*I-P.X).R
293+
return ApplyArray(*, Diagonal(sign.(view(M,band(0)))/abs(M[1])), M)
294+
# special case (Δa,Δb,Δc) = (-2,0,0)
295+
elseif (Δa == -2) && iszero(Δb) && iszero(Δc)
296+
M = qr(Q.X).R
297+
return ApplyArray(\, M, Diagonal(sign.(view(M,band(0)))*abs(M[1])))
298+
# special case (Δa,Δb,Δc) = (0,-2,0)
299+
elseif iszero(Δa) && (Δb == -2) && iszero(Δc)
300+
M = qr(I-Q.X).R
301+
return ApplyArray(\, M, Diagonal(sign.(view(M,band(0)))*abs(M[1])))
302+
# special case (Δa,Δb,Δc) = (0,0,-2)
303+
elseif iszero(Δa) && iszero(Δb) && (Δc == -2)
304+
M = qr(Q.t*I-Q.X).R
305+
return ApplyArray(\, M, Diagonal(sign.(view(M,band(0)))*abs(M[1])))
306+
# special case (Δa,Δb,Δc) = (1,0,0)
307+
elseif isone(Δa) && iszero(Δb) && iszero(Δc)
275308
M = cholesky(P.X).U
276-
return ApplyArray(*, Diagonal(Fill(1/M[1],∞)), M)
277-
elseif iszero(Δa) && isone(Δb) && iszero(Δc) # special case (Δa,Δb,Δc) = (0,1,0)
309+
return M/M[1]
310+
# special case (Δa,Δb,Δc) = (0,1,0)
311+
elseif iszero(Δa) && isone(Δb) && iszero(Δc)
278312
M = cholesky(I-P.X).U
313+
return M/M[1]
314+
# special case (Δa,Δb,Δc) = (0,0,1)
315+
elseif iszero(Δa) && iszero(Δb) && isone(Δc)
316+
M = cholesky(Q.t*I-P.X).U
317+
return M/M[1]
318+
# special case (Δa,Δb,Δc) = (-1,0,0)
319+
elseif isone(-Δa) && iszero(Δb) && iszero(Δc)
320+
M = cholesky(Q.X).U
321+
return UpperTriangular(ApplyArray(inv,M/M[1]))
322+
# special case (Δa,Δb,Δc) = (0,-1,0)
323+
elseif iszero(Δa) && isone(-Δb) && iszero(Δc)
324+
M = cholesky(I-Q.X).U
325+
return UpperTriangular(ApplyArray(inv,M/M[1]))
326+
# special case (Δa,Δb,Δc) = (0,0,-1)
327+
elseif iszero(Δa) && iszero(Δb) && isone(-Δc)
328+
M = cholesky(Q.t*I-Q.X).U
329+
return UpperTriangular(ApplyArray(inv,M/M[1]))
330+
elseif isinteger(Δa) && isinteger(Δb) && isinteger(Δc) && (Δa 0) && (Δb 0) && (Δc 0)
331+
M = cholesky(Symmetric(P.X^(Δa)*(I-P.X)^(Δb)*(Q.t*I-P.X)^(Δc))).U
279332
return ApplyArray(*, Diagonal(Fill(1/M[1],∞)), M)
280-
elseif iszero(Δa) && iszero(Δb) && isone(Δc) # special case (Δa,Δb,Δc) = (0,0,1)
281-
M = cholesky(Qt*I-P.X).U
282-
return ApplyArray(*, Diagonal(Fill(1/M[1],∞)), M)
283-
elseif isinteger(Δa) && isinteger(Δb) && isinteger(Δc)
284-
M = cholesky(Symmetric(P.X^(Δa)*(I-P.X)^(Δb)*(Qt*I-P.X)^(Δc))).U
285-
return ApplyArray(*, Diagonal(Fill(1/M[1],∞)), M) # match normalization choice P_0(x) = 1
286333
else
287-
error("Implement")
334+
error("Implement modification by ($Δa,$Δb,$Δc)")
288335
end
289336
end
290337

291-
# returns conversion operator from SemiclassicalJacobi P to SemiclassicalJacobi Q.
292-
semijacobi_ldiv(Q::SemiclassicalJacobi, P::SemiclassicalJacobi) = semijacobi_ldiv(Q.t, Q.a, Q.b, Q.c, P)
293-
function semijacobi_ldiv(Qt, Qa, Qb, Qc, P::SemiclassicalJacobi)
294-
@assert Qt P.t
295-
(Qt P.t) && (Qa P.a) && (Qb P.b) && (Qc P.c) && return SymTridiagonal(Ones(∞),Zeros(∞))
296-
Δa = Qa-P.a
297-
Δb = Qb-P.b
298-
Δc = Qc-P.c
338+
"""
339+
semijacobi_ldiv_direct(Q::SemiclassicalJacobi, P::SemiclassicalJacobi)
340+
341+
Returns conversion operator from SemiclassicalJacobi `P` to SemiclassicalJacobi `Q`. Integer distances are covered by decomposition methods, for non-integer cases a Lanczos fallback is attempted.
342+
"""
343+
function semijacobi_ldiv(Q::SemiclassicalJacobi, P::SemiclassicalJacobi)
344+
@assert Q.t P.t
345+
T = promote_type(eltype(Q), eltype(P))
346+
(Q.t P.t) && (Q.a P.a) && (Q.b P.b) && (Q.c P.c) && return return SquareEye{eltype(Q.t)}((axes(P,2),))
347+
Δa = Q.a-P.a
348+
Δb = Q.b-P.b
349+
Δc = Q.c-P.c
299350
if isinteger(Δa) && isinteger(Δb) && isinteger(Δc) # (Δa,Δb,Δc) are integers -> use QR/Cholesky iteratively
300-
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)))
301-
M = semijacobi_ldiv_direct(Qt, Qa, Qb, Qc, P)
302-
elseif Δa > 0 # iterative modification by 1
303-
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))
351+
if ((isone(abs(Δa))||(Δa == 2)) && iszero(Δb) && iszero(Δc)) || (iszero(Δa) && (isone(abs(Δb))||(Δb == 2)) && iszero(Δc)) || (iszero(Δa) && iszero(Δb) && (isone(abs(Δc))||(Δc == 2)))
352+
return semijacobi_ldiv_direct(Q, P)
353+
elseif Δa > 0 # iterative raising by 1
354+
QQ = SemiclassicalJacobi(Q.t, Q.a-1-iseven(Δa), Q.b, Q.c, P)
355+
return ApplyArray(*,semijacobi_ldiv_direct(Q, QQ),semijacobi_ldiv(QQ, P))
304356
elseif Δb > 0
305-
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))
357+
QQ = SemiclassicalJacobi(Q.t, Q.a, Q.b-1-iseven(Δb), Q.c, P)
358+
return ApplyArray(*,semijacobi_ldiv_direct(Q, QQ),semijacobi_ldiv(QQ, P))
306359
elseif Δc > 0
307-
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))
360+
QQ = SemiclassicalJacobi(Q.t, Q.a, Q.b, Q.c-1-iseven(Δc), P)
361+
return ApplyArray(*,semijacobi_ldiv_direct(Q, QQ),semijacobi_ldiv(QQ, P))
362+
elseif Δa < 0 # iterative lowering by 1
363+
QQ = SemiclassicalJacobi(Q.t, Q.a+1+iseven(Δa), Q.b, Q.c, P)
364+
return ApplyArray(*,semijacobi_ldiv_direct(Q, QQ),semijacobi_ldiv(QQ, P))
365+
elseif Δb < 0
366+
QQ = SemiclassicalJacobi(Q.t, Q.a, Q.b+1+iseven(Δb), Q.c, P)
367+
return ApplyArray(*,semijacobi_ldiv_direct(Q, QQ),semijacobi_ldiv(QQ, P))
368+
elseif Δc < 0
369+
QQ = SemiclassicalJacobi(Q.t, Q.a, Q.b, Q.c+1+iseven(Δc), P)
370+
return ApplyArray(*,semijacobi_ldiv_direct(Q, QQ),semijacobi_ldiv(QQ, P))
308371
end
309372
else # fallback to Lancos
310373
R = SemiclassicalJacobi(P.t, mod(P.a,-1), mod(P.b,-1), mod(P.c,-1))
311374
= toclassical(R)
312-
return (P \ R) * _p0(R̃) * (R̃ \ SemiclassicalJacobi(Qt, Qa, Qb, Qc))
375+
return (P \ R) * _p0(R̃) * (R̃ \ Q)
313376
end
314377
end
315378

@@ -596,5 +659,6 @@ function sumquotient(wP::SemiclassicalJacobiWeight{T},wQ::SemiclassicalJacobiWei
596659
end
597660

598661
include("neg1c.jl")
662+
include("deprecated.jl")
599663

600664
end

src/deprecated.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# The old ldiv variants are still supported for now but deprecated and implemented using non-efficient constructors
2+
Base.@deprecate semijacobi_ldiv_direct(Qt, Qa, Qb, Qc, P::SemiclassicalJacobi) semijacobi_ldiv_direct(SemiclassicalJacobi(Qt, Qa, Qb, Qc), P)
3+
Base.@deprecate semijacobi_ldiv(Qt, Qa, Qb, Qc, P::SemiclassicalJacobi) semijacobi_ldiv(SemiclassicalJacobi(Qt, Qa, Qb, Qc), P)

test/runtests.jl

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -619,5 +619,60 @@ end
619619
@test sprint(show, W) == "Weighted(SemiclassicalJacobi with weight x^2.3 * (1-x)^5.3 * (2.0-x)^0.4 on 0..1)"
620620
end
621621

622+
@testset "Lowering" begin
623+
@testset "Constructing Lowered Polynomials" begin
624+
t = 1.3184
625+
P = SemiclassicalJacobi(t, 3, 3, 3)
626+
Q_1 = SemiclassicalJacobi(t, 2, 3, 3, P)
627+
Q_2 = SemiclassicalJacobi(t, 3, 3, 2, P)
628+
Q_3 = SemiclassicalJacobi(t, 3, 2, 3, P)
629+
Q_4 = SemiclassicalJacobi(t, 1, 2, 3, P)
630+
Q_5 = SemiclassicalJacobi(t, 3, 2, 1, P)
631+
Q_6 = SemiclassicalJacobi(t, 2, 2, 2, P)
632+
Q_7 = SemiclassicalJacobi(t, 1, 1, 1, P)
633+
634+
@test Q_1.X[1:20,1:20] SemiclassicalJacobi(t, 2, 3, 3).X[1:20,1:20]
635+
@test Q_2.X[1:20,1:20] SemiclassicalJacobi(t, 3, 3, 2).X[1:20,1:20]
636+
@test Q_3.X[1:20,1:20] SemiclassicalJacobi(t, 3, 2, 3).X[1:20,1:20]
637+
@test Q_4.X[1:20,1:20] SemiclassicalJacobi(t, 1, 2, 3).X[1:20,1:20]
638+
@test Q_5.X[1:20,1:20] SemiclassicalJacobi(t, 3, 2, 1).X[1:20,1:20]
639+
@test Q_6.X[1:20,1:20] SemiclassicalJacobi(t, 2, 2, 2).X[1:20,1:20]
640+
@test Q_7.X[1:20,1:20] SemiclassicalJacobi(t, 1, 1, 1).X[1:20,1:20]
641+
end
642+
@testset "Decrements of 1 via Cholesky" begin
643+
# Cholesky lowering
644+
t = 2.2
645+
P = SemiclassicalJacobi(t, 2, 2, 2)
646+
R_0 = SemiclassicalJacobi(t, 1, 2, 2) \ P
647+
R_1 = SemiclassicalJacobi(t, 2, 1, 2) \ P
648+
R_t = SemiclassicalJacobi(t, 2, 2, 1) \ P
649+
650+
R_0inv = P \ SemiclassicalJacobi(t, 1, 2, 2)
651+
R_1inv = P \ SemiclassicalJacobi(t, 2, 1, 2)
652+
R_tinv = P \ SemiclassicalJacobi(t, 2, 2, 1)
653+
654+
@test ApplyArray(inv,R_0inv)[1:20,1:20] R_0[1:20,1:20]
655+
@test ApplyArray(inv,R_1inv)[1:20,1:20] R_1[1:20,1:20]
656+
@test ApplyArray(inv,R_tinv)[1:20,1:20] R_t[1:20,1:20]
657+
end
658+
@testset "Decrements of 2 via QR" begin
659+
# Cholesky lowering
660+
t = 2.2
661+
P = SemiclassicalJacobi(t, 3, 3, 3)
662+
R_0 = SemiclassicalJacobi(t, 1, 3, 3) \ P
663+
R_1 = SemiclassicalJacobi(t, 3, 1, 3) \ P
664+
R_t = SemiclassicalJacobi(t, 3, 3, 1) \ P
665+
666+
R_0inv = P \ SemiclassicalJacobi(t, 1, 3, 3)
667+
R_1inv = P \ SemiclassicalJacobi(t, 3, 1, 3)
668+
R_tinv = P \ SemiclassicalJacobi(t, 3, 3, 1)
669+
670+
@test ApplyArray(inv,R_0inv)[1:20,1:20] R_0[1:20,1:20]
671+
@test ApplyArray(inv,R_1inv)[1:20,1:20] R_1[1:20,1:20]
672+
@test ApplyArray(inv,R_tinv)[1:20,1:20] R_t[1:20,1:20]
673+
end
674+
end
675+
622676
include("test_derivative.jl")
623-
include("test_neg1c.jl")
677+
include("test_neg1c.jl")
678+

0 commit comments

Comments
 (0)