Skip to content

Commit 1123c96

Browse files
authored
Generalized Zernike (#65)
* Start Zernike weight * fix m * test other parameters * Update disk.jl * Update disk.jl * Work on Laplacian * Start xdisk conversion * Update Project.toml * Zernike conversion works * Add Zernike \ * work on Heat equation * tests pass * increase coverage
1 parent efa05c9 commit 1123c96

File tree

8 files changed

+320
-192
lines changed

8 files changed

+320
-192
lines changed

Project.toml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name = "MultivariateOrthogonalPolynomials"
22
uuid = "4f6956fd-4f93-5457-9149-7bfc4b2ce06d"
3-
version = "0.0.7"
3+
version = "0.0.8"
44

55
[deps]
66
ArrayLayouts = "4c555306-a7a7-4459-81d9-ec55ddd5c99a"
@@ -12,6 +12,7 @@ ContinuumArrays = "7ae1f121-cc2c-504b-ac30-9b923412ae5c"
1212
DomainSets = "5b8099bc-c8ec-5219-889f-1d9e522a28bf"
1313
FastTransforms = "057dd010-8810-581a-b7be-e3fc3b93f78c"
1414
FillArrays = "1a297f60-69ca-5386-bcde-b61e274b549b"
15+
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
1516
HarmonicOrthogonalPolynomials = "e416a80e-9640-42f3-8df8-80a93ca01ea5"
1617
InfiniteArrays = "4858937d-0d70-526a-a4dd-2d5cb5dd786c"
1718
InfiniteLinearAlgebra = "cde9dba0-b1de-11e9-2c62-0bab9446c55c"
@@ -28,12 +29,12 @@ ArrayLayouts = "0.6"
2829
BandedMatrices = "0.16"
2930
BlockArrays = "0.14.1, 0.15"
3031
BlockBandedMatrices = "0.10.1"
31-
ClassicalOrthogonalPolynomials = "0.3"
32-
ContinuumArrays = "0.5, 0.6"
32+
ClassicalOrthogonalPolynomials = "0.3.1"
33+
ContinuumArrays = "0.6"
3334
DomainSets = "0.4"
3435
FastTransforms = "0.11, 0.12"
3536
FillArrays = "0.11"
36-
HarmonicOrthogonalPolynomials = "0.0.3"
37+
HarmonicOrthogonalPolynomials = "0.0.4"
3738
InfiniteArrays = "0.10"
3839
InfiniteLinearAlgebra = "0.5"
3940
LazyArrays = "0.21"

examples/diskheat.jl

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using MultivariateOrthogonalPolynomials, DifferentialEquations
2+
3+
N = 20
4+
WZ = Weighted(Zernike(1))[:,Block.(Base.OneTo(N))]
5+
Δ = Laplacian(axes(WZ,1))
6+
*WZ).args[3].data |> typeof
7+
using LazyArrays: arguments, ApplyLayout
8+
arguments(ApplyLayout{typeof(*)}(), WZ)[2].data
9+
@ent arguments(ApplyLayout{typeof(*)}(), WZ)
10+
11+
function heat!(du, u, (R,Δ), t)
12+
13+
end

src/MultivariateOrthogonalPolynomials.jl

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,17 @@ import QuasiArrays: LazyQuasiMatrix, LazyQuasiArrayStyle
1111
import ContinuumArrays: @simplify, Weight, grid, TransformFactorization, Expansion
1212

1313
import BlockArrays: block, blockindex, BlockSlice, viewblock
14-
import BlockBandedMatrices: _BandedBlockBandedMatrix
14+
import BlockBandedMatrices: _BandedBlockBandedMatrix, AbstractBandedBlockBandedMatrix, _BandedMatrix, blockbandwidths, subblockbandwidths
1515
import LinearAlgebra: factorize
1616
import LazyArrays: arguments, paddeddata
1717

18-
import ClassicalOrthogonalPolynomials: jacobimatrix
18+
import ClassicalOrthogonalPolynomials: jacobimatrix, Weighted, orthogonalityweight
1919
import HarmonicOrthogonalPolynomials: BivariateOrthogonalPolynomial, MultivariateOrthogonalPolynomial, Plan,
2020
PartialDerivative, BlockOneTo, BlockRange1, interlace
2121

2222
export UnitTriangle, UnitDisk, JacobiTriangle, TriangleWeight, WeightedTriangle, PartialDerivative, Laplacian,
23-
MultivariateOrthogonalPolynomial, BivariateOrthogonalPolynomial, Zernike, RadialCoordinate
23+
MultivariateOrthogonalPolynomial, BivariateOrthogonalPolynomial, Zernike, RadialCoordinate,
24+
zerniker, zernikez, Weighted, Block, ZernikeWeight
2425

2526
if VERSION < v"1.6-"
2627
oneto(n) = Base.OneTo(n)

src/disk.jl

Lines changed: 143 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,25 +43,84 @@ getindex(A::DiskTrav, k::Int) = A[findblockindex(axes(A,1), k)]
4343

4444
ClassicalOrthogonalPolynomials.checkpoints(d::UnitDisk{T}) where T = [SVector{2,T}(0.1,0.2), SVector{2,T}(0.2,0.3)]
4545

46+
"""
47+
ZernikeWeight(a, b)
48+
49+
is a quasi-vector representing `r^(2a) * (1-r^2)^b`
50+
"""
51+
struct ZernikeWeight{T} <: Weight{T}
52+
a::T
53+
b::T
54+
end
55+
56+
"""
57+
ZernikeWeight(b)
4658
47-
struct Zernike{T} <: BivariateOrthogonalPolynomial{T} end
59+
is a quasi-vector representing `(1-r^2)^b`
60+
"""
61+
62+
ZernikeWeight(b) = ZernikeWeight(zero(b), b)
63+
64+
axes(::ZernikeWeight{T}) where T = (Inclusion(UnitDisk{T}()),)
65+
66+
==(w::ZernikeWeight, v::ZernikeWeight) = w.a == v.a && w.b == v.b
67+
68+
function getindex(w::ZernikeWeight, xy::StaticVector{2})
69+
r = norm(xy)
70+
r^(2w.a) * (1-r^2)^w.b
71+
end
4872

73+
74+
"""
75+
Zernike(a, b)
76+
77+
is a quasi-matrix orthogonal `r^(2a) * (1-r^2)^b`
78+
"""
79+
struct Zernike{T} <: BivariateOrthogonalPolynomial{T}
80+
a::T
81+
b::T
82+
Zernike{T}(a::T, b::T) where T = new{T}(a, b)
83+
end
84+
Zernike{T}(a, b) where T = Zernike{T}(convert(T,a), convert(T,b))
85+
Zernike(a::T, b::V) where {T,V} = Zernike{float(promote_type(T,V))}(a, b)
86+
Zernike{T}(b) where T = Zernike{T}(zero(b), b)
87+
Zernike{T}() where T = Zernike{T}(zero(T))
88+
89+
"""
90+
Zernike(b)
91+
92+
is a quasi-matrix orthogonal `(1-r^2)^b`
93+
"""
94+
Zernike(b) = Zernike(zero(b), b)
4995
Zernike() = Zernike{Float64}()
5096

5197
axes(P::Zernike{T}) where T = (Inclusion(UnitDisk{T}()),blockedrange(oneto(∞)))
5298

99+
==(w::Zernike, v::Zernike) = w.a == v.a && w.b == v.b
100+
53101
copy(A::Zernike) = A
54102

103+
orthogonalityweight(Z::Zernike) = ZernikeWeight(Z.a, Z.b)
104+
105+
zerniker(ℓ, m, a, b, r::T) where T = sqrt(convert(T,2)^(m+a+b+2-iszero(m))/π) * r^m * normalizedjacobip((ℓ-m) ÷ 2, b, m+a, 2r^2-1)
106+
zerniker(ℓ, m, b, r) = zerniker(ℓ, m, zero(b), b, r)
107+
zerniker(ℓ, m, r) = zerniker(ℓ, m, zero(r), r)
108+
109+
function zernikez(ℓ, ms, a, b, rθ::RadialCoordinate{T}) where T
110+
r,θ =.r,rθ.θ
111+
m = abs(ms)
112+
zerniker(ℓ, m, a, b, r) * (signbit(ms) ? sin(m*θ) : cos(m*θ))
113+
end
114+
115+
zernikez(ℓ, ms, a, b, xy::StaticVector{2}) = zernikez(ℓ, ms, a, b, RadialCoordinate(xy))
116+
zernikez(ℓ, ms, b, xy::StaticVector{2}) = zernikez(ℓ, ms, zero(b), b, xy)
117+
zernikez(ℓ, ms, xy::StaticVector{2,T}) where T = zernikez(ℓ, ms, zero(T), xy)
118+
55119
function getindex(Z::Zernike{T}, rθ::RadialCoordinate, B::BlockIndex{1}) where T
56-
r,θ =.r, rθ.θ
57120
= Int(block(B))-1
58121
k = blockindex(B)
59122
m = iseven(ℓ) ? k-isodd(k) : k-iseven(k)
60-
if iszero(m)
61-
sqrt(convert(T,2)/π) * Normalized(Legendre{T}())[2r^2-1, ℓ ÷ 2 + 1]
62-
else
63-
sqrt(convert(T,2)^(m+2)/π) * r^m * Normalized(Jacobi{T}(0, m))[2r^2-1,(ℓ-m) ÷ 2 + 1] * (isodd(k+ℓ) ? cos(m*θ) : sin(m*θ))
64-
end
123+
zernikez(ℓ, (isodd(k+ℓ) ? 1 : -1) * m, Z.a, Z.b, rθ)
65124
end
66125

67126

@@ -70,6 +129,8 @@ getindex(Z::Zernike, xy::StaticVector{2}, B::Block{1}) = [Z[xy, B[j]] for j=1:In
70129
getindex(Z::Zernike, xy::StaticVector{2}, JR::BlockOneTo) = mortar([Z[xy,Block(J)] for J = 1:Int(JR[end])])
71130

72131

132+
133+
73134
###
74135
# Transforms
75136
###
@@ -93,10 +154,82 @@ struct ZernikeTransform{T} <: Plan{T}
93154
analysis::FastTransforms.FTPlan{T,2,FastTransforms.DISKANALYSIS}
94155
end
95156

96-
function ZernikeTransform{T}(N::Int) where T<:Real
157+
function ZernikeTransform{T}(N::Int, a::Number, b::Number) where T<:Real
97158
= N ÷ 2 + 1
98-
ZernikeTransform{T}(N, plan_disk2cxf(T, Ñ, 0, 0), plan_disk_analysis(T, Ñ, 4-3))
159+
ZernikeTransform{T}(N, plan_disk2cxf(T, Ñ, a, b), plan_disk_analysis(T, Ñ, 4-3))
99160
end
100161
*(P::ZernikeTransform{T}, f::Matrix{T}) where T = DiskTrav(P.disk2cxf \ (P.analysis * f))[Block.(1:P.N)]
101162

102-
factorize(S::FiniteZernike{T}) where T = TransformFactorization(grid(S), ZernikeTransform{T}(blocksize(S,2)))
163+
factorize(S::FiniteZernike{T}) where T = TransformFactorization(grid(S), ZernikeTransform{T}(blocksize(S,2), parent(S).a, parent(S).b))
164+
165+
# gives the entries for the Laplacian times (1-r^2) * Zernike(1)
166+
struct WeightedZernikeLaplacianDiag{T} <: AbstractBlockVector{T} end
167+
168+
axes(::WeightedZernikeLaplacianDiag) = (blockedrange(oneto(∞)),)
169+
170+
function Base.view(W::WeightedZernikeLaplacianDiag{T}, K::Block{1}) where T
171+
= Int(K)
172+
d =÷2 + 1
173+
if isodd(K̃)
174+
v = (d:K̃) .* (d:(-1):1)
175+
convert(AbstractVector{T}, -4*interlace(v, v[2:end]))
176+
else
177+
v = (d:K̃) .* (d-1:(-1):1)
178+
convert(AbstractVector{T}, -4 * interlace(v, v))
179+
end
180+
end
181+
182+
getindex(W::WeightedZernikeLaplacianDiag, k::Integer) = W[findblockindex(axes(W,1),k)]
183+
184+
@simplify function *::Laplacian, WZ::Weighted{<:Any,<:Zernike})
185+
@assert WZ.P.a == 0 && WZ.P.b == 1
186+
WZ.P * Diagonal(WeightedZernikeLaplacianDiag{eltype(eltype(WZ))}())
187+
end
188+
189+
struct ZernikeConversion{T} <: AbstractBandedBlockBandedMatrix{T} end
190+
191+
axes(Z::ZernikeConversion) = (blockedrange(oneto(∞)), blockedrange(oneto(∞)))
192+
193+
blockbandwidths(::ZernikeConversion) = (0,2)
194+
subblockbandwidths(::ZernikeConversion) = (0,0)
195+
196+
197+
function Base.view(W::ZernikeConversion{T}, KJ::Block{2}) where T
198+
K,J = KJ.n
199+
dat = Matrix{T}(undef,1,J)
200+
if J == K
201+
if isodd(K)
202+
R0 = Normalized(Jacobi(1,0)) \ Normalized(Jacobi(0,0))
203+
dat[1,1] = R0[K÷2+1,K÷2+1]
204+
end
205+
for m in range(2-iseven(K); step=2, length=J÷2)
206+
Rm = Normalized(Jacobi(1,m)) \ Normalized(Jacobi(0,m))
207+
j = K÷2-m÷2+isodd(K)
208+
dat[1,m] = dat[1,m+1] = Rm[j,j]
209+
end
210+
elseif J == K + 2
211+
if isodd(K)
212+
R0 = Normalized(Jacobi(1,0)) \ Normalized(Jacobi(0,0))
213+
j = K÷2+1
214+
dat[1,1] = R0[j,j+1]
215+
end
216+
for m in range(2-iseven(K); step=2, length=K÷2)
217+
Rm = Normalized(Jacobi(1,m)) \ Normalized(Jacobi(0,m))
218+
j = K÷2-m÷2+isodd(K)
219+
dat[1,m] = dat[1,m+1] = Rm[j,j+1]
220+
end
221+
else
222+
fill!(dat, zero(T))
223+
end
224+
dat ./= sqrt(2one(T))
225+
_BandedMatrix(dat, K, 0, 0)
226+
end
227+
228+
getindex(R::ZernikeConversion, k::Integer, j::Integer) = R[findblockindex.(axes(R),(k,j))...]
229+
230+
function \(A::Zernike{T}, B::Zernike{V}) where {T,V}
231+
A.a == B.a && A.b == B.b && return Eye{promote_type(T,V)}(∞)
232+
@assert A.a == 0 && A.b == 1
233+
@assert B.a == 0 && B.b == 0
234+
ZernikeConversion{promote_type(T,V)}()
235+
end

src/triangle.jl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ copy(A::JacobiTriangle) = A
2525

2626
Base.summary(io::IO, P::JacobiTriangle) = print(io, "JacobiTriangle($(P.a), $(P.b), $(P.c))")
2727

28+
29+
"""
30+
TriangleWeight(a, b, c)
31+
32+
is a quasi-vector representing `x^a * y^b * (1-x-y)^c`.
33+
"""
2834
struct TriangleWeight{T,V} <: Weight{T}
2935
a::V
3036
b::V

test/runtests.jl

Lines changed: 0 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -3,105 +3,3 @@ using MultivariateOrthogonalPolynomials, Test
33
include("test_disk.jl")
44
include("test_triangle.jl")
55
# include("test_dirichlettriangle.jl")
6-
7-
8-
## bessel
9-
10-
# for k=0:10
11-
# @test Fun(r->besselj(k,r),JacobiSquare(k))(0.9) ≈ besselj(k,0.9)
12-
# end
13-
#
14-
#
15-
# f=Fun([1.],Segment()^2)
16-
# f(0.1,0.2)
17-
#
18-
#
19-
# f=Fun([1.],Disk())
20-
# @test f(0.1,0.2) ≈ 1.
21-
#
22-
# f=ProductFun(Fun([0.,1.]+0.0im,Disk()))
23-
# x,y=0.1,0.2
24-
# r,θ=sqrt(x^2+y^2),atan2(y,x)
25-
# @test exp(-im*θ)*r ≈ f(x,y)
26-
#
27-
#
28-
# ## Disk
29-
#
30-
#
31-
#
32-
#
33-
# f=(x,y)->exp(x.*sin(y))
34-
# u=ProductFun(f,Disk(),50,51)
35-
# @test u(.1,.1) ≈ f(.1,.1)
36-
#
37-
#
38-
#
39-
#
40-
# # write your own tests here
41-
# # Laplace
42-
# d=Disk()
43-
# u=[dirichlet(d),lap(d)]\Fun(z->real(exp(z)),Circle())
44-
# @test u(.1,.2) ≈ real(exp(.1+.2im))
45-
#
46-
#
47-
#
48-
#
49-
# # remaining numbers determined numerically, may be
50-
# # inaccurate
51-
#
52-
# # Poisson
53-
# f=Fun((x,y)->exp(-10(x+.2).^2-20(y-.1).^2),d)
54-
# u=[dirichlet(d),lap(d)]\[0.,f]
55-
# @test u(.1,.2) ≈ -0.039860694987858845
56-
#
57-
# #Helmholtz
58-
# u=[dirichlet(d),lap(d)+100I]\1.0
59-
# @test_approx_eq_eps u(.1,.2) -0.3675973169667076 1E-11
60-
# u=[neumann(d),lap(d)+100I]\1.0
61-
# @test_approx_eq_eps u(.1,.2) -0.20795862954551195 1E-11
62-
#
63-
# # Screened Poisson
64-
# u=[neumann(d),lap(d)-100.0I]\1.0
65-
# @test_approx_eq_eps u(.1,.9) 0.04313812031635443 1E-11
66-
#
67-
# # Lap^2
68-
# u=[dirichlet(d),neumann(d),lap(d)^2]\Fun(z->real(exp(z)),Circle())
69-
# @test_approx_eq_eps u(.1,.2) 1.1137317420521624 1E-11
70-
#
71-
#
72-
# # Speed Test
73-
#
74-
#
75-
# d = Disk()
76-
# f = Fun((x,y)->exp(-10(x+.2)^2-20(y-.1)^2),d)
77-
# S = discretize([dirichlet(d);lap(d)],100);
78-
# @time S = discretize([dirichlet(d);lap(d)],100);
79-
# u=S\Any[0.;f];
80-
# @time u=S\Any[0.;f];
81-
#
82-
# println("Disk Poisson: should be ~0.16,0.016")
83-
#
84-
#
85-
#
86-
# ## README Test
87-
#
88-
# d = Disk()
89-
# f = Fun((x,y)->exp(-10(x+.2)^2-20(y-.1)^2),d)
90-
# u = [dirichlet(d);lap(d)]\Any[0.,f]
91-
#
92-
#
93-
#
94-
# d = Disk()
95-
# u0 = Fun((x,y)->exp(-50x^2-40(y-.1)^2)+.5exp(-30(x+.5)^2-40(y+.2)^2),d)
96-
# B= [dirichlet(d);neumann(d)]
97-
# L = -lap(d)^2
98-
# h = 0.001
99-
#
100-
# L=Laplacian(Space(d),1)
101-
# @show real(ApproxFun.diagop(L,1)[1:10,1:10])
102-
#
103-
# d = Disk()
104-
# u0 = Fun((x,y)->exp(-50x^2-40(y-.1)^2)+.5exp(-30(x+.5)^2-40(y+.2)^2),d)
105-
# B= [dirichlet(d);neumann(d)]
106-
# L = -lap(d)^2
107-
# h = 0.001

0 commit comments

Comments
 (0)