Repository: QuantestPy/quantestpy (language: python)

Issue:
Implement a C^{n}X^{m} gate



Discussion / hints:

Fix patch:
diff --git a/.github/workflows/github-actions.yml b/.github/workflows/github-actions.yml
index d54ed53..a78d00b 100644
--- a/.github/workflows/github-actions.yml
+++ b/.github/workflows/github-actions.yml
@@ -32,6 +32,6 @@ jobs:
           python3 -m unittest test.test_test_circuit
           python3 -m unittest test.test_circuit_assert_equal_to_operator
           python3 -m unittest test.test_circuit_assert_is_zero
+          python3 -m unittest test.test_test_circuit_cx_gate
           python3 -m unittest test.test_circuit_assert_ancilla_is_zero
           python3 -m unittest test.test_converter
-


Test patch:
diff --git a/quantestpy/test_circuit.py b/quantestpy/test_circuit.py
index dffeac1..f9fd9e6 100644
--- a/quantestpy/test_circuit.py
+++ b/quantestpy/test_circuit.py
@@ -9,7 +9,7 @@
 _S = np.array([[1, 0], [0, 1j]])
 _T = np.array([[1, 0], [0, np.exp(1j*np.pi/4)]])
 _IMPLEMENTED_SINGLE_QUBIT_GATES = ["x", "h", "s", "t"]
-_IMPLEMENTED_GATES = _IMPLEMENTED_SINGLE_QUBIT_GATES + ["cx", "cnot"]
+_IMPLEMENTED_GATES = _IMPLEMENTED_SINGLE_QUBIT_GATES + ["cx"]
 
 
 class TestCircuit:
@@ -82,11 +82,16 @@ def add_gate(self, gate: dict) -> None:
                 'gate["control_value"] must be a list'
             )
 
-        if gate["name"] in _IMPLEMENTED_GATES and \
+        if len(gate["control_qubit"]) != len(gate["control_value"]):
+            raise QuantestPyTestCircuitError(
+                "control_qubit and control_value must have the same lenght."
+            )
+
+        if gate["name"] in _IMPLEMENTED_SINGLE_QUBIT_GATES and \
                 len(gate["target_qubit"]) != 1:
             raise QuantestPyTestCircuitError(
-                "a gate must have a list containing exactly 1 element "
-                "for 'target_qubit'."
+                "single qubit gate must have a list containing exactly 1 "
+                "element for 'target_qubit'."
             )
 
         if gate["name"] in _IMPLEMENTED_SINGLE_QUBIT_GATES and \
@@ -103,16 +108,15 @@ def add_gate(self, gate: dict) -> None:
                 "'control_value'."
             )
 
-        if gate["name"] in ["cx", "cnot"] and len(gate["control_qubit"]) != 1:
+        if gate["name"] == "cx" and len(gate["control_qubit"]) < 1:
             raise QuantestPyTestCircuitError(
-                "cx and cnot gate must have a list containing exactly 1 "
-                "element for 'control_qubit'."
+                "cx gate must not have an empty list for "
+                "'control_qubit' and 'control_value'."
             )
 
-        if gate["name"] in ["cx", "cnot"] and len(gate["control_value"]) != 1:
+        if gate["name"] == "cx" and len(gate["target_qubit"]) < 1:
             raise QuantestPyTestCircuitError(
-                "cx and cnot gate must have a list containing exactly 1 "
-                "element for 'control_value'."
+                "cx gate must not have an empty list for 'target_qubit'."
             )
 
         self._gates.append(gate)
@@ -173,8 +177,11 @@ def _create_all_qubit_gate_from_single_qubit_gate(
 
         return all_qubit_gate
 
-    def _create_all_qubit_gate_from_cnot_gate(
-            self, control: int, target: int, control_value: int) -> np.ndarray:
+    def _create_all_qubit_gate_from_cx_gate(
+            self,
+            control_qubit: list,
+            target_qubit: list,
+            control_value: list) -> np.ndarray:
 
         if self._binary_to_vector is None:
             self._binary_to_vector = {}
@@ -191,23 +198,26 @@ def _create_all_qubit_gate_from_cnot_gate(
         all_qubit_gate = np.zeros(
             (2**self._num_qubit, 2**self._num_qubit))
 
-        for binary_before_cnot in self._binary_to_vector.keys():
+        for binary_before_cx in self._binary_to_vector.keys():
 
-            binary_tmp = list(binary_before_cnot)
-            if binary_tmp[control] == str(control_value):
-                if binary_tmp[target] == "1":
-                    binary_tmp[target] = "0"
-                elif binary_tmp[target] == "0":
-                    binary_tmp[target] = "1"
-                else:
-                    raise
+            binary_tmp = np.array([int(i) for i in list(binary_before_cx)])
+
+            if np.all(binary_tmp[control_qubit] == control_value):
+
+                for target in target_qubit:
+                    if binary_tmp[target] == 1:
+                        binary_tmp[target] = 0
+                    elif binary_tmp[target] == 0:
+                        binary_tmp[target] = 1
+                    else:
+                        raise
 
-            binary_after_cnot = "".join(binary_tmp)
+            binary_after_cx = "".join([str(i) for i in binary_tmp])
 
-            vector_after_cnot = self._binary_to_vector[binary_after_cnot]
-            vector_before_cnot = self._binary_to_vector[binary_before_cnot]
+            vector_after_cx = self._binary_to_vector[binary_after_cx]
+            vector_before_cx = self._binary_to_vector[binary_before_cx]
             all_qubit_gate += \
-                vector_after_cnot.reshape(-1, 1) * vector_before_cnot
+                vector_after_cx.reshape(-1, 1) * vector_before_cx
 
         return all_qubit_gate
 
@@ -267,12 +277,12 @@ def _get_whole_gates(self,) -> np.ndarray:
                     self._create_all_qubit_gate_from_single_qubit_gate(
                         _T, gate["target_qubit"][0])
 
-            elif gate["name"] == "cx" or gate["name"] == "cnot":
+            elif gate["name"] == "cx":
                 all_qubit_gate = \
-                    self._create_all_qubit_gate_from_cnot_gate(
-                        gate["control_qubit"][0],
-                        gate["target_qubit"][0],
-                        gate["control_value"][0])
+                    self._create_all_qubit_gate_from_cx_gate(
+                        gate["control_qubit"],
+                        gate["target_qubit"],
+                        gate["control_value"])
 
             else:
                 raise
diff --git a/test/test_test_circuit.py b/test/test_test_circuit.py
index 58a167f..12f1e10 100644
--- a/test/test_test_circuit.py
+++ b/test/test_test_circuit.py
@@ -89,7 +89,7 @@ def test__get_state_vector_2(self,):
         circ.add_gate({"name": "h", "target_qubit": [0], "control_qubit": [],
                        "control_value": []})
         circ.add_gate(
-            {"name": "cnot", "control_qubit": [0], "target_qubit": [1],
+            {"name": "cx", "control_qubit": [0], "target_qubit": [1],
              "control_value": [1]})
         actual_vec = circ._get_state_vector()
 
@@ -105,7 +105,7 @@ def test__get_state_vector_3(self,):
         circ.add_gate({"name": "x", "target_qubit": [1], "control_qubit": [],
                        "control_value": []})
         circ.add_gate(
-            {"name": "cnot", "control_qubit": [0], "target_qubit": [1],
+            {"name": "cx", "control_qubit": [0], "target_qubit": [1],
              "control_value": [1]})
         actual_vec = circ._get_state_vector()
 
@@ -130,83 +130,6 @@ def test__get_state_vector_4(self,):
         self.assertIsNone(
             np.testing.assert_allclose(actual_vec, expected_vec))
 
-    def test__create_all_qubit_gate_from_cnot_gate_1(self,):
-        circ = TestCircuit(2)
-        actual_gate = circ._create_all_qubit_gate_from_cnot_gate(
-            control=0, target=1, control_value=1
-        )
-
-        expected_gate = np.array([[1, 0, 0, 0],
-                                  [0, 1, 0, 0],
-                                  [0, 0, 0, 1],
-                                  [0, 0, 1, 0]])
-
-        self.assertIsNone(
-            np.testing.assert_allclose(actual_gate, expected_gate))
-
-    def test__create_all_qubit_gate_from_cnot_gate_2(self,):
-        circ = TestCircuit(2)
-        circ._from_right_to_left_for_qubit_ids = True
-        actual_gate = circ._create_all_qubit_gate_from_cnot_gate(
-            control=0, target=1, control_value=1
-        )
-
-        expected_gate = np.array([[1, 0, 0, 0],
-                                  [0, 0, 0, 1],
-                                  [0, 0, 1, 0],
-                                  [0, 1, 0, 0]])
-
-        self.assertIsNone(
-            np.testing.assert_allclose(actual_gate, expected_gate))
-
-    def test__create_all_qubit_gate_from_cnot_gate_3(self,):
-        circ = TestCircuit(2)
-        actual_gate = circ._create_all_qubit_gate_from_cnot_gate(
-            control=1, target=0, control_value=1
-        )
-
-        expected_gate = np.array([[1, 0, 0, 0],
-                                  [0, 0, 0, 1],
-                                  [0, 0, 1, 0],
-                                  [0, 1, 0, 0]])
-
-        self.assertIsNone(
-            np.testing.assert_allclose(actual_gate, expected_gate))
-
-    def test__create_all_qubit_gate_from_cnot_gate_4(self,):
-        circ = TestCircuit(3)
-        circ._from_right_to_left_for_qubit_ids = True
-        actual_gate = circ._create_all_qubit_gate_from_cnot_gate(
-            control=0, target=2, control_value=1
-        )
-
-        expected_gate = np.array([
-            [1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
-            [0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j],
-            [0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
-            [0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j],
-            [0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
-            [0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
-            [0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j],
-            [0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j]])
-
-        self.assertIsNone(
-            np.testing.assert_allclose(actual_gate, expected_gate))
-
-    def test__create_all_qubit_gate_from_cnot_gate_5(self,):
-        circ = TestCircuit(2)
-        actual_gate = circ._create_all_qubit_gate_from_cnot_gate(
-            control=0, target=1, control_value=0
-        )
-
-        expected_gate = np.array([[0, 1, 0, 0],
-                                  [1, 0, 0, 0],
-                                  [0, 0, 1, 0],
-                                  [0, 0, 0, 1]])
-
-        self.assertIsNone(
-            np.testing.assert_allclose(actual_gate, expected_gate))
-
     def test_set_initial_state_vector_1(self,):
         circ = TestCircuit(2)
         init_vec = np.array([1, 1j, -1, -1]) / 2.
@@ -223,7 +146,7 @@ def test_set_initial_state_vector_2(self,):
         init_vec = np.array([1, 0, 1, 0]) / np.sqrt(2.)
         circ.set_initial_state_vector(init_vec)
         circ.add_gate(
-            {"name": "cnot", "control_qubit": [0], "target_qubit": [1],
+            {"name": "cx", "control_qubit": [0], "target_qubit": [1],
              "control_value": [1]})
         actual_vec = circ._get_state_vector()
 
diff --git a/test/test_test_circuit_cx_gate.py b/test/test_test_circuit_cx_gate.py
new file mode 100644
index 0000000..cbe70d7
--- /dev/null
+++ b/test/test_test_circuit_cx_gate.py
@@ -0,0 +1,154 @@
+import unittest
+import numpy as np
+
+from quantestpy import TestCircuit
+
+
+class TestTestCircuitCXGate(unittest.TestCase):
+    """
+    How to execute this test:
+    $ pwd
+    {Your directory where you git-cloned quantestpy}/quantestpy
+    $ python -m unittest test.test_test_circuit_cx_gate
+    ........
+    ----------------------------------------------------------------------
+    Ran 8 tests in 0.009s
+
+    OK
+    $
+    """
+
+    def test_cx_regular_qubit_order(self,):
+        circ = TestCircuit(2)
+        actual_gate = circ._create_all_qubit_gate_from_cx_gate(
+            control_qubit=[0], target_qubit=[1], control_value=[1]
+        )
+
+        expected_gate = np.array([[1, 0, 0, 0],
+                                  [0, 1, 0, 0],
+                                  [0, 0, 0, 1],
+                                  [0, 0, 1, 0]])
+
+        self.assertIsNone(
+            np.testing.assert_allclose(actual_gate, expected_gate))
+
+    def test_cx_qiskit_qubit_order(self,):
+        circ = TestCircuit(2)
+        circ._from_right_to_left_for_qubit_ids = True
+        actual_gate = circ._create_all_qubit_gate_from_cx_gate(
+            control_qubit=[0], target_qubit=[1], control_value=[1]
+        )
+
+        expected_gate = np.array([[1, 0, 0, 0],
+                                  [0, 0, 0, 1],
+                                  [0, 0, 1, 0],
+                                  [0, 1, 0, 0]])
+
+        self.assertIsNone(
+            np.testing.assert_allclose(actual_gate, expected_gate))
+
+    def test_cx_flip_control_target(self,):
+        circ = TestCircuit(2)
+        actual_gate = circ._create_all_qubit_gate_from_cx_gate(
+            control_qubit=[1], target_qubit=[0], control_value=[1]
+        )
+
+        expected_gate = np.array([[1, 0, 0, 0],
+                                  [0, 0, 0, 1],
+                                  [0, 0, 1, 0],
+                                  [0, 1, 0, 0]])
+
+        self.assertIsNone(
+            np.testing.assert_allclose(actual_gate, expected_gate))
+
+    def test_cx_three_qubits_qiskit_qubit_order(self,):
+        circ = TestCircuit(3)
+        circ._from_right_to_left_for_qubit_ids = True
+        actual_gate = circ._create_all_qubit_gate_from_cx_gate(
+            control_qubit=[0], target_qubit=[2], control_value=[1]
+        )
+
+        # this is qiskit's output
+        expected_gate = np.array([
+            [1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
+            [0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j],
+            [0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
+            [0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j],
+            [0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
+            [0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
+            [0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j],
+            [0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j]])
+
+        self.assertIsNone(
+            np.testing.assert_allclose(actual_gate, expected_gate))
+
+    def test_cx_control_value_is_zero(self,):
+        circ = TestCircuit(2)
+        actual_gate = circ._create_all_qubit_gate_from_cx_gate(
+            control_qubit=[0], target_qubit=[1], control_value=[0]
+        )
+
+        expected_gate = np.array([[0, 1, 0, 0],
+                                  [1, 0, 0, 0],
+                                  [0, 0, 1, 0],
+                                  [0, 0, 0, 1]])
+
+        self.assertIsNone(
+            np.testing.assert_allclose(actual_gate, expected_gate))
+
+    def test_toffoli(self,):
+        circ = TestCircuit(3)
+        actual_gate = circ._create_all_qubit_gate_from_cx_gate(
+            control_qubit=[0, 1], target_qubit=[2], control_value=[1, 1]
+        )
+
+        expected_gate = np.array([[1, 0, 0, 0, 0, 0, 0, 0],
+                                  [0, 1, 0, 0, 0, 0, 0, 0],
+                                  [0, 0, 1, 0, 0, 0, 0, 0],
+                                  [0, 0, 0, 1, 0, 0, 0, 0],
+                                  [0, 0, 0, 0, 1, 0, 0, 0],
+                                  [0, 0, 0, 0, 0, 1, 0, 0],
+                                  [0, 0, 0, 0, 0, 0, 0, 1],
+                                  [0, 0, 0, 0, 0, 0, 1, 0]])
+
+        self.assertIsNone(
+            np.testing.assert_allclose(actual_gate, expected_gate))
+
+    def test_toffoli_qiskit_qubit_order(self,):
+        circ = TestCircuit(3)
+        circ._from_right_to_left_for_qubit_ids = True
+        actual_gate = circ._create_all_qubit_gate_from_cx_gate(
+            control_qubit=[0, 1], target_qubit=[2], control_value=[1, 1]
+        )
+
+        # this is qiskit's output
+        expected_gate = np.array([
+            [1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
+            [0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
+            [0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
+            [0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j],
+            [0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
+            [0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j],
+            [0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j],
+            [0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j]])
+
+        self.assertIsNone(
+            np.testing.assert_allclose(actual_gate, expected_gate))
+
+    def test_cx_multiple_targets(self,):
+
+        circ = TestCircuit(3)
+        gate_0 = circ._create_all_qubit_gate_from_cx_gate(
+            control_qubit=[0], target_qubit=[1, 2], control_value=[1]
+        )
+
+        circ = TestCircuit(3)
+        gate_1_0 = circ._create_all_qubit_gate_from_cx_gate(
+            control_qubit=[0], target_qubit=[1], control_value=[1]
+        )
+        gate_1_1 = circ._create_all_qubit_gate_from_cx_gate(
+            control_qubit=[0], target_qubit=[2], control_value=[1]
+        )
+
+        self.assertIsNone(
+            np.testing.assert_allclose(gate_0, np.matmul(gate_1_0, gate_1_1)))