Pfaffian calculations
A skew-symmetrix matrix $A = -A^T$ has a special property: its determinant is the square of a polynomial function of the matrix entries, called the Pfaffian. That is, $\mathrm{det}(A) = \mathrm{Pf}(A)^2$, but knowing the Pfaffian itself (and its sign, which is lost in the determinant) is useful for a number of applications.
We provide a function pfaffian(A)
to compute the Pfaffian of a skew-symmetric matrix A
.
julia> using SkewLinearAlgebra, LinearAlgebra
julia> A = [0 2 -7 4; -2 0 -8 3; 7 8 0 1;-4 -3 -1 0]
4×4 Matrix{Int64}:
0 2 -7 4
-2 0 -8 3
7 8 0 1
-4 -3 -1 0
julia> pfaffian(A)
-9.000000000000002
julia> det(A) # exact determinant is (-9)^2
80.99999999999999
By default, this computation is performed approximately using floating-point calculations, similar to Julia's det
algorithm for the determinant. However, for a BigInt
matrix, pfaffian(A)
is computed exactly using an algorithm by Galbiati and Maffioli (1994):
julia> pfaffian(BigInt.(A))
-9
julia> det(big.(A)) # also exact for BigInt
81
Note that you need not (but may) pass a SkewHermitian
matrix type to pfaffian
. However, because the Pfaffian is only defined for skew-symmetric matrices, it will give an error if you pass it a non-skewsymmetric matrix:
julia> pfaffian([1 2 3; 4 5 6; 7 8 9])
ERROR: ArgumentError: Pfaffian requires a skew-Hermitian matrix
We also provide a function pfaffian!(A)
that overwrites A
in-place (with undocumented values), rather than making a copy of the matrix for intermediate calculations:
julia> pfaffian!(BigInt[0 2 -7; -2 0 -8; 7 8 0])
0
(Note that the Pfaffian is always zero for any odd size skew-symmetric matrix.)
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 that the Pfaffian is sign * exp(logpf)
. (This is similar to the logabsdet
function in Julia's LinearAlgebra
library to compute the log of the determinant.)
julia> logpf, sign = logabspfaffian(A)
(2.1972245773362196, -1.0)
julia> sign * exp(logpf) # matches pfaffian(A), up to floating-point rounding errors
-9.000000000000002
julia> B = triu(rand(-9:9, 500,500)); B = B - B'; # 500×500 skew-symmetric matrix
julia> pfaffian(B) # overflows double-precision floating point
Inf
julia> pf = pfaffian(big.(B)) # exact answer in BigInt precision (slow)
-149678583522522720601879230931167588166888361287738234955688347466367975777696295859892310371985561723944757337655733584612691078889626269612647408920674699424393216780756729980039853434268507566870340916969614567968786613166601938742927283707974123631646016992038329261449437213872613766410239159659548127386325836018158542150965421313640795710036050440344289340146687857870477701301808699453999823930142237829465931054145755710674564378910415127367945223991977718726
julia> Float64(log(abs(pf))) # exactly rounded log(abs(pfaffian(B)))
1075.7105584607807
julia> logabspfaffian(B) # matches log and sign!
(1075.71055846078, -1.0)
Pfaffian Reference
SkewLinearAlgebra.pfaffian
— Functionpfaffian(A)
Returns the pfaffian of A
where a is a skew-symmetric matrix. If A
is not of type SkewHermitian{<:Real}
, then isskewsymmetric(A)
is checked to ensure that A == -transpose(A)
SkewLinearAlgebra.pfaffian!
— Functionpfaffian!(A)
Similar to pfaffian
, but overwrites A
in-place with intermediate calculations.
SkewLinearAlgebra.logabspfaffian
— Functionlogabspfaffian(A)
Returns a tuple (log|pf(A)|, sign)
, with the log of the absolute value of the pfaffian of A
as first output and the sign of the pfaffian as second output such that pfaffian(A) ≈ sign * exp(log|pf(A)|)
. A
must be a skew-symmetric matrix. If A
is not of type SkewHermitian{<:Real}
, then isskewsymmetric(A)
is checked to ensure that A == -transpose(A)
SkewLinearAlgebra.logabspfaffian!
— Functionlogabspfaffian!(A)
Similar to logabspfaffian
, but overwrites A
in-place with intermediate calculations.