Skip to content

Commit b42ba83

Browse files
authored
Added pfaffian(::AbstractMatrix{<:Complex}) and isskewhermitian (#137)
So far I added only computation of pfaffians for complex skew symmetric matrices, but if we want to take them seriously we should probably also add a type `SkewSymmetric` and use that for pfaffian computations instead of `SkewSymmetric` since that is what pfaffians are actually defined for. However, this is out of the scope of this PR This fixes #136
1 parent 186a4fa commit b42ba83

File tree

7 files changed

+97
-15
lines changed

7 files changed

+97
-15
lines changed

Project.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "SkewLinearAlgebra"
22
uuid = "5c889d49-8c60-4500-9d10-5d3a22e2f4b9"
33
authors = ["smataigne <[email protected]> and contributors"]
4-
version = "0.1.1"
4+
version = "0.1.2"
55

66
[deps]
77
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,6 @@ The `SkewLinearAlgebra` package provides specialized matrix types, optimized met
1010

1111
In particular, it defines new `SkewHermitian` and `SkewHermTridiagonal` matrix types supporting optimized eigenvalue/eigenvector,
1212
Hessenberg factorization, and matrix exponential/trigonometric functions. It also provides functions to compute the
13-
[Pfaffian](https://en.wikipedia.org/wiki/Pfaffian) of real skew-symmetric matrices, along with a Cholesky-like factorization.
13+
[Pfaffian](https://en.wikipedia.org/wiki/Pfaffian) of skew-symmetric matrices, along with a Cholesky-like factorization for real skew-symmetric matrices.
1414

1515
See the [Documentation](https://julialinearalgebra.github.io/SkewLinearAlgebra.jl/dev/) for details.

docs/src/pfaffian.md

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
# Pfaffian calculations
22

3-
A real skew-symmetrix matrix ``A = -A^T`` has a special property: its determinant is the square of a polynomial function of the
3+
A skew-symmetrix matrix ``A = -A^T`` has a special property: its determinant is the square of a polynomial function of the
44
matrix entries, called the [Pfaffian](https://en.wikipedia.org/wiki/Pfaffian). That is, ``\mathrm{det}(A) = \mathrm{Pf}(A)^2``, but
55
knowing the Pfaffian itself (and its sign, which is lost in the determinant) is useful for a number of applications.
66

7-
We provide a function `pfaffian(A)` to compute the Pfaffian of a real skew-symmetric matrix `A`.
7+
We provide a function `pfaffian(A)` to compute the Pfaffian of a skew-symmetric matrix `A`.
88
```jl
99
julia> using SkewLinearAlgebra, LinearAlgebra
1010

@@ -49,7 +49,7 @@ julia> pfaffian!(BigInt[0 2 -7; -2 0 -8; 7 8 0])
4949
```
5050
(Note that the Pfaffian is *always zero* for any *odd* size skew-symmetric matrix.)
5151

52-
Since the computation of the pfaffian can easily overflow/underflow the maximum/minimum representable floating-point value, we also provide a function `logabspfaffian` (along with an in-place variant `logabspfaffian!`) that returns a tuple `(logpf, sign)` such
52+
Since the computation of the pfaffian can easily overflow/underflow the maximum/minimum representable floating-point value, we also provide a function `logabspfaffian` for real skew-symmetric matrices (along with an in-place variant `logabspfaffian!`) that returns a tuple `(logpf, sign)` such
5353
that the Pfaffian is `sign * exp(logpf)`. (This is similar to the [`logabsdet`](https://docs.julialang.org/en/v1/stdlib/LinearAlgebra/#LinearAlgebra.logabsdet) function in Julia's `LinearAlgebra` library to compute the log of the determinant.)
5454

5555
```jl
@@ -81,4 +81,4 @@ SkewLinearAlgebra.pfaffian
8181
SkewLinearAlgebra.pfaffian!
8282
SkewLinearAlgebra.logabspfaffian
8383
SkewLinearAlgebra.logabspfaffian!
84-
```
84+
```

src/SkewLinearAlgebra.jl

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export
1515
JMatrix,
1616
#functions
1717
isskewhermitian,
18+
isskewsymmetric,
1819
skewhermitian,
1920
skewhermitian!,
2021
pfaffian,

src/pfaffian.jl

+57-8
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ end
5151

5252
function exactpfaffian!(A::AbstractMatrix)
5353
LinearAlgebra.require_one_based_indexing(A)
54-
isskewhermitian(A) || throw(ArgumentError("Pfaffian requires a skew-Hermitian matrix"))
54+
isskewsymmetric(A) || throw(ArgumentError("Pfaffian requires a skew-symmetric matrix"))
5555
return _exactpfaffian!(A)
5656
end
5757
exactpfaffian!(A::SkewHermitian) = _exactpfaffian!(A.data)
@@ -87,28 +87,77 @@ function _pfaffian!(A::SkewHermTridiagonal{<:Real})
8787
return pf
8888
end
8989

90-
pfaffian!(A::Union{SkewHermitian{<:Real},SkewHermTridiagonal{<:Real}})= _pfaffian!(A)
91-
pfaffian(A::Union{SkewHermitian{<:Real},SkewHermTridiagonal{<:Real}})= pfaffian!(copyeigtype(A))
90+
# Using the Parley-Reid algorithm from https://arxiv.org/abs/1102.3440 as implented
91+
# in https://github.com/KskAdch/TopologicalNumbers.jl/blob/42bda634d47aecb67cfcd495d97e0723b0e73a4f/src/pfaffian.jl#L332
92+
function _pfaffian!(A::AbstractMatrix{<:Complex})
93+
n = size(A, 1)
94+
isodd(n) && return zero(eltype(A))
95+
tau = Array{eltype(A)}(undef, n - 2)
96+
pf = one(eltype(A))
97+
@inbounds for k in 1:2:n-1
98+
tauk = @view tau[k:end]
99+
100+
# Pivot if neccessary
101+
@views kp = k + argmax(abs.(A[k+1:end, k]))
102+
if kp != k + 1
103+
@inbounds @simd for l in k:n
104+
A[k+1,l], A[kp,l] = A[kp,l], A[k+1,l]
105+
end
106+
@inbounds @simd for l in k:n
107+
A[l,k+1], A[l,kp] = A[l,kp], A[l,k+1]
108+
end
109+
pf *= -1
110+
end
111+
112+
# Apply Gauss transformation and update `pf`
113+
@inbounds if A[k+1,k] != zero(eltype(A))
114+
@inbounds @views tauk .= A[k,k+2:end] ./ A[k,k+1]
115+
pf *= @inbounds A[k,k+1]
116+
if k + 2 <= n
117+
@inbounds for l1 in eachindex(tauk)
118+
@simd for l2 in eachindex(tauk)
119+
@fastmath A[k+1+l2,k+1+l1] +=
120+
tauk[l2] * A[k+1+l1,k+1] -
121+
tauk[l1] * A[k+1+l2,k+1]
122+
end
123+
end
124+
end
125+
else
126+
return zero(eltype(A)) # Pfaffian is zero if there is a zero on the super/subdiagonal
127+
end
128+
end
129+
130+
return pf
131+
end
132+
133+
pfaffian!(A::Union{SkewHermitian{<:Real},SkewHermTridiagonal{<:Real}}) = _pfaffian!(A)
92134

93135
"""
94136
pfaffian(A)
95137
96-
Returns the pfaffian of `A` where a is a real skew-Hermitian matrix.
97-
If `A` is not of type `SkewHermitian{<:Real}`, then `isskewhermitian(A)`
98-
is checked to ensure that `A == -A'`
138+
Returns the pfaffian of `A` where a is a skew-symmetric matrix.
139+
If `A` is not of type `SkewHermitian{<:Real}`, then `isskewsymmetric(A)`
140+
is checked to ensure that `A == -transpose(A)`
99141
"""
100-
pfaffian(A::AbstractMatrix{<:Real}) = pfaffian!(copyeigtype(A))
142+
pfaffian(A::AbstractMatrix) = pfaffian!(copyeigtype(A))
101143

102144
"""
103145
pfaffian!(A)
104146
105147
Similar to [`pfaffian`](@ref), but overwrites `A` in-place with intermediate calculations.
106148
"""
107149
function pfaffian!(A::AbstractMatrix{<:Real})
108-
isskewhermitian(A) || throw(ArgumentError("Pfaffian requires a skew-Hermitian matrix"))
150+
isskewhermitian(A) || throw(ArgumentError("Pfaffian requires a skew-symmetric matrix"))
109151
return _pfaffian!(SkewHermitian(A))
110152
end
111153

154+
function pfaffian!(A::AbstractMatrix{<:Complex})
155+
LinearAlgebra.require_one_based_indexing(A)
156+
isskewsymmetric(A) || throw(ArgumentError("Pfaffian requires a skew-symmetric matrix"))
157+
return _pfaffian!(A)
158+
end
159+
160+
112161
function _logabspfaffian!(A::SkewHermitian{<:Real})
113162
n = size(A, 1)
114163
isodd(n) && return convert(eltype(A), -Inf), zero(eltype(A))

src/skewhermitian.jl

+18-1
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ Base.size(A::SkewHermitian) = size(A.data)
8282
Returns whether `A` is skew-Hermitian, i.e. whether `A == -A'`.
8383
"""
8484
function isskewhermitian(A::AbstractMatrix{<:Number})
85-
axes(A,1) == axes(A,2) || throw(ArgumentError("axes $(axes(A,1)) and $(axex(A,2)) do not match"))
85+
axes(A,1) == axes(A,2) || throw(ArgumentError("axes $(axes(A,1)) and $(axes(A,2)) do not match"))
8686
@inbounds for i in axes(A,1)
8787
for j = firstindex(A, 1):i
8888
A[i,j] == -A[j,i]' || return false
@@ -93,6 +93,23 @@ end
9393
isskewhermitian(A::SkewHermitian) = true
9494
isskewhermitian(a::Number) = a == -a'
9595

96+
"""
97+
isskewsymmetric(A)
98+
99+
Returns whether `A` is skew-symmetric, i.e. whether `A == -transpose(A)`.
100+
"""
101+
function isskewsymmetric(A::AbstractMatrix{<:Number})
102+
axes(A,1) == axes(A,2) || throw(ArgumentError("axes $(axes(A,1)) and $(axes(A,2)) do not match"))
103+
@inbounds for i in axes(A,1)
104+
for j = firstindex(A, 1):i
105+
A[i,j] == -A[j,i] || return false
106+
end
107+
end
108+
return true
109+
end
110+
isskewsymmetric(A::SkewHermitian{<:Real}) = true
111+
isskewsymmetric(a::Number) = iszero(a)
112+
96113
"""
97114
skewhermitian!(A)
98115

test/runtests.jl

+15
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,7 @@ end
386386
end
387387

388388
@testset "pfaffian.jl" begin
389+
# real skew-hermitian matrices
389390
for n in [1, 2, 3, 10, 11]
390391
A = skewhermitian(rand(-10:10,n,n) * 2)
391392
Abig = BigInt.(A.data)
@@ -401,6 +402,20 @@ end
401402
logpf, sign = logabspfaffian(S)
402403
@test pfaffian(S) sign * exp(logpf) sign * sqrt(det(Matrix(S)))
403404
end
405+
406+
# complex skew-symmetric matrices
407+
# n=11 is a bit jinxed because of https://github.com/JuliaLang/julia/issues/54287
408+
for n in [1, 2, 3, 10, 20]
409+
A = rand((-10:10) .+ 1im * (-10:10)', n, n)
410+
A = (A .- transpose(A)) ./ 2
411+
Abig = Complex{Rational{BigInt}}.(A)
412+
@test pfaffian(A) SkewLinearAlgebra.exactpfaffian(Abig)
413+
@test pfaffian(A)^2 det(A) atol=eps(Float64) * max(1, abs(det(A)))
414+
if VERSION v"1.7" # for exact det of BigInt matrices
415+
@test SkewLinearAlgebra.exactpfaffian(Abig)^2 == det(Abig)
416+
end
417+
end
418+
404419
# issue #49
405420
@test pfaffian(big.([0 14 7 -10 0 10 0 -11; -14 0 -10 7 13 -9 -12 -13; -7 10 0 -4 6 -17 -1 18; 10 -7 4 0 -2 -4 0 11; 0 -13 -6 2 0 -8 -18 17; -10 9 17 4 8 0 -8 12; 0 12 1 0 18 8 0 0; 11 13 -18 -11 -17 -12 0 0])) == -119000
406421

0 commit comments

Comments
 (0)