In the answer to: "https://mathematica.stackexchange.com/questions/125794/compute-numeric-pfaffians-of-matrices-efficiently" there is a pointer to the code below. I hope this will be helpful.
Note also that there is a resource function for Pfaffians at: https://resources.wolframcloud.com/FunctionRepository/resources/Pfaffian/.
The following contains functions to calculate the Pfaffian for real and complex skew-symmetric matrices. Note that the functions don't check (yet) if the input matrix is really skew-symmetric
Functions for skew-symmetric Parlett-Reid tridiagonalization and the corresponding Pfaffian routine
Compute the LTL decomposition of a skew-symmetric matrix using the Parlett-Reid algorithm. The function return T, L and P, where T is a tridiagonal matrix, L a unit lower triangulat matrix and P a permutation matrix, such that P A P^T = L T L^T .
Note that this function is not needed for the Pfaffian computation, but is only provided fro demontration purposes.
SkewLTL[Mat_] := Module[{A, L, Pv, N, ip},
A = Mat;
N = Dimensions[A][[1]];
L = IdentityMatrix[N];
Pv = Range[N];
For[i = 1, i < N - 1, i++,
(*find out the maximum entry in the column i, starting from row i+1*)
ip = i +
Position[Abs[A[[i + 1 ;;, i]]], Max[Abs[A[[i + 1 ;;, i]]]]][[1, 1]];
(*if the maximum entry is not at i+1, permute the matrix so that it is*)
If[i + 1 != ip,
(*Interchange rows and columns in A*)
A[[{i + 1, ip}, ;;]] = A[[{ip, i + 1}, ;;]];
A[[;; , {i + 1, ip}]] = A[[;; , {ip, i + 1}]];
(*interchange rows in L; this amounts to accumulating the product of
individual Gauss eliminations and permutations*)
L[[{i + 1, ip}, 1 ;; i]] = L[[{ip, i + 1}, 1 ;; i]];
(*Accumulate the total permutation matrix*)
Pv[[{i + 1, ip}]] = Pv[[{ip, i + 1}]];
];
(*Build the Gauss vector*)
L[[i + 2 ;;, i + 1]] = A[[i + 2 ;;, i]]/A[[i + 1, i]];
(*Row and column i are eliminated*)
A[[i + 2 ;;, i]] = 0; A[[i, i + 2 ;;]] = 0;
(*Update the remainder of the matrix using an outer product skew-
symmetric update. Note that
column and row i+1 are not affected by the update*)
A[[i + 2 ;; , i + 2 ;; ]] +=
Transpose[{L[[i + 2 ;;, i + 1]]}] . {A[[i + 2 ;;, i + 1]]} -
Transpose[{A[[i + 2 ;;, i + 1]]}] . { L[[i + 2 ;;, i + 1]]};
];
Return[{A, L, SparseArray[{i_, i_} -> 1, {N, N}][[Pv]]}]
]
Compute the Pfaffian of a skew-symmetric matrix using the LTL decomposition
PfaffianLTL[Mat_] := Module[{A, N, ip, pfaff},
A = Mat;
N = Dimensions[A][[1]];
If[OddQ[N], Return[0]];
pfaff = 1;
For[i = 1, i < N - 1, i += 2,
(*find out the maximum entry in the column i, starting from row i+1*)
ip = i +
Position[Abs[A[[i + 1 ;;, i]]], Max[Abs[A[[i + 1 ;;, i]]]]][[1, 1]];
(*if the maximum entry is not at i+1, permute the matrix so that it is*)
If[i + 1 != ip,
(*Interchange rows and columns in A*)
A[[{i + 1, ip}, ;;]] = A[[{ip, i + 1}, ;;]];
A[[;; , {i + 1, ip}]] = A[[;; , {ip, i + 1}]];
(*interchange contributes det(P)=-1*)
pfaff = -pfaff;
];
(*Multiply with every other entry on the diagonal*)
pfaff = pfaff*A[[i, i + 1]];
(*Build the Gauss vector*)
A[[i + 2 ;;, i]] = A[[i + 2 ;;, i]]/A[[i + 1, i]];
(*Update the remainder of the matrix using an outer product skew-
symmetric update. Note that
column and row i+1 are not affected by the update*)
A[[i + 2 ;; , i + 2 ;; ]] += (# - Transpose[#]) &@
Outer[Times, A[[i + 2 ;;, i]], A[[i + 2 ;;, i + 1]]]
(* The above is much faster than this construct for me:
Transpose[{A[[i+2;;,i]]}].{A[[i+2;;,i+1]]}-Transpose[{A[[i+2;;,i+1]]}].{
A[[i+2;;,i]]};*)
];
Return[pfaff*A[[N - 1, N]]]
]
Functions for the Householder tridiagonalization and the corresponding Pfaffian routine
The following contains functions to calculate the Pfaffian for real and complex skew-symmetric matrices. Note that the functions don't check (yet) if the inpiut matrix is really skew-symmetric
Function to compute a so-called Householder vector v for x, i.e. a vector such that (1-2/(v^H v) v v^H) * x is a multiple of the unit vector e_1.
HouseholderVectorReal[x_] := Module[{temp, tempfac, normx},
normx = Norm[x];
If[normx == 0,
Return[{UnitVector[Dimensions[x][[1]], 1], 0, 0}],
If[x[[1]] > 0,
tempfac = normx,
tempfac = -normx];
temp = x;
temp[[1]] += tempfac;
Return[{Normalize[temp], 2, -tempfac} ]]]
HouseholderVectorComplex[x_] := Module[{temp, tempfac, normx},
normx = Norm[x];
If[normx == 0,
Return[{UnitVector[Dimensions[x][[1]], 1], 0, 0}],
tempfac = E^(I Arg[x[[1]]]) Norm[x];
temp = x;
temp[[1]] += tempfac;
Return[{Normalize[temp], 2, -tempfac} ]]]
Now the functions that do the full tridagonalization that is at the heart of the Pfaffian calculation. For an input matrix A, they return {T,Q} such that A=Q T Q^T . In the real case, this should be the same as what is returned from the HessenbergDecomposition, for the complex case there is no Mathematica equivalent. Note that these functions are not needed for the Pfaffian calculation, they are here merely for testing.
SkewTridiagonalize[Mat_] :=
If[MatrixQ[Mat, NumberQ[#] && ! MatchQ[#, _Complex] &],
SkewTridiagonalizeReal[Mat], SkewTridiagonalizeComplex[Mat]]
SkewTridiagonalizeReal[Mat_] := Module[{A, Q, v, beta, alpha},
A = Mat;
Q = IdentityMatrix[Dimensions[A][[1]]];
For[i = 1, i < Dimensions[A][[1]] - 1, i++,
(*Compute the Householder vector*)
{v, beta, alpha} = HouseholderVectorReal[A[[i + 1 ;; , i]]];
(*eliminate the entries in row and column i*)
A[[i + 1, i]] = alpha; A[[i, i + 1]] = -alpha; A[[i + 2 ;;, i]] = 0;
A[[i, i + 2 ;;]] = 0;
(*update the matrix*)
w = beta* A[[i + 1 ;; , i + 1 ;;]] . v;
A[[i + 1 ;; , i + 1 ;; ]] += Transpose[{v}] . {w} - Transpose[{w}] . { v};
(*accumulate the Householder reflections into the full transformation*)
y = Q[[1 ;;, i + 1 ;;]] . v;
Q[[1 ;; , i + 1 ;;]] -= beta*Transpose[{y}] . {v}];
Return[{A, Q}]]
SkewTridiagonalizeComplex[Mat_] := Module[{A, Q, v, beta, alpha},
A = Mat;
Q = IdentityMatrix[Dimensions[A][[1]]];
For[i = 1, i < Dimensions[A][[1]] - 1, i++,
(*Compute the Householder vector*)
{v, beta, alpha} = HouseholderVectorComplex[A[[i + 1 ;; , i]]];
(*eliminate the entries in row and column i*)
A[[i + 1, i]] = alpha; A[[i, i + 1]] = -alpha; A[[i + 2 ;;, i]] = 0;
A[[i, i + 2 ;;]] = 0;
(*update the matrix*)
w = beta* A[[i + 1 ;; , i + 1 ;;]] . Conjugate[v];
A[[i + 1 ;; , i + 1 ;; ]] += Transpose[{v}] . {w} - Transpose[{w}] . { v};
(*accumulate the Householder reflections into the full transformation*)
y = Q[[1 ;;, i + 1 ;;]] . v;
Q[[1 ;; , i + 1 ;;]] -= beta*Transpose[{y}] . Conjugate[{v}]];
Return[{A, Q}]]
Functions to compute the Pfaffian of a real or complex skew-symmetric matrix.
PfaffianHReal[Mat_] := Module[{N, A, v, beta, alpha, pfaff},
A = Mat;
pfaff = 1;
N = Dimensions[A][[1]];
If[OddQ[N], Return[0]];
For[i = 1, i < N - 1, i += 2,
(*Compute the Householder vector*)
{v, beta, alpha} = HouseholderVectorReal[A[[i + 1 ;; , i]]];
(*multiply with off-
diagonal entry and determinant of Householder reflection*)
pfaff *= If[beta == 0, 1, -1]*(-alpha);
(*update the matrix*)
w = beta* A[[i + 1 ;; , i + 1 ;;]] . v;
A[[i + 1 ;; , i + 1 ;; ]] += Transpose[{v}] . {w} - Transpose[{w}] . { v}];
Return[pfaff*A[[N - 1, N]]]]
PfaffianHComplex[Mat_] := Module[{N, A, v, beta, alpha, pfaff},
A = Mat;
pfaff = 1;
N = Dimensions[A][[1]];
If[OddQ[N], Return[0]];
For[i = 1, i < N - 1, i += 2,
(*Compute the Householder vector*)
{v, beta, alpha} = HouseholderVectorComplex[A[[i + 1 ;; , i]]];
(*multiply with off-
diagonal entry and determinant of Householder reflection*)
pfaff *= If[beta == 0, 1, -1]*(-alpha);
(*update the matrix*)
w = beta* A[[i + 1 ;; , i + 1 ;;]] . Conjugate[v];
A[[i + 1 ;; , i + 1 ;; ]] += Transpose[{v}] . {w} - Transpose[{w}] . { v}];
Return[pfaff*A[[N - 1, N]]]]
PfaffianH[Mat_] :=
If[MatrixQ[Mat, NumberQ[#] && ! MatchQ[#, _Complex] &], PfaffianHReal[Mat],
PfaffianHComplex[Mat]]
Finally another function for testing: Compute the Pfaffian (in the real case) from the HessenbergDecomposition
PfaffianHessenberg::Mat =
"Pfaffian computation with Hessenberg decomposition only works for real \
matrices";
PfaffianHessenberg[Mat_] :=
If[MatrixQ[Mat, NumberQ[#] && ! MatchQ[#, _Complex] &],
Module[{H, Q}, {Q, H} = HessenbergDecomposition[Mat];
Return[Det[Q]*Product[H[[i, i + 1]], {i, 1, Dimensions[H][[1]], 2}]]],
Message[PfaffianHessenberg::Mat]]
Pfaffian routine combining all of the approaches above
Options[Pfaffian] = {Method -> "ParlettReid"}
{Method -> "ParlettReid"}
Pfaffian::Method =
"Unrecognized option `1` (must be either \"ParlettReid\", \"Householder\" \
or \"Hessenberg\")";
Pfaffian[Mat_, OptionsPattern[]] := Switch[OptionValue[Method],
"ParlettReid", PfaffianLTL[Mat], "Householder", PfaffianH[Mat],
"Hessenberg", PfaffianHessenberg[Mat], _,
Message[Pfaffian::Method, OptionValue@Method]]
Tests
Now some tests
First for real matrices
A = RandomReal[{0, 1}, {8, 8}]; A = A - A\[Transpose];
Pfaffian[A]
-0.311919
Chop[Det[A] - Pfaffian[A]^2]
0
Pfaffian[A, Method -> "Householder"]
-0.311919
Pfaffian[A, Method -> "Hessenberg"]
-0.311919
{T, L, P} = SkewLTL[A];
P . A . P\[Transpose] - L . T . L\[Transpose] // MatrixForm // Chop
{T, Q} = SkewTridiagonalize[A];
A - Q . T . Q\[Transpose] // MatrixForm // Chop
Then for complex matrices
B = RandomComplex[{0, 1 + I}, {8, 8}]; B = B - B\[Transpose];
Pfaffian[B]
-0.247022 + 0.895042 I
Chop[Det[B] - Pfaffian[B]^2]
0
Pfaffian[B, Method -> "Householder"]
-0.247022 + 0.895042 I
PfaffianHessenberg[B]
PfaffianHessenberg::Mat: Pfaffian computation with Hessenberg decomposition only works for real matrices
{T, L, P} = SkewLTL[B];
P . B . P\[Transpose] - L . T . L\[Transpose] // MatrixForm // Chop
{T, Q} = SkewTridiagonalize[B];
B - Q . T . Q\[Transpose] // MatrixForm // Chop