Skip to content

Commit

Permalink
Remove unused private Python class
Browse files Browse the repository at this point in the history
This commit removes the Python implementation of the function. This is
now unused in Qiskit and was never a public class so nothing external
should be depending on it. Since it's not used we should just remove it.
  • Loading branch information
mtreinish committed Jun 14, 2024
1 parent d8d1d89 commit c56f8af
Show file tree
Hide file tree
Showing 2 changed files with 7 additions and 222 deletions.
89 changes: 0 additions & 89 deletions qiskit/synthesis/two_qubit/two_qubit_decompose.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
140 changes: 7 additions & 133 deletions test/python/synthesis/test_synthesis.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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))
Expand Down

0 comments on commit c56f8af

Please sign in to comment.