Skip to content

Commit fcfe37c

Browse files
authored
Add convenience functions for symmetric eigen BandedMatrices (#240)
* Add convenience functions for symmetric eigen BandedMatrices * update docstring to mention n * update docstring * Add Ultraspherical test * Add SymmetricEigensystem * eigvals for SymmetricEigensystem * Don't restrict domainspace * access domains for PiecewiseSpace using components(d) * Add SkewSymmetricEigensystem * update docstrings * move docstring * Specify QuotientSpace type in SymmetricEigensystem constructor * remove symmetric_bandmatrices_eigen * collect matrices in skew-symmetric system
1 parent c5ca70e commit fcfe37c

File tree

6 files changed

+191
-99
lines changed

6 files changed

+191
-99
lines changed

Project.toml

Lines changed: 2 additions & 2 deletions
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.26"
3+
version = "0.6.27"
44

55
[deps]
66
ApproxFunBase = "fbd15aa5-315a-5a7d-a8a4-24992e37be05"
@@ -20,7 +20,7 @@ StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
2020
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
2121

2222
[compat]
23-
ApproxFunBase = "0.8.21"
23+
ApproxFunBase = "0.8.22"
2424
ApproxFunBaseTest = "0.1"
2525
Aqua = "0.5"
2626
BandedMatrices = "0.16, 0.17"

src/ApproxFunOrthogonalPolynomials.jl

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ import ApproxFunBase: Fun, SubSpace, WeightSpace, NoSpace, HeavisideSpace,
4444
ℓ⁰, recα, recβ, recγ, ℵ₀, ∞, RectDomain,
4545
assert_integer, supportsinplacetransform, ContinuousSpace, SpecialEvalPtType,
4646
isleftendpoint, isrightendpoint, evaluation_point, boundaryfn, ldiffbc, rdiffbc,
47-
LeftEndPoint, RightEndPoint, normalizedspace
47+
LeftEndPoint, RightEndPoint, normalizedspace, promotedomainspace
4848

4949
import DomainSets: Domain, indomain, UnionDomain, FullSpace, Point,
5050
Interval, ChebyshevInterval, boundary, rightendpoint, leftendpoint,
@@ -82,7 +82,7 @@ import SpecialFunctions: erfcx, dawson,
8282

8383
using StaticArrays: SVector
8484

85-
import LinearAlgebra: isdiag
85+
import LinearAlgebra: isdiag, eigvals
8686

8787
points(d::IntervalOrSegmentDomain{T},n::Integer) where {T} =
8888
_maybefromcanonical(d, chebyshevpoints(float(real(eltype(T))), n)) # eltype to handle point
@@ -140,6 +140,7 @@ include("Spaces/Spaces.jl")
140140
include("roots.jl")
141141
include("specialfunctions.jl")
142142
include("fastops.jl")
143+
include("symeigen.jl")
143144
include("show.jl")
144145

145146
end

src/Spaces/Jacobi/Jacobi.jl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,18 @@ end
1414
Jacobi(b::T,a::T,d::Domain) where {T<:Number} =
1515
Jacobi{typeof(d),promote_type(T,real(prectype(d)))}(b, a, d)
1616
Legendre(domain = ChebyshevInterval()) = Jacobi(0,0,Domain(domain)::Domain)
17+
Legendre(s::PolynomialSpace) = Legendre(Jacobi(s))
18+
Legendre(s::Jacobi) = s.a == s.b == 0 ? s : throw(ArgumentError("can't convert $s to Legendre"))
1719
Jacobi(b::Number,a::Number,d=ChebyshevInterval()) = Jacobi(promote(b, a)..., Domain(d)::Domain)
1820
Jacobi(A::Ultraspherical) = Jacobi(order(A)-0.5,order(A)-0.5,domain(A))
1921
Jacobi(A::Chebyshev) = Jacobi(-0.5,-0.5,domain(A))
22+
Jacobi(A::Jacobi) = A
2023

2124
const NormalizedJacobi{D<:Domain,R,T} = NormalizedPolynomialSpace{Jacobi{D,R,T},D,R}
2225
NormalizedJacobi(s...) = NormalizedPolynomialSpace(Jacobi(s...))
26+
NormalizedJacobi(s::Space) = NormalizedPolynomialSpace(Jacobi(_stripnorm(s)))
2327
NormalizedLegendre(d...) = NormalizedPolynomialSpace(Legendre(d...))
28+
NormalizedLegendre(s::Space) = NormalizedPolynomialSpace(Legendre(_stripnorm(s)))
2429

2530
function normalization(::Type{T}, sp::Jacobi, k::Int) where T
2631
x = FastTransforms.Anαβ(k, sp.a, sp.b)

src/symeigen.jl

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
export bandmatrices_eigen, SymmetricEigensystem, SkewSymmetricEigensystem
2+
3+
abstract type EigenSystem end
4+
5+
"""
6+
SymmetricEigensystem(L::Operator, B::Operator, QuotientSpaceType::Type = QuotientSpace)
7+
8+
Represent the eigensystem `L v = λ v` subject to `B v = 0`, where `L` is self-adjoint with respect to the standard `L2`
9+
inner product given the boundary conditions `B`.
10+
11+
!!! note
12+
No tests are performed to assert that the operator `L` is self-adjoint, and it's the user's responsibility
13+
to ensure that the operators are compliant.
14+
15+
The optional argument `QuotientSpaceType` specifies the type of space to be used to denote the quotient space in the basis
16+
recombination process. In most cases, the default choice of `QuotientSpace` is a good one. In specific instances where `B`
17+
is rank-deficient (e.g. it contains a column of zeros,
18+
which typically happens if one of the basis elements already satiafies the boundary conditions),
19+
one may need to choose this to be a `PathologicalQuotientSpace`.
20+
21+
!!! note
22+
No checks on the rank of `B` are carried out, and it's up to the user to specify the correct type.
23+
"""
24+
SymmetricEigensystem
25+
26+
"""
27+
SkewSymmetricEigensystem(L::Operator, B::Operator, QuotientSpaceType::Type = QuotientSpace)
28+
29+
Represent the eigensystem `L v = λ v` subject to `B v = 0`, where `L` is skew-symmetric with respect to the standard `L2`
30+
inner product given the boundary conditions `B`.
31+
32+
!!! note
33+
No tests are performed to assert that the operator `L` is skew-symmetric, and it's the user's responsibility
34+
to ensure that the operators are compliant.
35+
36+
The optional argument `QuotientSpaceType` specifies the type of space to be used to denote the quotient space in the basis
37+
recombination process. In most cases, the default choice of `QuotientSpace` is a good one. In specific instances where `B`
38+
is rank-deficient (e.g. it contains a column of zeros,
39+
which typically happens if one of the basis elements already satiafies the boundary conditions),
40+
one may need to choose this to be a `PathologicalQuotientSpace`.
41+
42+
!!! note
43+
No checks on the rank of `B` are carried out, and it's up to the user to specify the correct type.
44+
"""
45+
SkewSymmetricEigensystem
46+
47+
for SET in (:SymmetricEigensystem, :SkewSymmetricEigensystem)
48+
@eval begin
49+
struct $SET{LT,QT} <: EigenSystem
50+
L :: LT
51+
Q :: QT
52+
53+
function $SET(L, B, ::Type{QST} = QuotientSpace) where {QST}
54+
L2, B2 = promotedomainspace((L, B))
55+
if isambiguous(domainspace(L))
56+
throw(ArgumentError("could not detect spaces, please specify the domain spaces for the operators"))
57+
end
58+
59+
QS = QST(B2)
60+
S = domainspace(L2)
61+
Q = Conversion(QS, S)
62+
new{typeof(L2),typeof(Q)}(L2, Q)
63+
end
64+
end
65+
end
66+
end
67+
68+
function basis_recombination(SE::EigenSystem)
69+
L, Q = SE.L, SE.Q
70+
S = domainspace(L)
71+
D1 = Conversion_normalizedspace(S)
72+
D2 = Conversion_normalizedspace(S, Val(:backward))
73+
R = D1*Q;
74+
C = Conversion(S, rangespace(L))
75+
P = cache(PartialInverseOperator(C, (0, bandwidth(L, 1) + bandwidth(R, 1) + bandwidth(C, 2))));
76+
A = R'D1*P*L*D2*R
77+
BB = R'R;
78+
79+
return A, BB
80+
end
81+
82+
"""
83+
bandmatrices_eigen(S::Union{SymmetricEigensystem, SkewSymmetricEigensystem}, n::Integer)
84+
85+
Recast the symmetric/skew-symmetric eigenvalue problem `L v = λ v` subject to `B v = 0` to the generalized
86+
eigenvalue problem `SA v = λ SB v`, where `SA` and `SB` are banded operators, and
87+
return the `n × n` matrix representations of `SA` and `SB`.
88+
If `S isa SymmetricEigensystem`, the returned matrices will be `Symmetric`.
89+
90+
!!! note
91+
No tests are performed to assert that the system is symmetric/skew-symmetric, and it's the user's responsibility
92+
to ensure that the operators are compliant.
93+
"""
94+
function bandmatrices_eigen(S::SymmetricEigensystem, n::Integer)
95+
A, B = _bandmatrices_eigen(S, n)
96+
SA = Symmetric(A, :L)
97+
SB = Symmetric(B, :L)
98+
return SA, SB
99+
end
100+
101+
function bandmatrices_eigen(S::EigenSystem, n::Integer)
102+
A, B = _bandmatrices_eigen(S, n)
103+
A2 = tril(A, bandwidth(A,1))
104+
B2 = tril(B, bandwidth(B,1))
105+
Matrix(A2), Matrix(B2)
106+
end
107+
108+
function _bandmatrices_eigen(S::EigenSystem, n::Integer)
109+
AA, BB = basis_recombination(S)
110+
A = AA[1:n,1:n]
111+
B = BB[1:n,1:n]
112+
return A, B
113+
end
114+
115+
function eigvals(S::EigenSystem, n::Integer)
116+
SA, SB = bandmatrices_eigen(S, n)
117+
eigvals(SA, SB)
118+
end

test/EigTest.jl

Lines changed: 41 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ using ApproxFunOrthogonalPolynomials
22
using ApproxFunBase
33
using ApproxFunBase: bandwidth
44
using BandedMatrices
5+
using LinearAlgebra
56
using SpecialFunctions
67
using Test
78

@@ -11,28 +12,24 @@ using Test
1112
# -𝒟² u = λu, u'(±1) = 0.
1213
#
1314
d = Segment(-1..1)
14-
S = Ultraspherical(0.5, d)
15-
NS = NormalizedPolynomialSpace(S)
16-
L = -Derivative(S, 2)
17-
C = Conversion(domainspace(L), rangespace(L))
18-
B = Neumann(S)
19-
QS = QuotientSpace(B)
20-
Q = Conversion(QS, S)
21-
D1 = Conversion(S, NS)
22-
D2 = Conversion(NS, S)
23-
R = D1*Q
24-
P = cache(PartialInverseOperator(C, (0, bandwidth(L, 1) + bandwidth(R, 1) + bandwidth(C, 2))))
25-
A = R'D1*P*L*D2*R
26-
B = R'R
27-
28-
# Currently a hack to avoid a LAPACK calling bug with a pencil (A, B)
29-
# with smaller bandwidth in A.
30-
n = 50
31-
SA = Symmetric(Matrix(A[1:n,1:n]), :L)
32-
SB = Symmetric(Matrix(B[1:n,1:n]), :L)
33-
λ = eigvals(SA, SB)
34-
35-
@test λ[1:round(Int, 2n/5)] ^2/4).*(0:round(Int, 2n/5)-1).^2
15+
for S in (Ultraspherical(0.5, d), Legendre(d))
16+
L = -Derivative(S, 2)
17+
B = Neumann(S)
18+
19+
n = 50
20+
Seig = SymmetricEigensystem(L, B)
21+
SA, SB = bandmatrices_eigen(Seig, n)
22+
23+
# A hack to avoid a BandedMatrices bug on Julia v1.6, with a pencil (A, B)
24+
# with smaller bandwidth in A.
25+
λ = if VERSION >= v"1.8"
26+
eigvals(SA, SB)
27+
else
28+
eigvals(Symmetric(Matrix(SA)), Symmetric(Matrix(SB)))
29+
end
30+
31+
@test λ[1:round(Int, 2n/5)] ^2/4).*(0:round(Int, 2n/5)-1).^2
32+
end
3633
end
3734

3835
@testset "Schrödinger with piecewise-linear potential with Dirichlet boundary conditions" begin
@@ -42,25 +39,14 @@ using Test
4239
# where V = 100|x|.
4340
#
4441
d = Segment(-1..0)Segment(0..1)
45-
S = PiecewiseSpace(Ultraspherical.(0.5, d.domains))
46-
NS = PiecewiseSpace(NormalizedUltraspherical.(0.5, d.domains))
42+
S = PiecewiseSpace(Ultraspherical.(0.5, components(d)))
4743
V = 100Fun(abs, S)
4844
L = -Derivative(S, 2) + V
49-
C = Conversion(domainspace(L), rangespace(L))
5045
B = [Dirichlet(S); continuity(S, 0:1)]
51-
QS = QuotientSpace(B)
52-
Q = Conversion(QS, S)
53-
D1 = Conversion(S, NS)
54-
D2 = Conversion(NS, S)
55-
R = D1*Q
56-
P = cache(PartialInverseOperator(C, (0, bandwidth(L, 1) + bandwidth(R, 1) + bandwidth(C, 2))))
57-
A = R'D1*P*L*D2*R
58-
B = R'R
5946

47+
Seig = SymmetricEigensystem(L, B)
6048
n = 100
61-
SA = Symmetric(A[1:n,1:n], :L)
62-
SB = Symmetric(B[1:n,1:n], :L)
63-
λ = eigvals(SA, SB)
49+
λ = eigvals(Seig, n)
6450

6551
@test λ[1] parse(BigFloat, "2.19503852085715200848808942880214615154684642693583513254593767079468401198338e+01")
6652
end
@@ -71,26 +57,17 @@ using Test
7157
#
7258
# where V = 1000[χ_[-1,-1/2](x) + χ_[1/2,1](x)].
7359
#
74-
d = Segment(-1..(-0.5))Segment(-0.5..0.5)Segment(0.5..1)
75-
S = PiecewiseSpace(Ultraspherical.(0.5, d.domains))
76-
NS = PiecewiseSpace(NormalizedUltraspherical.(0.5, d.domains))
60+
d = Segment(-1..(-0.5)) Segment(-0.5..0.5) Segment(0.5..1)
61+
S = PiecewiseSpace(Ultraspherical.(0.5, components(d)))
62+
7763
V = Fun(x->abs(x) 1/2 ? 1000 : 0, S)
7864
L = -Derivative(S, 2) + V
79-
C = Conversion(domainspace(L), rangespace(L))
8065
B = [Dirichlet(S); continuity(S, 0:1)]
81-
QS = QuotientSpace(B)
82-
Q = Conversion(QS, S)
83-
D1 = Conversion(S, NS)
84-
D2 = Conversion(NS, S)
85-
R = D1*Q
86-
P = cache(PartialInverseOperator(C, (0, bandwidth(L, 1) + bandwidth(R, 1) + bandwidth(C, 2))))
87-
A = R'D1*P*L*D2*R
88-
B = R'R
66+
67+
Seig = SymmetricEigensystem(L, B)
8968

9069
n = 150
91-
SA = Symmetric(A[1:n,1:n], :L)
92-
SB = Symmetric(B[1:n,1:n], :L)
93-
λ = eigvals(SA, SB)
70+
λ = eigvals(Seig, n)
9471
# From Lee--Greengard (1997).
9572
λtrue = [2.95446;5.90736;8.85702;11.80147].^2
9673
@test norm((λ[1:4] - λtrue)./λ[1:4]) < 1e-5
@@ -102,36 +79,27 @@ using Test
10279
#
10380
# where V = x + 100δ(x-0.25).
10481
#
105-
d = Segment(-1..0.25)Segment(0.25..1)
106-
S = PiecewiseSpace(Ultraspherical.(0.5, d.domains))
107-
NS = PiecewiseSpace(NormalizedUltraspherical.(0.5, d.domains))
82+
d = Segment(-1..0.25) Segment(0.25..1)
83+
S = PiecewiseSpace(Ultraspherical.(0.5, components(d)))
10884
V = Fun(identity, S)
10985
L = -Derivative(S, 2) + V
110-
C = Conversion(domainspace(L), rangespace(L))
86+
11187
B4 = zeros(Operator{ApproxFunBase.prectype(S)}, 1, 2)
11288
B4[1, 1] = -Evaluation(component(S, 1), rightendpoint, 1) - 100*0.5*Evaluation(component(S, 1), rightendpoint)
11389
B4[1, 2] = Evaluation(component(S, 2), leftendpoint, 1) - 100*0.5*Evaluation(component(S, 2), leftendpoint)
11490
B4 = ApproxFunBase.InterlaceOperator(B4, PiecewiseSpace, ApproxFunBase.ArraySpace)
11591
B = [Evaluation(S, -1); Evaluation(S, 1) + Evaluation(S, 1, 1); continuity(S, 0); B4]
116-
QS = QuotientSpace(B)
117-
Q = Conversion(QS, S)
118-
D1 = Conversion(S, NS)
119-
D2 = Conversion(NS, S)
120-
R = D1*Q
121-
P = cache(PartialInverseOperator(C, (0, bandwidth(L, 1) + bandwidth(R, 1) + bandwidth(C, 2))))
122-
A = R'D1*P*L*D2*R
123-
B = R'R
12492

12593
n = 100
126-
SA = Symmetric(A[1:n,1:n], :L)
127-
SB = Symmetric(B[1:n,1:n], :L)
94+
Seig = SymmetricEigensystem(L, B)
95+
SA, SB = bandmatrices_eigen(Seig, n)
96+
λ, Q = eigen(SA, SB);
12897

98+
QS = QuotientSpace(B)
12999
k = 3
130-
131-
λ, Q = eigen(SA, SB);
132100
u_QS = Fun(QS, Q[:, k])
133101
u_S = Fun(u_QS, S)
134-
u = Fun(u_S, PiecewiseSpace(Chebyshev.(d.domains)))
102+
u = Fun(u_S, PiecewiseSpace(Chebyshev.(components(d))))
135103
u /= sign(u'(-1))
136104
u1, u2 = components(u)
137105

@@ -149,22 +117,13 @@ using Test
149117
#
150118
d = Segment(big(-1.0)..big(1.0))
151119
S = Ultraspherical(big(0.5), d)
152-
NS = NormalizedPolynomialSpace(S)
153120
L = -Derivative(S, 2)
154121
C = Conversion(domainspace(L), rangespace(L))
155122
B = Dirichlet(S)
156-
QS = QuotientSpace(B)
157-
Q = Conversion(QS, S)
158-
D1 = Conversion(S, NS)
159-
D2 = Conversion(NS, S)
160-
R = D1*Q
161-
P = cache(PartialInverseOperator(C, (0, bandwidth(L, 1) + bandwidth(R, 1) + bandwidth(C, 2))))
162-
A = R'D1*P*L*D2*R
163-
B = R'R
164123

165124
n = 300
166-
SA = Symmetric(A[1:n,1:n], :L)
167-
SB = Symmetric(B[1:n,1:n], :L)
125+
Seig = SymmetricEigensystem(L, B)
126+
SA, SB = bandmatrices_eigen(Seig, n)
168127
BSA = BandedMatrix(SA)
169128
BSB = BandedMatrix(SB)
170129
begin
@@ -189,21 +148,13 @@ using Test
189148
#
190149
d = Segment(-1..1)
191150
S = Ultraspherical(0.5, d)
192-
NS = NormalizedPolynomialSpace(S)
193151
Lsk = Derivative(S)
194-
Csk = Conversion(domainspace(Lsk), rangespace(Lsk))
195152
B = Evaluation(S, -1) + Evaluation(S, 1)
196-
QS = PathologicalQuotientSpace(B)
197-
Q = Conversion(QS, S)
198-
D1 = Conversion(S, NS)
199-
D2 = Conversion(NS, S)
200-
R = D1*Q
201-
Psk = cache(PartialInverseOperator(Csk, (0, 4 + bandwidth(Lsk, 1) + bandwidth(R, 1) + bandwidth(Csk, 2))))
202-
Ask = R'D1*Psk*Lsk*D2*R
203-
Bsk = R'R
153+
Seig = SkewSymmetricEigensystem(Lsk, B, PathologicalQuotientSpace)
204154

205155
n = 100
206-
λim = imag(sort!(eigvals(Matrix(tril(Ask[1:n,1:n], 1)), Matrix(tril(Bsk[1:n,1:n], 2))), by = abs))
156+
λ = eigvals(Seig, n)
157+
λim = imag(sort!(λ, by = abs))
207158

208159
@test abs.(λim[1:2:round(Int, 2n/5)]) π.*(0.5:round(Int, 2n/5)/2)
209160
@test abs.(λim[2:2:round(Int, 2n/5)]) π.*(0.5:round(Int, 2n/5)/2)

0 commit comments

Comments
 (0)