torch_pfaffian.strategies package¶
Submodules¶
torch_pfaffian.strategies.pfaffian_block_det module¶
- class torch_pfaffian.strategies.pfaffian_block_det.PfaffianBlockDet(*args, **kwargs)¶
Bases:
PfaffianStrategyPfaffian of a block-antidiagonal skew-symmetric matrix from the determinant of its off-diagonal block.
For a matrix of the form
\[\begin{split}A = \begin{pmatrix} 0 & B \\ -B^T & 0 \end{pmatrix}, \qquad \text{pf}(A) = (-1)^{n(n-1)/2} \det(B),\end{split}\]with
Bthe upper-rightn x nblock, a singlen x ndeterminant gives the signed Pfaffian. This identity holds only for that block form: on a general skew-symmetric matrix the formula returns a wrong value. The forward therefore validates the input withis_block_form()(both diagonal blocks exactly zero, even dimension) and raisesValueErrorotherwise. The check reads both diagonal blocks and costs up to three host synchronizations; setCHECK_BLOCK_FORMtoFalse, or passFalseas the second positional argument ofapply()(apply(matrix, False)), to skip it for inputs known to have the block form.The backward returns the same gradient convention as the general strategies,
(1 / 2) pf(A) (A^{-1})^Ton the full matrix, split between the upper-right and lower-left blocks.- CHECK_BLOCK_FORM = True¶
- NAME = 'PfaffianBlockDet'¶
- SVD_COFACTOR_MIN_BLOCK_SIZE = 17¶
- static backward(ctx: BackwardCFunction, grad_output: Tensor) tuple[torch.Tensor | None, None]¶
Compute the gradient of the Pfaffian with respect to the full input matrix.
With
pf = c * det(B),c = (-1)^{n(n-1)/2}andBthe upper-right block, Jacobi’s formula givesd pf / d B = c * adj(B)^Twhereadj(B) = det(B) B^{-1}is the classical adjugate. For the block form,A^{-1} = [[0, -B^{-T}], [B^{-1}, 0]], so the general-strategy gradient(1 / 2) pf(A) (A^{-1})^Tis(1 / 2) c adj(B)^Tin the upper-right block, its negated transpose in the lower-left block, and zero in the diagonal blocks. Returning that split (rather than the whole derivative in the upper-right block) makesA.gradindependent of which strategy computed the Pfaffian; the gradient with respect toBofA = [[0, B], [-B^T, 0]]is the same either way.For an invertible block the adjugate is the single inverse
pf * (B^{-1})^T. For a (numerically) singular block that product is inaccurate or garbage, yet the true derivativec * adj(B)^Tis finite and generally nonzero. The adjugate is then computed exactly by_exact_cofactor_matrix()(from an SVD, with the cofactor determinants as fallback), on the flagged batch elements only so invertible inputs keep the single cheap inverse. A block is flagged byPfaffianStrategy._flagged_inverse(), the same dtype-adaptive criteria asPfaffianStrategy.pfaffian_grad_matrix()(LU pivot ratio, LUinfoand inverse residual; an exactly singular block can have a round-off nonzero forward determinant, sopf == 0alone misses it). Flagging a well-conditioned block only costs speed, never accuracy. Flagged blocks whosegrad_outputis exactly zero get a zero gradient without the exact path, since the gradient is linear ingrad_output. WithEXACT_SINGULAR_GRADset toFalsethe backward is host-synchronization-free and blocks singular at round-off level (seePfaffianStrategy._round_off_inverse()) get a zero gradient. That test is an LU pivot ratio belown * eps, so an invertible but strongly graded block (for examplediag(1, 1e-9)infloat32) is also given a zero gradient: this mode trades accuracy for speed.The Pfaffian is holomorphic in the entries of
A, so for complex inputs the backward returns the conjugate of the analytic derivative timesgrad_output, which is PyTorch’s Wirtinger convention. For real inputs the conjugation is a no-op.- Parameters:
ctx – Context holding the saved input matrix and the forward Pfaffian.
grad_output – Gradient of the output with respect to the loss.
- Returns:
Gradient of the input matrix (or
Nonewhen the input does not require grad), andNonefor thecheck_block_formflag.- Return type:
tuple[torch.Tensor | None, None]
- static forward(matrix: Tensor, check_block_form: bool | None = None) Tensor¶
Compute the Pfaffian of a block-antidiagonal skew-symmetric matrix from its upper-right block.
- Parameters:
matrix – Skew-symmetric matrix
[[0, B], [-B^T, 0]]of shape(..., 2n, 2n).check_block_form – Whether to validate the block form.
None(default) usesCHECK_BLOCK_FORM.
- Returns:
The signed Pfaffian, of shape
(...,).- Return type:
torch.Tensor
- Raises:
ValueError – If the check is enabled and
matrixdoes not have the block form.
- static is_block_form(matrix: Tensor) bool¶
Whether every matrix of the batch has the form
[[0, B], [-B^T, 0]]with squareB.Only the structure is checked (square, even dimension, both diagonal blocks exactly zero); the skew-symmetry relation between the two off-diagonal blocks is assumed, as for every strategy. The entry
(0, 1)is tested first so a general skew-symmetric input is rejected after reading a single entry per batch element.- Parameters:
matrix – Matrix of shape
(..., m, m).- Returns:
Truewhen all batch elements have the block-antidiagonal form.- Return type:
bool
- static setup_context(ctx: FunctionCtx, inputs, output)¶
torch_pfaffian.strategies.pfaffian_det module¶
- class torch_pfaffian.strategies.pfaffian_det.PfaffianDet(*args, **kwargs)¶
Bases:
PfaffianStrategyCompute the Pfaffian as
pf = sqrt(|det(A)|)and rely on autograd for the backward pass.The other strategies are full
torch.autograd.Functionimplementations with a custom analytic backward. This strategy instead differentiates straight throughdetandsqrtwith PyTorch autograd, so it overridesapply()to bypass thetorch.autograd.Functionmachinery. The radicand is clamped toEPSILONfor numerical stability, matching the other determinant-based strategy.- NAME = 'PfaffianDet'¶
- static apply(matrix: Tensor) Tensor¶
torch_pfaffian.strategies.pfaffian_fdbpf module¶
- class torch_pfaffian.strategies.pfaffian_fdbpf.PfaffianFDBPf(*args, **kwargs)¶
Bases:
PfaffianStrategyThis class implements the Pfaffian using the determinant of the matrix for the forward pass and the derivative of the Pfaffian with respect to the input matrix for the backward pass.
- NAME = 'PfaffianFDBPf'¶
- static backward(ctx: BackwardCFunction, grad_output: Tensor) Tensor | None¶
Gradient of the Pfaffian magnitude with respect to the input matrix.
\[\frac{\partial |\text{pf}(A)|}{\partial A_{ij}} = \frac{|\text{pf}(A)|}{2} (A^{-1})_{ji}\]For complex inputs the magnitude is a real function of a complex matrix, so the backward follows PyTorch’s Wirtinger convention (
z.grad = 2 d L / d conj(z)for a real loss) and returns(|pf(A)| / 2) conj(A^{-T}). For real inputs the conjugation is a no-op.The inverse uses
torch.linalg.inv()(an LU factorization) rather thantorch.linalg.pinv()(an SVD), which can fail to converge on ill-conditioned or near-repeated-singular-value inputs. The forward floors the magnitude atsqrt(EPSILON), so singular elements (pfat that floor, and the always-singular odd-dimensional inputs whosepfis0) are replaced by the identity before the batched inverse:invraises on an exactly-singular matrix, and there the gradient is negligible anyway since it scales withpf.- Parameters:
ctx – Context holding the saved input matrix and the forward Pfaffian magnitude.
grad_output – Gradient of the output with respect to the loss.
- Returns:
Gradient of the input matrix, or
Nonewhen the input does not require grad.- Return type:
torch.Tensor | None
- static forward(matrix: Tensor)¶
torch_pfaffian.strategies.pfaffian_masked_magnitude module¶
- class torch_pfaffian.strategies.pfaffian_masked_magnitude.PfaffianMaskedMagnitude(*args, **kwargs)¶
Bases:
PfaffianStrategyPfaffian magnitude
sqrt(max(|det(A)|, epsilon))whose gradient is zero wherePf(A)vanishes up to round-off.The forward is the fast determinant path of
PfaffianDet, floored atsqrt(epsilon)with a per-callepsilon. The backward returns(|Pf(A)| / 2) conj(A^{-T})(PyTorch’s Wirtinger convention for a real output of a complex input), except that it gives a zero gradient to:elements whose determinant was clamped to
epsilonin the forward (the mask is saved in the forward rather than recomputed from the floored value, which could be off by one ulp);elements whose LU factorization fails, or whose 1-norm reciprocal condition number is not finite;
elements that vanish up to round-off, flagged when both
rcond_1(A) <= ROUNDOFF_RCOND_SCALE * epsandsigma_min(A) / sigma_max(A) <= ROUNDOFF_SPECTRAL_SCALE * eps, withepsthe machine epsilon of the dtype.
A Pfaffian that is zero in exact arithmetic is computed as round-off, and the unmasked backward then divides noise by noise and returns an
O(1)gradient. No numerical test separates such a structural zero from a genuine value at round-off level (in float32 the rare outcomes of a state near a basis state are genuine yet already 10 to 35% off), so this strategy is opt-in: use it when exact zeros are expected, for example the parity-forbidden outcomes of a basis state.The determinant is computed with a backward error
~ eps * sigma_max, so the round-off of|Pf(A)| = prod sigma_pairis~ eps * sigma_max * |Pf(A)| / sigma_min: the Pfaffian is unresolved whensigma_min / sigma_maxis at round-off level, at every size. For a skew matrixrcond_1(A) <= sigma_min / sigma_max, and the 1-norm ratio can be up tomtimes smaller, so the 1-norm test alone would mask small but resolved Pfaffians, while the spectral test alone would miss the structural zeros of long circuits. Both tests must agree:a structural zero rounded once measures
sigma_min / sigma_max <= 0.5 epsup tom = 256; accumulated round-off (up to1e5Givens rotations applied in place in complex128) raises it to~10 epswhile itsrcond_1stays below4 eps. Longer circuits drift past both tests;a Pfaffian with
sigma_min / sigma_max ~ 4 epsis still accurate to a few percent, so only the 1-norm test separates it from a structural zero.
The backward costs one inverse, two 1-norms and one set of singular values per element. When the SVD fails to converge, the whole batch falls back on the 1-norm test alone.
The criterion has a known limit: it is not scale-free across blocks, so a resolved element whose exact block structure spans a very wide range (about
0.25 / epsbetween block scales) can be masked; once rotated, such an element is no longer resolved in floating point. Elements whose determinant overflows are recovered bytorch_pfaffian.pfaffian()in the log domain, which carries the unmasked gradient. Only dtypes with an LU factorization are supported (float32, float64, complex64, complex128).Odd-dimensional matrices are singular, so their magnitude is the floor
sqrt(epsilon)with a zero gradient.apply()returns only the magnitude.- NAME = 'PfaffianMaskedMagnitude'¶
- ROUNDOFF_RCOND_SCALE = 4.0¶
- ROUNDOFF_SPECTRAL_SCALE = 16.0¶
- classmethod apply(matrix: Tensor, epsilon: float | None = None) Tensor¶
Compute the masked-gradient Pfaffian magnitude.
- Parameters:
matrix – Skew-symmetric matrices of shape
(..., m, m), real or complex.epsilon – Floor of
|det(A)|.None(default) usesPfaffianStrategy.EPSILON.
- Returns:
The magnitude of shape
(...,)in the real dtype ofmatrix.- Return type:
torch.Tensor
- static backward(ctx: BackwardCFunction, grad_output: Tensor, grad_clamped: Tensor | None = None) tuple[torch.Tensor | None, None]¶
Gradient
grad_output * (|Pf(A)| / 2) conj(A^{-T}), zero at clamped, singular and round-off elements.- Parameters:
ctx – Context holding the input matrix, the forward magnitude and the clamped mask.
grad_output – Gradient of the loss with respect to the magnitude, of shape
(...,).grad_clamped – Unused; the clamped mask is not differentiable.
- Returns:
Gradient of the input matrix (or
Nonewhen it does not require grad), andNoneforepsilon.- Return type:
tuple[torch.Tensor | None, None]
- static forward(matrix: Tensor, epsilon: float | None = None) tuple[torch.Tensor, torch.Tensor]¶
Compute the floored magnitude and the mask of the elements clamped to the floor.
- Parameters:
matrix – Skew-symmetric matrices of shape
(..., m, m), real or complex.epsilon – Floor of
|det(A)|.None(default) usesPfaffianStrategy.EPSILON.
- Returns:
The magnitude
sqrt(max(|det(A)|, epsilon))of shape(...,)in the real dtype ofmatrix, and the boolean clamped mask of shape(...,).- Return type:
tuple[torch.Tensor, torch.Tensor]
- static setup_context(ctx: FunctionCtx, inputs, output)¶
torch_pfaffian.strategies.pfaffian_parlett_reid module¶
- class torch_pfaffian.strategies.pfaffian_parlett_reid.PfaffianParlettReid(*args, **kwargs)¶
Bases:
PfaffianStrategyCompute the signed Pfaffian of a skew-symmetric matrix with the Parlett-Reid algorithm.
The forward performs a batched skew-tridiagonalization with partial pivoting: the batch is processed together and only the
n / 2column-elimination steps are sequential. The result is the signed Pfaffian, unlike the determinant-based strategies which return only the magnitude. The backward uses the closed formd pf(A) / d A = (1 / 2) pf(A) (A^{-1})^Tvia the Pfaffian adjugate, with no autograd graph over the elimination. For invertible inputs this is a single inverse; for singular inputs (pf == 0) the adjugate is computed exactly from minor Pfaffians, so the gradient is correct everywhere (seePfaffianStrategy.pfaffian_grad_matrix()).The input is a skew-symmetric matrix of shape
(..., 2n, 2n).- NAME = 'PfaffianParlettReid'¶
- static backward(ctx: BackwardCFunction, grad_output: Tensor) Tensor | None¶
Gradient of the signed Pfaffian with respect to the input matrix.
\[\frac{\partial \text{pf}(A)}{\partial A_{ij}} = \frac{\text{pf}(A)}{2} (A^{-1})_{ji}\]- Parameters:
ctx – Context holding the saved input matrix and the forward Pfaffian.
grad_output – Gradient of the output with respect to the loss.
- Returns:
Gradient of the input matrix, or
Nonewhen the input does not require grad.- Return type:
torch.Tensor | None
- static forward(matrix: Tensor) Tensor¶
torch_pfaffian.strategies.pfaffian_rust_parlett_reid module¶
- class torch_pfaffian.strategies.pfaffian_rust_parlett_reid.RustPfaffianParlettReid(*args, **kwargs)¶
Bases:
PfaffianStrategyCompute the signed Pfaffian with the Rust Parlett-Reid kernel.
The forward moves the input to a contiguous CPU array and calls the compiled
torch_pfaffian._rustkernel for the input dtype, each running natively at that precision:float16,float32/complex64andfloat64/complex128map to the half-, single- and double-precision kernels. Half precision is the caller’s explicit choice and carries its risk: the elimination runs entirely infloat16, which is the least accurate kernel and can overflow the narrowfloat16range toinffor larger or larger-scaled matrices (usefloat32for a measurably more accurate result). Complex inputs are computed natively over the complex field (no imaginary part is discarded), giving the correct complex signed Pfaffian. Any other dtype raisesTypeError. The result is cast back to the input dtype and device. The backward is the same asPfaffianParlettReid(the Pfaffian adjugated pf(A) / d A = (1 / 2) pf(A) (A^{-1})^T, exact for invertible and singular inputs and conjugated for complex autograd), computed in PyTorch. CUDA inputs are evaluated on CPU for the forward; the backward runs on the input device.The input is a skew-symmetric matrix of shape
(..., n, n).- NAME = 'RustPfaffianParlettReid'¶
- static backward(ctx: BackwardCFunction, grad_output: Tensor) Tensor | None¶
Gradient of the signed Pfaffian with respect to the input matrix.
\[\frac{\partial \text{pf}(A)}{\partial A_{ij}} = \frac{\text{pf}(A)}{2} (A^{-1})_{ji}\]- Parameters:
ctx – Context holding the saved input matrix and the forward Pfaffian.
grad_output – Gradient of the output with respect to the loss.
- Returns:
Gradient of the input matrix, or
Nonewhen the input does not require grad.- Return type:
torch.Tensor | None
- static forward(matrix: Tensor) Tensor¶
torch_pfaffian.strategies.pfaffian_slog module¶
- class torch_pfaffian.strategies.pfaffian_slog.SlogPfaffianStrategy(*args, **kwargs)¶
Bases:
FunctionLog-domain signed Pfaffian returning
(phase, log|Pf|)with an analytic backward forlog|Pf|.This is a standalone
torch.autograd.Function(deliberately not aPfaffianStrategysubclass, so it does not enter the scalar-returning strategy registry). The forward is the host-synchronization-freeparlett_reid_slog(); the returnedphaseis marked non-differentiable (for real inputs it is piecewise constant, and callers needing the full complex derivative use the linear-domainPfaffianParlettReid).log|Pf|is differentiable whereverPf != 0, with gradient0.5 * (A^{-1})^Tunder PyTorch’s Wirtinger convention (the.conj()is a no-op for real inputs); batch elements withPf = 0receive an exactly zero gradient.The input is a skew-symmetric matrix of shape
(..., m, m).- static backward(ctx: Any, *grad_outputs: Any) Tensor¶
Gradient of
log|Pf(A)|with respect to the input matrix.\[\frac{\partial \log|\text{pf}(A)|}{\partial A_{ij}} = \frac{1}{2} (A^{-1})_{ji}\]The gradient of the non-differentiable
phaseoutput is ignored. Singular elements (phase == 0) are replaced by the identity before the batched inverse and then assigned an exactly zero gradient, so the backward never synchronizes with the host and never raises.- Parameters:
ctx – Context holding the saved input matrix and the forward phase.
grad_outputs – Gradients of
(phase, log_abs)with respect to the loss; only thelog_absgradient is used.
- Returns:
Gradient of the input matrix, of shape
(..., m, m).- Return type:
torch.Tensor
- static forward(matrix: Tensor) tuple[torch.Tensor, torch.Tensor]¶
- static setup_context(ctx: Any, inputs: tuple[torch.Tensor, ...], output: tuple[torch.Tensor, torch.Tensor]) None¶
torch_pfaffian.strategies.pfaffian_small module¶
- class torch_pfaffian.strategies.pfaffian_small.PfaffianSmall(*args, **kwargs)¶
Bases:
PfaffianStrategySigned Pfaffian for small even dimensions via the unrolled perfect-matching kernel.
Like
PfaffianDet, this strategy differentiates straight through plain tensor ops rather than a custom analytic backward, so it overridesapply()to callsmall_pfaffian()directly and lets autograd handle the backward. The kernel is exact and autograd-friendly for even dimensionsm <= SMALL_N_MAX.For
m >= 2the input is re-skew-symmetrized (A = 0.5 * (A - A^T)) before the kernel. The unrolled kernel only reads the upper triangle, so raw autograd would pile the whole gradient there, whereas the analytic vector-Jacobian product of the other strategies returns the skew-split convention0.5 * pf(A) (A^{-1})^T. The projection is idempotent for skew inputs, so the forward is unchanged while the gradient matches the other strategies (including atm = 2, where the gradient is[[0, 0.5], [-0.5, 0]]rather than the upper-triangle[[0, 1], [0, 0]]).The input is a skew-symmetric matrix of shape
(..., m, m).- NAME = 'PfaffianSmall'¶
- static apply(matrix: Tensor) Tensor¶
torch_pfaffian.strategies.strategy module¶
- class torch_pfaffian.strategies.strategy.PfaffianStrategy(*args, **kwargs)¶
Bases:
Function- EPSILON = 1e-30¶
- EXACT_SINGULAR_GRAD = True¶
- INVERSE_RESIDUAL_RTOL_EXPONENT = 0.5¶
- MINOR_CHUNK_ELEMENTS = 1048576¶
- NAME = 'PfaffianStrategy'¶
- NULL_SPACE_SEPARATION_EXPONENT = 0.25¶
- SINGULARITY_RTOL_EXPONENT = 0.75¶
- static backward(ctx: BackwardCFunction, grad_output)¶
- static forward(matrix: Tensor)¶
- classmethod pfaffian_grad_matrix(matrix: Tensor, pfaffian: Tensor, grad_output: Tensor) Tensor¶
Gradient of the signed Pfaffian with respect to the input matrix.
Uses the closed form
d pf(A) / d A = (1 / 2) pf(A) (A^{-1})^Tvia the Pfaffian adjugatepf(A) A^{-1}. For well-conditioned inputs the adjugate ispf(A) * inv(A)(a single inverse); for (numerically) singular inputs, where that product would be inaccurate or garbage, the adjugate is recomputed exactly by_singular_pfaffian_adjugate()(from the singular values and at most one minor Pfaffian when the corank is clear, else from all the minor Pfaffians of_pfaffian_adjugate(), usingcls’s own forward). The minor-based path runs only on the flagged batch elements, so well-conditioned inputs keep the single cheap inverse. Flagged elements whosegrad_outputis exactly zero (for example the elements of a magnitude whose gradient vanishes atpf = 0) get a zero gradient without the minor-based path, since the gradient is linear ingrad_output; this skip is disabled whengrad_outputitself requires grad (double backward, forward-mode products such astorch.autograd.functional.jvp).An element is flagged by
_flagged_inverse()(the LU pivot ratiomin|U_ii| / max|U_ii| <= eps^0.75, a nonzero LUinfo, or an inverse residual||A A^{-1} - I||_max > eps^0.5), or when its forward Pfaffian is exactly0(which also catches a Pfaffian that underflowed on a well-conditioned matrix with tiny entries). An exactly-singular matrix has a round-off-sized LU pivot (and a round-off forward Pfaffian, rarely exactly0), so the pivot ratio catches it, while the residual catches ill-conditioned elements whose pivots look benign. The pivot ratio is scale-free: unlike a test of|pf|againstmax|A_ij|^(n/2), it does not flag well-conditioned matrices whose entries span several orders of magnitude (e.g. block-diagonal matrices). Flagging a well-conditioned element only costs speed, never accuracy.The inverse is an LU factorization (
torch.linalg.lu_factor_ex()andtorch.linalg.lu_solve(), ortorch.linalg.inv_ex()in double backward) rather thantorch.linalg.pinv()(an SVD), which can fail to converge on ill-conditioned or near-repeated-singular-value inputs. A skew-symmetricAis invertible exactly whenpf(A) != 0(sincedet(A) = pf(A)^2), so the inverse is only relied upon on the unflagged elements. Flagged elements are solved against the identity factors instead, so the batched solve never divides by a zero pivot.The Pfaffian is holomorphic in the entries of
A, so for complex inputs the backward returns the conjugate of the analytic derivative,conj(d pf / d A) * grad_output, which is PyTorch’s Wirtinger convention for complex autograd. For real inputs the conjugation is a no-op, so real gradients are unchanged.All of the above assumes
EXACT_SINGULAR_GRADisTrue(the default). When it isFalsethe backward takes a fully host-synchronization-free path, skipping the.any()branch and the adjugate entirely: elements singular at round-off level (an LU pivot ratio at mostn * eps, an LU failure, orpf == 0; finite inputs only, so NaN still propagates) receive an exactly zero gradient (the true derivative there is nonzero only at corank exactly 2), and every other element, however ill-conditioned, the single inversepf(A) A^{-1}(accurate to aboutcond(A) * eps). In this mode flagging trades accuracy for speed, unlike the exact mode.- Parameters:
matrix – The saved input matrix of shape
(..., n, n).pfaffian – The saved forward Pfaffian of shape
(...,).grad_output – Gradient of the output with respect to the loss, of shape
(...,).
- Returns:
Gradient of the input matrix, of shape
(..., n, n).- Return type:
torch.Tensor
- static setup_context(ctx: FunctionCtx, inputs, output)¶