def test_unitary_eig(matrix): # np.linalg.eig(matrix) won't work for the perturbed matrix d, vecs = unitary_eig(matrix) # test both unitarity and correctness of decomposition np.testing.assert_allclose(matrix, vecs @ np.diag(d) @ vecs.conj().T, atol=1e-14)
def test_non_unitary_eig(): with pytest.raises(Exception): unitary_eig( np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 0, 1, 2], [3, 4, 5, 6]]))
def _two_qubit_multiplexor_to_ops( q0: ops.Qid, q1: ops.Qid, q2: ops.Qid, u1: np.ndarray, u2: np.ndarray, shift_left: bool = True, diagonal: Optional[np.ndarray] = None, atol: float = 1e-8, ) -> Tuple[Optional[np.ndarray], List[ops.Operation]]: r"""Converts a two qubit double multiplexor to circuit. Input: U_1 ⊕ U_2, with select qubit a (i.e. a = |0> => U_1(b,c), a = |1> => U_2(b,c). We want this: $$ U_1 ⊕ U_2 = (V ⊕ V) @ (D ⊕ D^{\dagger}) @ (W ⊕ W) $$ We can get it via: $$ U_1 = V @ D @ W (1) U_2 = V @ D^{\dagger} @ W (2) $$ We can derive $$ U_1 U_2^{\dagger}= V @ D^2 @ V^{\dagger}, (3) $$ i.e the eigendecomposition of $U_1 U_2^{\dagger}$ will give us D and V. W is easy to derive from (2). This function, after calculating V, D and W, also returns the circuit that implements these unitaries: V, W on qubits b, c and the middle diagonal multiplexer on a,b,c qubits. The resulting circuit will have only known two-qubit and one-qubit gates, namely CZ, CNOT and rx, ry, PhasedXPow gates. Args: q0: first qubit q1: second qubit q2: third qubit u1: two-qubit operation on b,c for a = |0> u2: two-qubit operation on b,c for a = |1> shift_left: return the extracted diagonal or not diagonal: an incoming diagonal to be merged with Returns: The circuit implementing the two qubit multiplexor consisting only of known two-qubit and single qubit gates """ u1u2 = u1 @ u2.conj().T eigvals, v = cirq.unitary_eig(u1u2) d = np.diag(np.sqrt(eigvals)) w = d @ v.conj().T @ u2 circuit_u1u2_mid = _middle_multiplexor_to_ops(q0, q1, q2, eigvals) if diagonal is not None: v = diagonal @ v d_v, circuit_u1u2_r = opt.two_qubit_matrix_to_diagonal_and_operations( q1, q2, v, atol=atol) w = d_v @ w # if it's interesting to extract the diagonal then let's do it if shift_left: d_w, circuit_u1u2_l = opt.two_qubit_matrix_to_diagonal_and_operations( q1, q2, w, atol=atol) # if we are at the end of the circuit, then just fall back to KAK else: d_w = None circuit_u1u2_l = opt.two_qubit_matrix_to_operations( q1, q2, w, allow_partial_czs=False, atol=atol) return d_w, circuit_u1u2_l + circuit_u1u2_mid + circuit_u1u2_r