diff --git a/qiskit/synthesis/two_qubit/two_qubit_decompose.py b/qiskit/synthesis/two_qubit/two_qubit_decompose.py index 41ba75c6b237..a189ef5cf2c2 100644 --- a/qiskit/synthesis/two_qubit/two_qubit_decompose.py +++ b/qiskit/synthesis/two_qubit/two_qubit_decompose.py @@ -691,95 +691,6 @@ def traces(self, target): return self._inner_decomposer.traces(target._inner_decomposition) -class TwoQubitDecomposeUpToDiagonal: - """ - Class to decompose two qubit unitaries into the product of a diagonal gate - and another unitary gate which can be represented by two CX gates instead of the - usual three. This can be used when neighboring gates commute with the diagonal to - potentially reduce overall CX count. - """ - - def __init__(self): - sy = np.array([[0, -1j], [1j, 0]]) - self.sysy = np.kron(sy, sy) - - def _u4_to_su4(self, u4): - phase_factor = np.conj(np.linalg.det(u4) ** (-1 / u4.shape[0])) - su4 = u4 / phase_factor - return su4, cmath.phase(phase_factor) - - def _gamma(self, mat): - """ - proposition II.1: this invariant characterizes when two operators in U(4), - say u, v, are equivalent up to single qubit gates: - - u ≡ v -> Det(γ(u)) = Det(±(γ(v))) - """ - sumat, _ = self._u4_to_su4(mat) - sysy = self.sysy - return sumat @ sysy @ sumat.T @ sysy - - def _cx0_test(self, mat): - # proposition III.1: zero cx sufficient - gamma = self._gamma(mat) - evals = np.linalg.eigvals(gamma) - return np.all(np.isclose(evals, np.ones(4))) - - def _cx1_test(self, mat): - # proposition III.2: one cx sufficient - gamma = self._gamma(mat) - evals = np.linalg.eigvals(gamma) - uvals, ucnts = np.unique(np.round(evals, 10), return_counts=True) - return ( - len(uvals) == 2 - and all(ucnts == 2) - and all((np.isclose(x, 1j)) or np.isclose(x, -1j) for x in uvals) - ) - - def _cx2_test(self, mat): - # proposition III.3: two cx sufficient - gamma = self._gamma(mat) - return np.isclose(np.trace(gamma).imag, 0) - - def _real_trace_transform(self, mat): - """ - Determine diagonal gate such that - - U3 = D U2 - - Where U3 is a general two-qubit gate which takes 3 cnots, D is a - diagonal gate, and U2 is a gate which takes 2 cnots. - """ - a1 = ( - -mat[1, 3] * mat[2, 0] - + mat[1, 2] * mat[2, 1] - + mat[1, 1] * mat[2, 2] - - mat[1, 0] * mat[2, 3] - ) - a2 = ( - mat[0, 3] * mat[3, 0] - - mat[0, 2] * mat[3, 1] - - mat[0, 1] * mat[3, 2] - + mat[0, 0] * mat[3, 3] - ) - theta = 0 # arbitrary - phi = 0 # arbitrary - psi = np.arctan2(a1.imag + a2.imag, a1.real - a2.real) - phi - diag = np.diag(np.exp(-1j * np.array([theta, phi, psi, -(theta + phi + psi)]))) - return diag - - def __call__(self, mat): - """do the decomposition""" - su4, phase = self._u4_to_su4(mat) - real_map = self._real_trace_transform(su4) - mapped_su4 = real_map @ su4 - if not self._cx2_test(mapped_su4): - warnings.warn("Unitary decomposition up to diagonal may use an additionl CX gate.") - circ = two_qubit_cnot_decompose(mapped_su4) - circ.global_phase += phase - return real_map.conj(), circ - - # This weird duplicated lazy structure is for backwards compatibility; Qiskit has historically # always made ``two_qubit_cnot_decompose`` available publicly immediately on import, but it's quite # expensive to construct, and we want to defer the obejct's creation until it's actually used. We diff --git a/test/python/synthesis/test_synthesis.py b/test/python/synthesis/test_synthesis.py index 8d953ff1b83c..6f001a6fbfda 100644 --- a/test/python/synthesis/test_synthesis.py +++ b/test/python/synthesis/test_synthesis.py @@ -62,8 +62,8 @@ TwoQubitControlledUDecomposer, Ud, decompose_two_qubit_product_gate, - TwoQubitDecomposeUpToDiagonal, ) +from qiskit._accelerate.two_qubit_decompose import two_qubit_decompose_up_to_diagonal from qiskit._accelerate.two_qubit_decompose import Specialization from qiskit.synthesis.unitary import qsd from test import combine # pylint: disable=wrong-import-order @@ -1673,149 +1673,23 @@ def test_a2_opt_single_2q(self): class TestTwoQubitDecomposeUpToDiagonal(QiskitTestCase): """test TwoQubitDecomposeUpToDiagonal class""" - def test_prop31(self): - """test proposition III.1: no CNOTs needed""" - dec = TwoQubitDecomposeUpToDiagonal() - # test identity - mat = np.identity(4) - self.assertTrue(dec._cx0_test(mat)) - - sz = np.array([[1, 0], [0, -1]]) - zz = np.kron(sz, sz) - self.assertTrue(dec._cx0_test(zz)) - - had = np.matrix([[1, 1], [1, -1]]) / np.sqrt(2) - hh = np.kron(had, had) - self.assertTrue(dec._cx0_test(hh)) - - sy = np.array([[0, -1j], [1j, 0]]) - hy = np.kron(had, sy) - self.assertTrue(dec._cx0_test(hy)) - - qc = QuantumCircuit(2) - qc.cx(0, 1) - self.assertFalse(dec._cx0_test(Operator(qc).data)) - - def test_prop32_true(self): - """test proposition III.2: 1 CNOT sufficient""" - dec = TwoQubitDecomposeUpToDiagonal() - qc = QuantumCircuit(2) - qc.ry(np.pi / 4, 0) - qc.ry(np.pi / 3, 1) - qc.cx(0, 1) - qc.ry(np.pi / 4, 0) - qc.y(1) - mat = Operator(qc).data - self.assertTrue(dec._cx1_test(mat)) - - qc = QuantumCircuit(2) - qc.ry(np.pi / 5, 0) - qc.ry(np.pi / 3, 1) - qc.cx(1, 0) - qc.ry(np.pi / 2, 0) - qc.y(1) - mat = Operator(qc).data - self.assertTrue(dec._cx1_test(mat)) - - # this SU4 is non-local - mat = scipy.stats.unitary_group.rvs(4, random_state=84) - self.assertFalse(dec._cx1_test(mat)) - - def test_prop32_false(self): - """test proposition III.2: 1 CNOT not sufficient""" - dec = TwoQubitDecomposeUpToDiagonal() - qc = QuantumCircuit(2) - qc.ry(np.pi / 4, 0) - qc.ry(np.pi / 3, 1) - qc.cx(0, 1) - qc.ry(np.pi / 4, 0) - qc.y(1) - qc.cx(0, 1) - qc.ry(np.pi / 3, 0) - qc.rx(np.pi / 2, 1) - mat = Operator(qc).data - self.assertFalse(dec._cx1_test(mat)) - - def test_prop33_true(self): - """test proposition III.3: 2 CNOT sufficient""" - dec = TwoQubitDecomposeUpToDiagonal() - qc = QuantumCircuit(2) - qc.rx(np.pi / 4, 0) - qc.ry(np.pi / 2, 1) - qc.cx(0, 1) - qc.rx(np.pi / 4, 0) - qc.ry(np.pi / 2, 1) - qc.cx(0, 1) - qc.rx(np.pi / 4, 0) - qc.y(1) - mat = Operator(qc).data - self.assertTrue(dec._cx2_test(mat)) - - def test_prop33_false(self): - """test whether circuit which requires 3 cx fails 2 cx test""" - dec = TwoQubitDecomposeUpToDiagonal() - qc = QuantumCircuit(2) - qc.u(0.1, 0.2, 0.3, 0) - qc.u(0.4, 0.5, 0.6, 1) - qc.cx(0, 1) - qc.u(0.1, 0.2, 0.3, 0) - qc.u(0.4, 0.5, 0.6, 1) - qc.cx(0, 1) - qc.u(0.5, 0.2, 0.3, 0) - qc.u(0.2, 0.4, 0.1, 1) - qc.cx(1, 0) - qc.u(0.1, 0.2, 0.3, 0) - qc.u(0.4, 0.5, 0.6, 1) - mat = Operator(qc).data - self.assertFalse(dec._cx2_test(mat)) - - def test_ortho_local_map(self): - """test map of SO4 to SU2⊗SU2""" - dec = TwoQubitDecomposeUpToDiagonal() - emap = np.array([[1, 1j, 0, 0], [0, 0, 1j, 1], [0, 0, 1j, -1], [1, -1j, 0, 0]]) / math.sqrt( - 2 - ) - so4 = scipy.stats.ortho_group.rvs(4, random_state=284) - sy = np.array([[0, -1j], [1j, 0]]) - self.assertTrue(np.allclose(-np.kron(sy, sy), emap @ emap.T)) - self.assertFalse(dec._cx0_test(so4)) - self.assertTrue(dec._cx0_test(emap @ so4 @ emap.T.conj())) - - def test_ortho_local_map2(self): - """test map of SO4 to SU2⊗SU2""" - dec = TwoQubitDecomposeUpToDiagonal() - emap = np.array([[1, 0, 0, 1j], [0, 1j, 1, 0], [0, 1j, -1, 0], [1, 0, 0, -1j]]) / math.sqrt( - 2 - ) - so4 = scipy.stats.ortho_group.rvs(4, random_state=284) - sy = np.array([[0, -1j], [1j, 0]]) - self.assertTrue(np.allclose(-np.kron(sy, sy), emap @ emap.T)) - self.assertFalse(dec._cx0_test(so4)) - self.assertTrue(dec._cx0_test(emap @ so4 @ emap.T.conj())) - - def test_real_trace_transform(self): - """test finding diagonal factor of unitary""" - dec = TwoQubitDecomposeUpToDiagonal() - u4 = scipy.stats.unitary_group.rvs(4, random_state=83) - su4, _ = dec._u4_to_su4(u4) - real_map = dec._real_trace_transform(su4) - self.assertTrue(dec._cx2_test(real_map @ su4)) - def test_call_decompose(self): """ test __call__ method to decompose """ - dec = TwoQubitDecomposeUpToDiagonal() + dec = two_qubit_decompose_up_to_diagonal u4 = scipy.stats.unitary_group.rvs(4, random_state=47) - dmat, circ2cx = dec(u4) + dmat, circ2cx_data = dec(u4) + circ2cx = QuantumCircuit._from_circuit_data(circ2cx_data) dec_diag = dmat @ Operator(circ2cx).data self.assertTrue(Operator(u4) == Operator(dec_diag)) def test_circuit_decompose(self): """test applying decomposed gates as circuit elements""" - dec = TwoQubitDecomposeUpToDiagonal() + dec = two_qubit_decompose_up_to_diagonal u4 = scipy.stats.unitary_group.rvs(4, random_state=47) - dmat, circ2cx = dec(u4) + dmat, circ2cx_data = dec(u4) + circ2cx = QuantumCircuit._from_circuit_data(circ2cx_data) qc1 = QuantumCircuit(2) qc1.append(UnitaryGate(u4), range(2))