Skip to content

Commit 82c63de

Browse files
jlbossestevengj
andauthored
Added logabspfaffian(::AbstractMatrix{<:Complex}) (#139)
* Reduced allocations in pfaffian(::AbstractMatrix{<:Complex}) * Added logabspfaffian(::AbstractMatrix{<:Complex}) I realised that the algorithm used to compute `pfaffian(::AbstractMatrix{<:Complex})` can be used to compute the `logabspfaffian` with little modifications and decided to add that too for completeness sake * Added logabspfaffian(::AbstractMatrix{<:Complex}) I realised that the algorithm used to compute `pfaffian(::AbstractMatrix{<:Complex})` can be used to compute the `logabspfaffian` with little modifications and decided to add that too for completeness sake * Update src/pfaffian.jl Co-authored-by: Steven G. Johnson <[email protected]> * Revert "Added logabspfaffian(::AbstractMatrix{<:Complex})" This reverts commit 0b49126. * Removed superfluous @inbounds * Added argmaxabs() for compatibility with julia 1.6 * Merged orign/main in and added argmaxabs() to _logabspfaffian() --------- Co-authored-by: Steven G. Johnson <[email protected]>
1 parent 1e70879 commit 82c63de

File tree

3 files changed

+65
-12
lines changed

3 files changed

+65
-12
lines changed

docs/src/pfaffian.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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` for real skew-symmetric matrices (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` (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

src/pfaffian.jl

Lines changed: 61 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -106,21 +106,21 @@ function _pfaffian!(A::AbstractMatrix{<:Complex})
106106
# Pivot if neccessary
107107
@views kp = k + argmaxabs(A[k+1:end, k])
108108
if kp != k + 1
109-
@inbounds @simd for l in k:n
109+
@simd for l in k:n
110110
A[k+1,l], A[kp,l] = A[kp,l], A[k+1,l]
111111
end
112-
@inbounds @simd for l in k:n
112+
@simd for l in k:n
113113
A[l,k+1], A[l,kp] = A[l,kp], A[l,k+1]
114114
end
115115
pf *= -1
116116
end
117117

118118
# Apply Gauss transformation and update `pf`
119-
@inbounds if A[k+1,k] != zero(eltype(A))
120-
@inbounds @views tauk .= A[k,k+2:end] ./ A[k,k+1]
121-
pf *= @inbounds A[k,k+1]
119+
if A[k+1,k] != zero(eltype(A))
120+
@views tauk .= A[k,k+2:end] ./ A[k,k+1]
121+
pf *= A[k,k+1]
122122
if k + 2 <= n
123-
@inbounds for l1 in eachindex(tauk)
123+
for l1 in eachindex(tauk)
124124
@simd for l2 in eachindex(tauk)
125125
@fastmath A[k+1+l2,k+1+l1] +=
126126
tauk[l2] * A[k+1+l1,k+1] -
@@ -177,6 +177,7 @@ function _logabspfaffian!(A::SkewHermitian{<:Real})
177177
end
178178
return logpf, sgn*sign(det(H.Q))
179179
end
180+
180181
function _logabspfaffian!(A::SkewHermTridiagonal{<:Real})
181182
n = size(A, 1)
182183
isodd(n) && return convert(eltype(A.ev), -Inf), zero(eltype(A.ev))
@@ -188,18 +189,61 @@ function _logabspfaffian!(A::SkewHermTridiagonal{<:Real})
188189
end
189190
return logpf, sgn
190191
end
192+
193+
function _logabspfaffian!(A::AbstractMatrix{<:Complex})
194+
n = size(A, 1)
195+
isodd(n) && return convert(real(eltype(A)), -Inf), zero(eltype(A))
196+
tau = Array{eltype(A)}(undef, n - 2)
197+
logpf = phase = zero(real(eltype(A)))
198+
@inbounds for k in 1:2:n-1
199+
tauk = @view tau[k:end]
200+
201+
# Pivot if neccessary
202+
@views kp = k + argmaxabs(A[k+1:end, k])
203+
if kp != k + 1
204+
@simd for l in k:n
205+
A[k+1,l], A[kp,l] = A[kp,l], A[k+1,l]
206+
end
207+
@simd for l in k:n
208+
A[l,k+1], A[l,kp] = A[l,kp], A[l,k+1]
209+
end
210+
phase += π
211+
end
212+
213+
# Apply Gauss transformation and update `pf`
214+
if A[k+1,k] != zero(eltype(A))
215+
@views tauk .= A[k,k+2:end] ./ A[k,k+1]
216+
logpf += log(abs(A[k,k+1]))
217+
phase += angle(A[k,k+1])
218+
if k + 2 <= n
219+
for l1 in eachindex(tauk)
220+
@simd for l2 in eachindex(tauk)
221+
@fastmath A[k+1+l2,k+1+l1] +=
222+
tauk[l2] * A[k+1+l1,k+1] -
223+
tauk[l1] * A[k+1+l2,k+1]
224+
end
225+
end
226+
end
227+
else
228+
return convert(real(eltype(A)), -Inf), zero(eltype(A))
229+
end
230+
end
231+
232+
return logpf, cis(phase)
233+
end
234+
191235
logabspfaffian!(A::Union{SkewHermitian{<:Real},SkewHermTridiagonal{<:Real}})= _logabspfaffian!(A)
192236
logabspfaffian(A::Union{SkewHermitian{<:Real},SkewHermTridiagonal{<:Real}})= logabspfaffian!(copyeigtype(A))
193237

194238
"""
195239
logabspfaffian(A)
196240
197-
Returns a tuple `(log|Pf A|, sign)`, with the log of the absolute value of the pfaffian of `A` as first output
198-
and the sign (±1) of the pfaffian as second output. A must be a real skew-Hermitian matrix.
199-
If `A` is not of type `SkewHermitian{<:Real}`, then `isskewhermitian(A)`
200-
is checked to ensure that `A == -A'`
241+
Returns a tuple `(log|pf(A)|, sign)`, with the log of the absolute value of the pfaffian of
242+
`A` as first output and the sign of the pfaffian as second output such that
243+
`pfaffian(A) ≈ sign * exp(log|pf(A)|)`. `A` must be a skew-symmetric matrix. If `A` is not of type
244+
`SkewHermitian{<:Real}`, then `isskewsymmetric(A)` is checked to ensure that `A == -transpose(A)`
201245
"""
202-
logabspfaffian(A::AbstractMatrix{<:Real}) = logabspfaffian!(copyeigtype(A))
246+
logabspfaffian(A::AbstractMatrix) = logabspfaffian!(copyeigtype(A))
203247

204248

205249
"""
@@ -211,3 +255,9 @@ function logabspfaffian!(A::AbstractMatrix{<:Real})
211255
isskewhermitian(A) || throw(ArgumentError("Pfaffian requires a skew-Hermitian matrix"))
212256
return _logabspfaffian!(SkewHermitian(A))
213257
end
258+
259+
function logabspfaffian!(A::AbstractMatrix{<:Complex})
260+
LinearAlgebra.require_one_based_indexing(A)
261+
isskewsymmetric(A) || throw(ArgumentError("Pfaffian requires a skew-symmetric matrix"))
262+
return _logabspfaffian!(A)
263+
end

test/runtests.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,9 @@ end
414414
if VERSION v"1.7" # for exact det of BigInt matrices
415415
@test SkewLinearAlgebra.exactpfaffian(Abig)^2 == det(Abig)
416416
end
417+
418+
logpf, sign = logabspfaffian(A)
419+
@test pfaffian(A) sign * exp(logpf)
417420
end
418421

419422
# issue #49

0 commit comments

Comments
 (0)