Skip to content

Commit 365bd5b

Browse files
authored
Revised: parametrize struct definitions of random matrix ensemble types (#97)
* parametrize ensemble structs * add `test/test_throws.jl` * test`eigvaljpdf(d::GaussianLaguerre{β}, lambda})` * fix eigvals in Wishart test * include `test_throws.jl` * add `GaussianJacobi` fixes and tests * add Ginibre tests * fix Ginibre test file
1 parent 37f177b commit 365bd5b

File tree

8 files changed

+177
-131
lines changed

8 files changed

+177
-131
lines changed

src/GaussianEnsembles.jl

Lines changed: 77 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,12 @@ export GaussianHermite, GaussianLaguerre, GaussianJacobi,
2424
#####################
2525

2626
"""
27-
GaussianHermite(β::Int) <: ContinuousMatrixDistribution
27+
GaussianHermite{β} <: ContinuousMatrixDistribution
28+
GaussianHermite(β::Real) -> GaussianHermite{β}()
2829
2930
Represents a Gaussian Hermite ensemble with Dyson index `β`.
3031
31-
`Wigner(β)` is a synonym.
32+
`Wigner{β}` is a synonym.
3233
3334
## Examples
3435
@@ -41,15 +42,15 @@ julia> rand(Wigner(2), 3)
4142
```
4243
"""
4344
struct GaussianHermite{β} <: ContinuousMatrixDistribution end
44-
GaussianHermite(β) = GaussianHermite{β}()
45+
GaussianHermite::Real) = GaussianHermite{β}()
4546

4647
"""
4748
Synonym for GaussianHermite{β}
4849
"""
4950
const Wigner{β} = GaussianHermite{β}
5051

5152
"""
52-
rand(d::Wigner, n::Int)
53+
rand(d::Wigner{β}, n::Int)
5354
5455
Generates an `n × n` matrix randomly sampled from the Gaussian-Hermite ensemble (also known as the Wigner ensemble).
5556
@@ -64,16 +65,24 @@ function rand(d::Wigner{1}, n::Int)
6465
end
6566

6667
function rand(d::Wigner{2}, n::Int)
67-
A = randn(n, n) + im*randn(n, n)
68+
A = randn(ComplexF64, n, n)
6869
normalization = (4*n)
6970
return Hermitian((A + A') / normalization)
7071
end
7172

7273
function rand(d::Wigner{4}, n::Int)
7374
#Employs 2x2 matrix representation of quaternions
74-
X = randn(n, n) + im*randn(n, n)
75-
Y = randn(n, n) + im*randn(n, n)
76-
A = [X Y; -conj(Y) conj(X)]
75+
X = randn(ComplexF64, n, n)
76+
Y = randn(ComplexF64, n, n)
77+
A = Matrix{ComplexF64}(undef, 2n, 2n)
78+
@inbounds for j in 1:n, i in 1:n
79+
x = X[i, j]
80+
y = Y[i, j]
81+
A[i, j] = x
82+
A[i+n, j] = -conj(y)
83+
A[i, j+n] = y
84+
A[i+n, j+n] = conj(x)
85+
end
7786
normalization = (8*n)
7887
return Hermitian((A + A') / normalization)
7988
end
@@ -90,7 +99,7 @@ function rand(d::Wigner{β}, dims::Int...) where {β}
9099
end
91100

92101
"""
93-
tridand(d::Wigner, n::Int)
102+
tridand(d::Wigner{β}, n::Int)
94103
95104
Generates an `n × n` symmetric tridiagonal matrix from the Gaussian-Hermite ensemble (also known as the Wigner ensemble).
96105
@@ -158,15 +167,15 @@ end
158167
#####################
159168

160169
"""
161-
GaussianLaguerre(β::Real, a::Real)` <: ContinuousMatrixDistribution
170+
GaussianLaguerre{β}(a::Real)` <: ContinuousMatrixDistribution
171+
GaussianLaguerre(β::Real, a::Real) -> GaussianLaguerre{β}(a)
162172
163173
Represents a Gaussian-Laguerre ensemble with Dyson index `β` and `a` parameter
164174
used to control the density of eigenvalues near `λ = 0`.
165175
166-
`Wishart(β, a)` is a synonym.
176+
`Wishart{β}(a)` is a synonym.
167177
168178
## Fields
169-
- `beta`: Dyson index
170179
- `a`: Parameter used for weighting the joint probability density function of the ensemble
171180
172181
## Examples
@@ -186,32 +195,41 @@ julia> rand(GaussianLaguerre(4, 8), 2)
186195
## References:
187196
- Edelman and Rao, 2005
188197
"""
189-
mutable struct GaussianLaguerre <: ContinuousMatrixDistribution
190-
beta::Real
198+
struct GaussianLaguerre{β} <: ContinuousMatrixDistribution
191199
a::Real
192200
end
193-
const Wishart = GaussianLaguerre
201+
GaussianLaguerre::Real, a::Real) = GaussianLaguerre{β}(a::Real)
202+
const Wishart{β} = GaussianLaguerre{β}
194203

195204
#TODO Check - the eigenvalue distribution looks funky
196205
#TODO The appropriate matrix size should be calculated from a and one matrix dimension
197206
"""
198-
rand(d::GaussianLaguerre, n::Int)
207+
rand(d::GaussianLaguerre{β}, n::Int)
199208
200209
Generate a random matrix sampled from the Gaussian Laguerre ensemble (also known as the Wishart ensemble)
201210
with parameters defined in `d`.
202211
203212
The Dyson index `β` is restricted to `β = 1,2` (`n × n` matrix) or `4` (`2n × 2n` block matrix representation),
204213
for real, complex, and quaternionic fields, respectively.
205214
"""
206-
function rand(d::GaussianLaguerre, n::Int)
207-
a, beta = d.a, d.beta
208-
a >= beta * n / 2 || throw(ArgumentError("the minimum value of `a` must be `βn/2`."))
209-
m = Int(2*a/beta)
210-
if beta == 1 # real
215+
function rand(d::GaussianLaguerre{1}, n::Int)
216+
a = d.a
217+
a >= n / 2 || throw(ArgumentError("the minimum value of `a` must be `βn/2`."))
218+
m = Int(2a)
211219
A = randn(m, n)
212-
elseif beta == 2 # complex
220+
return (A' * A) / n
221+
end
222+
function rand(d::GaussianLaguerre{2}, n::Int)
223+
a = d.a
224+
a >= n || throw(ArgumentError("the minimum value of `a` must be `βn/2`."))
225+
m = Int(2a)
213226
A = randn(ComplexF64, m, n)
214-
elseif beta == 4 # quaternion
227+
return (A' * A) / n
228+
end
229+
function rand(d::GaussianLaguerre{4}, n::Int)
230+
a = d.a
231+
a >= 2n || throw(ArgumentError("the minimum value of `a` must be `βn/2`."))
232+
m = Int(2a)
215233
# employs 2x2 matrix representation of quaternions
216234
X = randn(ComplexF64, m, n)
217235
Y = randn(ComplexF64, m, n)
@@ -224,26 +242,24 @@ function rand(d::GaussianLaguerre, n::Int)
224242
A[i, j+n] = y
225243
A[i+m, j+n] = conj(x)
226244
end
227-
else
228-
error("beta = $(beta) is not implemented")
229-
end
230-
return (A' * A) / n
245+
return (A' * A) / n
231246
end
247+
rand(d::GaussianLaguerre{β}, n::Int) where {β} = throw(ArgumentError("beta = $(β) is not implemented"))
232248

233249
"""
234-
bidrand(d::GaussianLaguerre, n::Int)
250+
bidrand(d::GaussianLaguerre{β}, n::Int)
235251
236252
Generate an `n × n` bidiagonal matrix sampled from the Gaussian Laguerre ensemble (also known as the Wishart ensemble).
237253
"""
238-
function bidrand(d::GaussianLaguerre, m::Integer)
239-
if d.a <= d.beta*(m-1)/2.0
240-
error("Given your choice of m and beta, a must be at least $(d.beta*(m-1)/2.0) (You said a = $(d.a))")
254+
function bidrand(d::GaussianLaguerre{β}, m::Integer) where {β}
255+
if d.a <= β*(m-1)/2.0
256+
error("Given your choice of m and beta, a must be at least $(β*(m-1)/2.0) (You said a = $(d.a))")
241257
end
242-
Bidiagonal([chi(2*d.a-i*d.beta) for i=0:m-1], [chi(d.beta*i) for i=m-1:-1:1], true)
258+
Bidiagonal([chi(2*d.a-i*β) for i=0:m-1], [chi(β*i) for i=m-1:-1:1], true)
243259
end
244260

245261
"""
246-
tridrand(d::GaussianLaguerre, n::Int)
262+
tridrand(d::GaussianLaguerre{β}, n::Int)
247263
248264
Generate an `n × n` tridiagonal matrix sampled from the Gaussian Laguerre ensemble (also known as the Wishart ensemble).
249265
"""
@@ -257,25 +273,25 @@ end
257273
eigvalrand(d::GaussianLaguerre, m::Integer) = eigvals(tridrand(d, m))
258274

259275
#TODO Check m and ns
260-
function eigvaljpdf(d::GaussianLaguerre, lambda::Vector{Eigenvalue}) where {Eigenvalue<:Number}
276+
function eigvaljpdf(d::GaussianLaguerre{β}, lambda::Vector{Eigenvalue}) where {β,Eigenvalue<:Number}
261277
m = length(lambda)
262278
#Laguerre parameters
263-
p = 0.5*d.beta*(m-1) + 1.0
279+
p = 0.5*β*(m-1) + 1.0
264280
#Calculate normalization constant
265281
c = 2.0^-(m*d.a)
266-
z = (d.a - d.beta*(m)*0.5)
282+
z = (d.a - β*(m)*0.5)
267283
for j=1:m
268-
z += 0.5*d.beta
284+
z += 0.5*β
269285
if z < 0 && (int(z) - z) < eps()
270286
#Pole of gamma function, there is no density here no matter what
271287
return 0.0
272288
end
273-
c *= gamma(1 + beta/2)/(gamma(1 + beta*j/2)*gamma(z))
289+
c *= gamma(1 + β/2)/(gamma(1 + β*j/2)*gamma(z))
274290
end
275291

276-
Prod = prod(lambda.^(a-p)) #Calculate Laguerre product term
292+
Prod = prod(lambda.^(d.a-p)) #Calculate Laguerre product term
277293
Energy = sum(lambda)/2 #Calculate argument of exponential
278-
return c * VandermondeDeterminant(lambda, beta) * Prod * exp(-Energy)
294+
return c * VandermondeDeterminant(lambda, β) * Prod * exp(-Energy)
279295
end
280296

281297

@@ -285,37 +301,37 @@ end
285301
###################
286302

287303
"""
288-
GaussianJacobi(β::Real, a::Real, a::Real)` <: ContinuousMatrixDistribution
304+
GaussianJacobi{β}(a::Real, b::Real) <: ContinuousMatrixDistribution
305+
GaussianJacobi(β::Real, a::Real, b::Real) -> GaussianJacobi{β}(a, b)
289306
290307
Represents a Gaussian-Jacobi ensemble with Dyson index `β`, while
291308
`a`and `b` are parameters used to weight the joint probability density function of the ensemble.
292309
293-
`MANOVA(β, a, b)` is a synonym.
310+
`MANOVA{β}(a, b)` is a synonym.
294311
295312
## Fields
296-
- `beta`: Dyson index
297313
- `a`: Parameter used for shaping the joint probability density function near `λ = 0`
298314
- `b`: Parameter used for shaping the joint probability density function near `λ = 1`
299315
300316
## References:
301317
- Edelman and Rao, 2005
302318
"""
303-
mutable struct GaussianJacobi <: ContinuousMatrixDistribution
304-
beta::Real
319+
struct GaussianJacobi{β} <: ContinuousMatrixDistribution
305320
a::Real
306321
b::Real
307322
end
308-
const MANOVA = GaussianJacobi
323+
GaussianJacobi::Real, a::Real, b::Real) = GaussianJacobi{β}(a, b)
324+
const MANOVA{β} = GaussianJacobi{β}
309325

310326
"""
311-
rand(d::GaussianJacobi, n::Int)
327+
rand(d::GaussianJacobi{β}, n::Int)
312328
313329
Generate an `n × n` random matrix sampled from the Gaussian-Jacobi ensemble (also known as the MANOVA ensemble)
314330
with parameters defined in `d`.
315331
"""
316-
function rand(d::GaussianJacobi, m::Integer)
317-
w1 = Wishart(m, int(2.0*d.a/d.beta), d.beta)
318-
w2 = Wishart(m, int(2.0*d.b/d.beta), d.beta)
332+
function rand(d::GaussianJacobi{β}, n::Int) where {β}
333+
w1 = rand(Wishart(β, d.a), n)
334+
w2 = rand(Wishart(β, d.b), n)
319335
return (w1 + w2) \ w1
320336
end
321337

@@ -346,12 +362,12 @@ end
346362
# and generalized singular value problems", Foundations of Computational Mathematics,
347363
# vol. 8 iss. 2 (2008), pp 259-285.
348364
#TODO check normalization
349-
function sprand(d::GaussianJacobi, n::Integer, a::Real, b::Real)
365+
function sprand(d::GaussianJacobi{β}, n::Integer, a::Real, b::Real) where {β}
350366
CoordI = zeros(8n-4)
351367
CoordJ = zeros(8n-4)
352368
Values = zeros(8n-4)
353369

354-
c, s, cp, sp = SampleCSValues(n, a, b, d.beta)
370+
c, s, cp, sp = SampleCSValues(n, a, b, β)
355371

356372
#Diagonals of each block
357373
for i=1:n
@@ -392,9 +408,9 @@ function sprand(d::GaussianJacobi, n::Integer, a::Real, b::Real)
392408
end
393409

394410
#Return n eigenvalues distributed according to the Jacobi ensemble
395-
function eigvalrand(d::GaussianJacobi, n::Integer)
411+
function eigvalrand(d::GaussianJacobi{β}, n::Integer) where {β}
396412
#Generate just the upper left quadrant of the matrix
397-
c, s, cp, sp = SampleCSValues(n, d.a, d.b, d.beta)
413+
c, s, cp, sp = SampleCSValues(n, d.a, d.b, β)
398414
dv = [i==1 ? c[n] : c[n+1-i] * sp[n+1-i] for i=1:n]
399415
ev = [-s[n+1-i]*cp[n-i] for i=1:n-1]
400416

@@ -404,28 +420,28 @@ function eigvalrand(d::GaussianJacobi, n::Integer)
404420
end
405421

406422
#TODO Check m and ns
407-
function eigvaljpdf(d::GaussianJacobi, lambda::Vector{Eigenvalue}) where {Eigenvalue<:Number}
423+
function eigvaljpdf(d::GaussianJacobi{β}, lambda::Vector{Eigenvalue}) where {β,Eigenvalue<:Number}
408424
m = length(lambda)
409425
#Jacobi parameters
410426
a1, a2 = d.a, d.b
411-
p = 1.0 + d.beta*(m-1)/2.0
427+
p = 1.0 + β*(m-1)/2.0
412428
#Calculate normalization constant
413429
c = 1.0
414430
for j=1:m
415-
z1 = (a1 - beta*(m-j)/2.0)
431+
z1 = (a1 - β*(m-j)/2.0)
416432
if z1 < 0 && (int(z1) - z1) < eps()
417433
return 0.0 #Pole of gamma function, there is no density here no matter what
418434
end
419-
z2 = (a2 - beta*(m-j)/2.0)
435+
z2 = (a2 - β*(m-j)/2.0)
420436
if z2 < 0 && (int(z2) - z2) < eps()
421437
return 0.0 #Pole of gamma function, there is no density here no matter what
422438
end
423-
c *= gamma(1 + beta/2)*gamma(a1+a2-beta*(m-j)/2)
424-
c /= gamma(1 + beta*j/2)*gamma(z1)*gamma(z2)
439+
c *= gamma(1 + β/2)*gamma(a1+a2-β*(m-j)/2)
440+
c /= gamma(1 + β*j/2)*gamma(z1)*gamma(z2)
425441
end
426442

427443
Prod = prod(lambda.^(a1-p))*prod((1-lambda).^(a2-p)) #Calculate Laguerre product term
428444
Energy = sum(lambda/2) #Calculate argument of exponential
429445

430-
return c * VandermondeDeterminant(lambda, beta) * Prod * exp(-Energy)
446+
return c * VandermondeDeterminant(lambda, β) * Prod * exp(-Energy)
431447
end

src/Ginibre.jl

Lines changed: 16 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,16 @@ export rand, Ginibre
22
import Base.rand
33

44
"""
5-
Ginibre(β::Int, N::Int) <: ContinuousMatrixDistribution
5+
Ginibre{β} <: ContinuousMatrixDistribution
6+
Ginibre(β::Real) -> Ginibre{β}()
67
78
Represents a Ginibre ensemble with Dyson index `β` living in `GL(N, F)`, the set
89
of all invertible `N × N` matrices over the field `F`.
910
10-
## Fields
11-
- `beta`: Dyson index
12-
- `N`: Matrix dimension over the field `F`.
13-
1411
## Examples
1512
1613
```@example
17-
julia> rand(Ginibre(2, 3))
14+
julia> rand(Ginibre(2), 3)
1815
3×3 Matrix{ComplexF64}:
1916
0.781329+2.00346im 0.0595122+0.488652im -0.323494-0.35966im
2017
1.11089+0.935174im -0.384457+1.71419im 0.114358-0.360676im
@@ -24,35 +21,28 @@ julia> rand(Ginibre(2, 3))
2421
## References:
2522
- Edelman and Rao, 2005
2623
"""
27-
struct Ginibre <: ContinuousMatrixDistribution
28-
beta::Float64
29-
N::Integer
30-
end
24+
struct Ginibre{β} <: ContinuousMatrixDistribution end
25+
Ginibre::B) where {B} = Ginibre{β}()
3126

3227
"""
33-
rand(W::Ginibre)
28+
rand(W::Ginibre{β}, n::Int)
3429
3530
Samples a matrix from the Ginibre ensemble.
3631
3732
For `β = 1,2,4`, generates matrices randomly sampled from the real, complex, and quaternion
3833
Ginibre ensemble, respectively.
3934
"""
40-
function rand(W::Ginibre)
41-
beta, n = W.beta, W.N
42-
if beta==1
43-
randn(n,n)
44-
elseif beta==2
45-
randn(n,n)+im*randn(n,n)
46-
elseif beta==4
47-
Q0=randn(n,n)
48-
Q1=randn(n,n)
49-
Q2=randn(n,n)
50-
Q3=randn(n,n)
51-
[Q0+im*Q1 Q2+im*Q3;-Q2+im*Q3 Q0-im*Q1]
52-
else
53-
error(string("beta = ", beta, " not implemented"))
54-
end
35+
rand(W::Ginibre{1}, n::Int) = randn(n, n)
36+
rand(W::Ginibre{2}, n::Int) = randn(ComplexF64, n, n)
37+
function rand(W::Ginibre{4}, n::Int)
38+
Q0=randn(n,n)
39+
Q1=randn(n,n)
40+
Q2=randn(n,n)
41+
Q3=randn(n,n)
42+
return [Q0+im*Q1 Q2+im*Q3;-Q2+im*Q3 Q0-im*Q1]
5543
end
44+
rand(W::Ginibre{β}, n::Int) where {β} = throw(ArgumentError("Cannot sample random matrix of size $n x $n for β="))
45+
5646

5747
function jpdf(Z::AbstractMatrix{z}) where {z<:Complex}
5848
pi^(size(Z,1)^2)*exp(-trace(Z'*Z))

0 commit comments

Comments
 (0)