ffsim.linalg.givens_decomposition

ffsim.linalg.givens_decomposition(mat, tol=1e-12, *, max_givens=None, max_layers=None, **optimize_kwargs)[source]

Givens rotation decomposition of a unitary matrix.

The Givens rotation decomposition of an \(n \times n\) unitary matrix \(U\) is given by

\[U = D G_L^* G_{L-1}^* \cdots G_1^*\]

where \(D\) is a diagonal matrix and each \(G_k\) is a Givens rotation. Here, the star \(*\) denotes the element-wise complex conjugate. A Givens rotation acts on the two-dimensional subspace spanned by the \(i\)-th and \(j\)-th basis vectors as

\[\begin{split}\begin{pmatrix} c & s \\ -s^* & c \\ \end{pmatrix}\end{split}\]

where \(c\) is a real number and \(s\) is a complex number. Therefore, a Givens rotation is described by a 4-tuple \((c, s, i, j)\), where \(c\) and \(s\) are the numbers appearing in the rotation matrix, and \(i\) and \(j\) are the indices of the basis vectors of the subspace being rotated. This function always returns Givens rotations with the property that \(i\) and \(j\) differ by at most one, that is, either \(j = i + 1\) or \(j = i - 1\).

The number of Givens rotations \(L\) is at most \(\frac{n (n-1)}{2}\), but it may be less. If we think of Givens rotations acting on disjoint indices as operations that can be performed in parallel, then the entire sequence of rotations can always be performed using at most \(n\) layers of parallel operations. The decomposition algorithm is described in the reference below.

Compression. By default this function returns an exact decomposition. If max_givens or max_layers is specified, the exact decomposition is trimmed to use at most that many Givens rotations or brickwork layers, respectively (never more than the exact decomposition already contains, so a near-identity matrix may use fewer rotations than the budget). The retained rotations lie at the beginning of the brickwork pattern, and their angles (together with the diagonal phases) are numerically optimized to minimize the Hilbert-Schmidt infidelity \(1 - \lvert \operatorname{Tr}(U^\dagger V) \rvert^2 / n^2\) between the original matrix \(U\) and the reconstructed matrix \(V\). Because this objective is invariant to a global phase, the global phase of the reconstructed matrix is ignored when the decomposition is compressed. The returned decomposition is then only approximate. When both max_givens and max_layers are given, the tighter of the two constraints is applied. Note that when the decomposition is compressed, tol is not respected: the optimized angles are chosen to best approximate \(U\), so the reconstructed matrix may differ from \(U\) by more than tol.

References

Parameters:
  • mat (ndarray) – The unitary matrix to decompose into Givens rotations.

  • tol (float) – Matrix entries smaller than this value will be treated as equal to zero when computing the exact decomposition (which the compressed path also starts from).

  • max_givens (int | None) – The maximum number of Givens rotations to use. If specified, the decomposition is compressed to use at most this many Givens rotations.

  • max_layers (int | None) – The maximum number of brickwork layers to use. If specified, the decomposition is compressed to use at most this many layers. The full brickwork pattern has \(n\) layers.

  • optimize_kwargs – Keyword arguments to pass to scipy.optimize.minimize(), which performs the optimization when the decomposition is compressed.

Return type:

tuple[list[tuple[float, complex, int, int]], ndarray]

Returns:

  • A list containing the Givens rotations \(G_1, \ldots, G_L\). Each Givens rotation is represented as a 4-tuple \((c, s, i, j)\), where \(c\) and \(s\) are the numbers appearing in the rotation matrix, and \(i\) and \(j\) are the indices of the basis vectors of the subspace being rotated.

  • A Numpy array containing the diagonal elements of the matrix \(D\).