givens_decomposition

givens_decomposition(unitary)

Decomposes a unitary matrix into Givens rotations and diagonal phases.

The \(n \times n\) unitary matrix, \(U\), can be decomposed into a diagonal matrix, \(D\), and sequence of \(2 \times 2\) Givens rotations, \(G\), acting on adjacent indices. This algorithm [1] requires at most \(n (n-1) / 2\) such Givens rotations.

Each Givens rotation is defined by a 4-tuple, (c, s, i, j), with:

  • c: the real-valued cosine

  • s: the complex-valued sine

  • i: the first row index

  • j: the second row index

which result in a matrix of the form:

\[\begin{pmatrix} c & s \\ -s^\dagger & c \end{pmatrix}\]
Parameters:

unitary – the unitary matrix, \(U\), to be decomposed.

Returns:

A 2-tuple consisting of

  • the sequence of Givens rotations represented as 4-tuples as explained above

  • the vector of complex phases of the diagonal matrix, \(D\)

The original unitary is recovered by processing the returned rotations in reverse order and right-multiplying the diagonal matrix by the element-wise complex conjugate of each rotation matrix \(G_k\) (as defined above). That is, for \(N\) returned rotations,

\[U = D \cdot \overline{G_N} \cdot \overline{G_{N-1}} \cdots \overline{G_1},\]

where \(\overline{G_k}\) denotes element-wise conjugation (not the conjugate transpose) and each \(G_k\) acts only on rows/columns \(i\) and \(j\) of its rotation.

>>> import numpy as np
>>> from qiskit_fermions.linalg import givens_decomposition
>>> unitary = np.array([[0.6, 0.8j], [0.8, -0.6j]], dtype=complex)
>>> rotations, phases = givens_decomposition(unitary)
>>> reconstructed = np.diag(phases).astype(complex)
>>> for c, s, i, j in rotations[::-1]:
...     givens_mat = np.eye(2, dtype=complex)
...     givens_mat[np.ix_((i, j), (i, j))] = [[c, s], [-s.conjugate(), c]]
...     reconstructed = reconstructed @ givens_mat.conj()
>>> bool(np.allclose(reconstructed, unitary))
True