Skip to content

Commit e065da9

Browse files
authored
Add Zernike(0,a) Jacobi matrices (#120)
* Add Jacobi matrices * Tests for Zernike jacobi matrices * export jacobimatrix * need up to date packages for symmetric matrices * increase coverage * fix broken test * using InfiniteArrays.jl in tests
1 parent 4810e9c commit e065da9

File tree

4 files changed

+178
-6
lines changed

4 files changed

+178
-6
lines changed

Project.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
2626
[compat]
2727
ArrayLayouts = "0.7, 0.8"
2828
BandedMatrices = "0.16, 0.17"
29-
BlockArrays = "0.16.7"
30-
BlockBandedMatrices = "0.11"
29+
BlockArrays = "0.16.14"
30+
BlockBandedMatrices = "0.11.5"
3131
ClassicalOrthogonalPolynomials = "0.5.1, 0.6"
3232
ContinuumArrays = "0.10"
3333
DomainSets = "0.5"

src/MultivariateOrthogonalPolynomials.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export MultivariateOrthogonalPolynomial, BivariateOrthogonalPolynomial,
3030
DunklXuDisk, DunklXuDiskWeight, WeightedDunklXuDisk,
3131
Zernike, ZernikeWeight, zerniker, zernikez,
3232
PartialDerivative, Laplacian, AbsLaplacianPower, AngularMomentum,
33-
RadialCoordinate, Weighted, Block
33+
RadialCoordinate, Weighted, Block, jacobimatrix
3434

3535
include("ModalInterlace.jl")
3636
include("disk.jl")

src/disk.jl

Lines changed: 134 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,8 +160,141 @@ getindex(Z::Zernike, xy::StaticVector{2}, B::BlockIndex{1}) = Z[RadialCoordinate
160160
getindex(Z::Zernike, xy::StaticVector{2}, B::Block{1}) = [Z[xy, B[j]] for j=1:Int(B)]
161161
getindex(Z::Zernike, xy::StaticVector{2}, JR::BlockOneTo) = mortar([Z[xy,Block(J)] for J = 1:Int(JR[end])])
162162

163+
###
164+
# Jacobi matrices
165+
###
166+
167+
# Due to excessively long lazy typing, we wrap the bands of the Zernike Jacobi matrix in its own type
168+
# The bands for X and Y are distinct
169+
struct ZernikeJacobimatrixBandsX{T} <: AbstractBlockMatrix{T}
170+
Z::Zernike{T}
171+
data::AbstractBlockMatrix{T}
172+
ZernikeJacobimatrixBandsX{T}(Z::Zernike{T}) where T = new{T}(Z, zernikejacobibandsX(Z))
173+
end
174+
struct ZernikeJacobimatrixBandsY{T} <: AbstractBlockMatrix{T}
175+
Z::Zernike{T}
176+
data::AbstractBlockMatrix{T}
177+
ZernikeJacobimatrixBandsY{T}(Z::Zernike{T}) where T = new{T}(Z, zernikejacobibandsY(Z))
178+
end
179+
180+
size(b::ZernikeJacobimatrixBandsX) = size(b.data)
181+
axes(b::ZernikeJacobimatrixBandsX) = axes(b.data)
182+
size(b::ZernikeJacobimatrixBandsY) = size(b.data)
183+
axes(b::ZernikeJacobimatrixBandsY) = axes(b.data)
184+
185+
function zernikejacobibandsX(Z::Zernike)
186+
α = Z.b # extract second basis parameter
187+
188+
k = mortar(Base.OneTo.(oneto(∞))) # k counts the the angular mode (+1)
189+
n = mortar(Fill.(oneto(∞),oneto(∞))) # n counts the block number which corresponds to the order (+1)
190+
191+
# repeatedly used for sorting
192+
keven = iseven.(k)
193+
kodd = isodd.(k)
194+
neven = iseven.(n)
195+
nodd = isodd.(n)
196+
197+
# h1-h5 are helpers for our different sorting scheme
198+
199+
## Compute super diagonal of super diagonal blocks.
200+
dufirst = neven .* (k .== 2) .* n .* (n .+ 2*α) ./ 2
201+
202+
## Compute even-block entries in super diagonal of super diagonal blocks
203+
h1 = n .- (n .+ 2 .- k .- keven .* (n .> 2)) 2
204+
dueven = ((nodd .* k) .>= 2) .* h1 .* (h1 .+ α)
205+
206+
## Compute odd-block entries in super diagonal of super diagonal blocks
207+
h2 = (n .- 2 .+ k .+ kodd .* (n .> 2)) 2
208+
duodd = (((neven .* k) .>= 2) .- (neven .* (k .== 2))) .* h2 .* (h2 .+ α)
209+
210+
## Compute even-block sub diagonal elements of super diagonal blocks
211+
h3 = n .- (k .+ 1 .+ kodd .+ n) 2
212+
dleven = ((k .<= (n .- 2)) .- ((k .< 2) .* nodd)) .* neven .* h3 .* (h3 .+ α)
213+
214+
## Compute odd-block sub diagonal elements of super diagonal blocks
215+
h4 = n .- (k .+ 1 .+ keven .+ n) 2
216+
dlodd = (k .> 1) .* (n .> 3) .* nodd .* h4 .* (h4 .+ α)
163217

218+
## Compute and add in special case odd-block sub diagonal elements of super diagonal blocks
219+
h5 = n .- 2 .+ k .+ keven
220+
dlspecial = nodd .* (k .== 1) .* h5 .* (h5 .+ 2*α) ./ 2
164221

222+
# finalize bands with explicit formula
223+
quotient = 4 .* (n .+-1)) .* (n .+ α)
224+
du = sqrt.( (dufirst .+ dueven .+ duodd ) ./ quotient)
225+
dl = sqrt.( (dleven .+ dlodd .+ dlspecial) ./ quotient)
226+
227+
return BlockHcat(
228+
BlockBroadcastArray(hcat, du, Zeros((axes(n,1),)), dl),
229+
Zeros((axes(n,1),Base.OneTo(3))),
230+
Zeros((axes(n,1),Base.OneTo(3)))
231+
)
232+
end
233+
234+
function zernikejacobibandsY(Z::Zernike)
235+
α = Z.b # extract second basis parameter
236+
237+
k = mortar(Base.OneTo.(oneto(∞))) # k counts the the angular mode (+1)
238+
n = mortar(Fill.(oneto(∞),oneto(∞))) # n counts the block number which corresponds to the order (+1)
239+
240+
# repeatedly used for sorting
241+
keven = iseven.(k)
242+
kodd = isodd.(k)
243+
neven = iseven.(n)
244+
nodd = isodd.(n)
245+
246+
# h1-h4 are helpers for our different sorting scheme
247+
248+
# first entries for all blocks
249+
h1 = (n .- nodd)
250+
l1 = (k .== 1) .* (h1 .* (h1 .+ 2*α) ./ 2)
251+
252+
# Even blocks
253+
h0 = (kodd .* ((k 2) .+ 1) .- (keven .* ((k 2) .- 1)))
254+
h2 = (k .>= 2) .* ((n 2 .- 1) .+ h0)
255+
l2 = neven .* (h2 .* (h2 .+ α))
256+
257+
# Odd blocks
258+
h3 = (n .> k .>= 2) .* (((n .+ 1) 2) .- h0)
259+
l3 = nodd .* (h3 .* (h3 .+ α))
260+
# Combine for diagonal of super diagonal block
261+
d = sqrt.((l1 .+ l2 .+ l3) ./ (4 .* (n .+-1)) .* (n .+ α)))
262+
263+
# The off-diagonals of the super diagonal block are negative, shifted versions of the diagonal with some entries skipped
264+
dl = (-1) .* (nodd .* kodd .+ neven .* keven) .* Vcat(0 , d)
265+
du = (-1) .* (nodd .* keven .+ neven .* kodd) .* d[2:end]
266+
267+
# zero blocks for banded blockarray structure
268+
z = Zeros((axes(n,1),))
269+
z5 = Zeros((axes(n,1),Base.OneTo(5)))
270+
271+
# generate and return bands
272+
return dat = BlockHcat(BlockBroadcastArray(hcat, dl, z, d, z, du), z5, z5)
273+
end
274+
275+
function getindex(b::ZernikeJacobimatrixBandsX{T},i,j) where T
276+
return BlockArrays.getindex(b.data,i,j)
277+
end
278+
function getindex(b::ZernikeJacobimatrixBandsY{T},i,j) where T
279+
return BlockArrays.getindex(b.data,i,j)
280+
end
281+
282+
function jacobimatrix(::Val{1}, Z::Zernike{T}) where T
283+
if iszero(Z.a)
284+
dat = ZernikeJacobimatrixBandsX{T}(Z)
285+
return Symmetric(BlockBandedMatrices._BandedBlockBandedMatrix(dat', axes(dat,1), (1,1), (1,1)))
286+
else
287+
error("Implement for non-zero first basis parameter.")
288+
end
289+
end
290+
function jacobimatrix(::Val{2}, Z::Zernike{T}) where T
291+
if iszero(Z.a)
292+
dat = ZernikeJacobimatrixBandsY{T}(Z)
293+
return Symmetric(BlockBandedMatrices._BandedBlockBandedMatrix(dat', axes(dat,1), (1,1), (2,2)))
294+
else
295+
error("Implement for non-zero first basis parameter.")
296+
end
297+
end
165298

166299
###
167300
# Transforms
@@ -314,4 +447,4 @@ function \(A::Zernike{T}, wB::Weighted{V,Zernike{V}}) where {T,V}
314447
c = Int(B.a - A.a + B.b - A.b)
315448
@assert iszero(B.a)
316449
ModalInterlace{TV}((Normalized.(Jacobi{TV}.(A.b, A.a:∞)) .\ HalfWeighted{:a}.(Normalized.(Jacobi{TV}.(B.b, B.a:∞)))) .* convert(TV, 2)^(-c/2), (ℵ₀,ℵ₀), (2Int(B.b), 2Int(A.a+A.b)))
317-
end
450+
end

test/test_disk.jl

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using MultivariateOrthogonalPolynomials, ClassicalOrthogonalPolynomials, StaticArrays, BlockArrays, BandedMatrices, FastTransforms, LinearAlgebra, RecipesBase, Test, SpecialFunctions, LazyArrays
1+
using MultivariateOrthogonalPolynomials, ClassicalOrthogonalPolynomials, StaticArrays, BlockArrays, BandedMatrices, FastTransforms, LinearAlgebra, RecipesBase, Test, SpecialFunctions, LazyArrays, InfiniteArrays
22
import MultivariateOrthogonalPolynomials: DiskTrav, grid, ZernikeTransform, ZernikeITransform, *, ModalInterlace
33
import ClassicalOrthogonalPolynomials: HalfWeighted
44
import ForwardDiff: hessian
@@ -68,6 +68,45 @@ import ForwardDiff: hessian
6868
end
6969
end
7070

71+
@testset "Jacobi matrices" begin
72+
α = 10 * rand()
73+
Z = Zernike(α)
74+
xy = axes(Z, 1)
75+
x, y = first.(xy), last.(xy)
76+
n = 150
77+
# X tests
78+
JX = zeros(n,n)
79+
for j = 1:n
80+
JX[1:n,j] = (Z \ (x .* Z[:,j]))[1:n]
81+
end
82+
X = jacobimatrix(Val(1),Z)
83+
# The Zernike Jacobi matrices are symmetric for this normalization
84+
@test issymmetric(X)
85+
# Consistency with expansion
86+
@test X[1:150,1:150] JX
87+
# Multiplication by x
88+
f = Z \ (sin.(x.*y) .+ x.^2 .- y)
89+
xf = Z \ (x.*sin.(x.*y) .+ x.^3 .- x.*y)
90+
@test X[Block.(1:20),Block.(1:20)]*f[Block.(1:20)] xf[Block.(1:20)]
91+
# Y tests
92+
JY = zeros(n,n)
93+
for j = 1:n
94+
JY[1:n,j] = (Z \ (y .* Z[:,j]))[1:n]
95+
end
96+
Y = jacobimatrix(Val(2),Z)
97+
# The Zernike Jacobi matrices are symmetric for this normalization
98+
@test issymmetric(Y)
99+
# Consistency with expansion
100+
@test Y[1:150,1:150] JY
101+
# Multiplication by x
102+
f = Z \ (sin.(x.*y) .+ x.^2 .- y)
103+
yf = Z \ (y.*sin.(x.*y) .+ y .* x.^2 .- y.^2)
104+
@test Y[Block.(1:20),Block.(1:20)]*f[Block.(1:20)] yf[Block.(1:20)]
105+
# data size tests
106+
@test size((X.data).data) == (9, ℵ₀)
107+
@test size((Y.data).data) == (15, ℵ₀)
108+
end
109+
71110
@testset "Transform" begin
72111
for (a,b) in ((0,0), (0.1, 0.2), (0,1))
73112
Zn = Zernike(a,b)[:,Block.(Base.OneTo(3))]
@@ -503,4 +542,4 @@ end
503542
end
504543
end
505544
end
506-
end
545+
end

0 commit comments

Comments
 (0)