qml.math.decomposition.givens_decomposition

givens_decomposition(unitary)[source]

Decompose a unitary into a sequence of Givens rotation gates with phase shifts and a diagonal phase matrix.

This decomposition is based on the construction scheme given in Optica, 3, 1460 (2016), which allows one to write any \(N\times N\) unitary matrix \(U\) as:

\[U = D \left(\prod_{m \in G} T_m(\theta, \phi)\right),\]

where \(D\) is a diagonal phase matrix, \(T_m(\theta, \phi)\) is the Givens rotation (with phase shift) between matrix indices \(m\) and \(m+1\) (zero-based indexing), and \(G\) defines the ordered sequence of the Givens rotations. The explicit form of the Givens rotation with phase shift reads:

\[\begin{split}T(\theta, \phi) = \begin{bmatrix} \mathbb{I}_{m} & 0 & 0 & 0 \\ 0 & e^{i \phi} \cos(\theta) & -\sin(\theta) & 0 \\ 0 & e^{i \phi} \sin(\theta) & \cos(\theta) & 0 \\ 0 & 0 & 0 & \mathbb{I}_{N-m-2} \end{bmatrix},\end{split}\]

where \(\theta \in [0, \pi/2]\) is the angle of rotation and \(\phi \in [0, 2 \pi]\) represents the phase shift.

For real-valued matrices with unit determinant, i.e. special orthogonal \(U\), all phase angles \(\phi\) vanish and \(D\) can be fixed to the identity, absorbing its phases \(\pm 1\) (with an even number of \(-1\)s due to the determinant constraint) into the \(T\) matrices. Whether \(U\) is orthogonal is inferred from the data type of unitary. If the determinant of an orthogonal \(U\) is negative, this will show as a single negative phase in the first output value of givens_decomposition.

Example

unitary = np.array([[ 0.73678+0.27511j, -0.5095 +0.10704j, -0.06847+0.32515j],
                    [-0.21271+0.34938j, -0.38853+0.36497j,  0.61467-0.41317j],
                    [ 0.41356-0.20765j, -0.00651-0.66689j,  0.32839-0.48293j]])

phase_mat, ordered_rotations = givens_decomposition(unitary)
>>> phase_mat
tensor([-0.20604358+0.9785369j , -0.82993272+0.55786114j,
        0.56230612-0.82692833j], requires_grad=True)
>>> ordered_rotations
[(tensor([[-0.65087861-0.63937521j, -0.40933651-0.j        ],
        [-0.29201359-0.28685265j,  0.91238348-0.j        ]], requires_grad=True),
(0, 1)),
(tensor([[ 0.47970366-0.33308926j, -0.8117487 -0.j        ],
        [ 0.66677093-0.46298215j,  0.5840069 -0.j        ]], requires_grad=True),
(1, 2)),
(tensor([[ 0.36147547+0.73779454j, -0.57008306-0.j        ],
        [ 0.2508207 +0.51194108j,  0.82158706-0.j        ]], requires_grad=True),
(0, 1))]
Parameters:

unitary (tensor) – unitary matrix on which decomposition will be performed

Returns:

diagonal elements of the phase matrix \(D\) and Givens rotation matrix \(T\) with their indices

Return type:

(tensor_like, list[(tensor_like, tuple)])

Raises:

ValueError – if the provided matrix is not square.

Pseudocode

The algorithm that implements the decomposition is the following:

def givens_decomposition(U):
    for i in range(1, N):
        if i % 2:
            for j in range(0, i):
                # Find T^-1(i-j, i-j+1) matrix that nulls element (N-j, i-j) of U
                # Update U = U @ T^-1(i-j, i-j+1)
        else:
            for j in range(i):
                # Find T(N+j-i-1, N+j-i) matrix that nulls element (N+j-i, j) of U
                # Update U = T(N+j-i-1, N+j-i) @ U

    if real_data_type:
        # Absorb the diagonal phases, which can only be 1s and an even number of
        # -1s, in the T gates. Adjust ordering and/or matrices of left-applied
        # and right-applied T matrices so that they make up U instead of U^{-1}.
    else:
        # Commute the diagonal phases from between the left-applied and right-applied
        # T matrices to the left. Adjust ordering and/or matrices of left-applied
        # and right-applied T matrices so that they make up U instead of U^{-1}.