def three_qubit_matrix_to_operations( q0: ops.Qid, q1: ops.Qid, q2: ops.Qid, u: np.ndarray, atol: float = 1e-8 ) -> Sequence[ops.Operation]: """Returns operations for a 3 qubit unitary. The algorithm is described in Shende et al.: Synthesis of Quantum Logic Circuits. Tech. rep. 2006, https://arxiv.org/abs/quant-ph/0406176 Args: q0: first qubit q1: second qubit q2: third qubit u: unitary matrix atol: A limit on the amount of absolute error introduced by the construction. Returns: The resulting operations will have only known two-qubit and one-qubit gates based operations, namely CZ, CNOT and rx, ry, PhasedXPow gates. Raises: ValueError: If the u matrix is non-unitary or not of shape (8,8). """ if np.shape(u) != (8, 8): raise ValueError(f"Expected unitary matrix with shape (8,8) got {np.shape(u)}") if not cirq.is_unitary(u, atol=atol): raise ValueError(f"Matrix is not unitary: {u}") try: from scipy.linalg import cossin except ImportError: # coverage: ignore # coverage: ignore raise ImportError( "cirq.three_qubit_unitary_to_operations requires " "SciPy 1.5.0+, as it uses the cossin function. Please" " upgrade scipy in your environment to use this " "function!" ) (u1, u2), theta, (v1h, v2h) = cossin(u, 4, 4, separate=True) cs_ops = _cs_to_ops(q0, q1, q2, theta) if len(cs_ops) > 0 and cs_ops[-1] == cirq.CZ(q2, q0): # optimization A.1 - merging the last CZ from the end of CS into UD # cz = cirq.Circuit([cs_ops[-1]]).unitary() # CZ(c,a) = CZ(a,c) as CZ is symmetric # for the u1⊕u2 multiplexor operator: # as u1(b,c) is the operator in case a = \0>, # and u2(b,c) is the operator for (b,c) in case a = |1> # we can represent the merge by phasing u2 with I ⊗ Z u2 = u2 @ np.diag([1, -1, 1, -1]) cs_ops = cs_ops[:-1] d_ud, ud_ops = _two_qubit_multiplexor_to_ops(q0, q1, q2, u1, u2, shift_left=True, atol=atol) _, vdh_ops = _two_qubit_multiplexor_to_ops( q0, q1, q2, v1h, v2h, shift_left=False, diagonal=d_ud, atol=atol ) return list(cirq.Circuit(vdh_ops + cs_ops + ud_ops).all_operations())
def test_cossin_error_empty_subblocks(): with pytest.raises(ValueError, match="x11.*empty"): cossin(([], [], [], [])) with pytest.raises(ValueError, match="x12.*empty"): cossin(([1, 2], [], [6, 7], [8, 9, 10])) with pytest.raises(ValueError, match="x21.*empty"): cossin(([1, 2], [3, 4, 5], [], [8, 9, 10])) with pytest.raises(ValueError, match="x22.*empty"): cossin(([1, 2], [3, 4, 5], [2], []))
def test_cossin_error_partitioning(): x = np.array(ortho_group.rvs(4), dtype=np.float) with pytest.raises(ValueError, match="invalid p=0.*0<p<4.*"): cossin(x, 0, 1) with pytest.raises(ValueError, match="invalid p=4.*0<p<4.*"): cossin(x, 4, 1) with pytest.raises(ValueError, match="invalid q=-2.*0<q<4.*"): cossin(x, 1, -2) with pytest.raises(ValueError, match="invalid q=5.*0<q<4.*"): cossin(x, 1, 5)
def test_cossin(dtype_, m, p, q, swap_sign): seed(1234) if dtype_ in COMPLEX_DTYPES: x = np.array(unitary_group.rvs(m), dtype=dtype_) else: x = np.array(ortho_group.rvs(m), dtype=dtype_) u, cs, vh = cossin(x, p, q, swap_sign=swap_sign) assert_allclose(x, u @ cs @ vh, rtol=0., atol=m * 1e3 * np.finfo(dtype_).eps) assert u.dtype == dtype_ # Test for float32 or float 64 assert cs.dtype == np.real(u).dtype assert vh.dtype == dtype_ u, cs, vh = cossin([x[:p, :q], x[:p, q:], x[p:, :q], x[p:, q:]], swap_sign=swap_sign) assert_allclose(x, u @ cs @ vh, rtol=0., atol=m * 1e3 * np.finfo(dtype_).eps) assert u.dtype == dtype_ assert cs.dtype == np.real(u).dtype assert vh.dtype == dtype_ _, cs2, vh2 = cossin(x, p, q, compute_u=False, swap_sign=swap_sign) assert_allclose(cs, cs2, rtol=0., atol=10 * np.finfo(dtype_).eps) assert_allclose(vh, vh2, rtol=0., atol=10 * np.finfo(dtype_).eps) u2, cs2, _ = cossin(x, p, q, compute_vh=False, swap_sign=swap_sign) assert_allclose(u, u2, rtol=0., atol=10 * np.finfo(dtype_).eps) assert_allclose(cs, cs2, rtol=0., atol=10 * np.finfo(dtype_).eps) _, cs2, _ = cossin(x, p, q, compute_u=False, compute_vh=False, swap_sign=swap_sign) assert_allclose(cs, cs2, rtol=0., atol=10 * np.finfo(dtype_).eps)
def test_cossin_mixed_types(): seed(1234) x = np.array(ortho_group.rvs(4), dtype=np.float) u, cs, vh = cossin([ x[:2, :2], np.array(x[:2, 2:], dtype=np.complex128), x[2:, :2], x[2:, 2:] ]) assert u.dtype == np.complex128 assert cs.dtype == np.float64 assert vh.dtype == np.complex128 assert_allclose(x, u @ cs @ vh, rtol=0., atol=1e4 * np.finfo(np.complex128).eps)
def test_cossin_separate(dtype_): m, p, q = 250, 80, 170 pfx = 'or' if dtype_ in REAL_DTYPES else 'un' X = ortho_group.rvs(m) if pfx == 'or' else unitary_group.rvs(m) X = np.array(X, dtype=dtype_) drv, dlw = get_lapack_funcs((pfx + 'csd', pfx + 'csd_lwork'), [X]) lwval = _compute_lwork(dlw, m, p, q) lwvals = { 'lwork': lwval } if pfx == 'or' else dict(zip(['lwork', 'lrwork'], lwval)) *_, theta, u1, u2, v1t, v2t, _ = \ drv(X[:p, :q], X[:p, q:], X[p:, :q], X[p:, q:], **lwvals) (u1_2, u2_2), theta2, (v1t_2, v2t_2) = cossin(X, p, q, separate=True) assert_allclose(u1_2, u1, rtol=0., atol=10 * np.finfo(dtype_).eps) assert_allclose(u2_2, u2, rtol=0., atol=10 * np.finfo(dtype_).eps) assert_allclose(v1t_2, v1t, rtol=0., atol=10 * np.finfo(dtype_).eps) assert_allclose(v2t_2, v2t, rtol=0., atol=10 * np.finfo(dtype_).eps) assert_allclose(theta2, theta, rtol=0., atol=10 * np.finfo(dtype_).eps)
def test_cossin_error_incorrect_subblocks(): with pytest.raises(ValueError, match="be due to missing p, q arguments."): cossin(([1, 2], [3, 4, 5], [6, 7], [8, 9, 10]))
def test_cossin_error_non_square(): with pytest.raises(ValueError, match="only supports square"): cossin(np.array([[1, 2]]), 1, 1)
def test_cossin_error_non_iterable(): with pytest.raises(ValueError, match="containing the subblocks of X"): cossin(12j)
def test_cossin_error_missing_partitioning(): with pytest.raises(ValueError, match=".*exactly four arrays.* got 2"): cossin(unitary_group.rvs(2)) with pytest.raises(ValueError, match=".*might be due to missing p, q"): cossin(unitary_group.rvs(4))
def make_cs_decompose(operator, p=None, q=None): if not p and not q: return cossin(operator) else: return cossin(operator, p=p, q=q)