Repository: qutip/qutip

Issue:
Checking the behavior of the parameter `index` in the function `QubitCircuit.add_gate()`
**Is your feature request related to a problem? Please describe.**
I would like to check the behavior of the parameter `index` in the function `QubitCircuit.add_gate()`

In `qutip.qip.QubitCircuit.add_gate()`, there is a optional `list` type attribute called index.
This seems to be used to insert gates in the middle of the gate list (`qutip.qip.QubitCircuit.gates`).
The corresponding code is shown below.
```
            for position in index:
                num_mes = (sum(isinstance(op, Measurement) for op
                               in self.gates[:position]))
                gate = Gate(name, targets=targets, controls=controls,
                            arg_value=arg_value, arg_label=arg_label,
                            classical_controls=classical_controls,
                            control_value=control_value)
                self.gates.insert(position, gate)
```
In this code, if `index` contains two or more elements, then the insert positions would be shifted one by one.
Is this an expected behavior?

In the current code, the positions in `index` would not indicate the original positions during the insertion loop. (e.g. When we want to insert a gate `A` to `gates=[B0,B1,B2,B3,B4,B5]` in `index=[0,2,3]`, then the output gate list would be `gates=[A,B0,A,A,B1,B2,B3,B4,B5]`, not `gates=[A,B0,B1,A,B2,A,B3,B4,B5]`.)

In addition, the variable `num_mes` is unused throughout the code.

**Describe the solution you'd like**
If this is not the expected behavior, and if the expected behavior is like the latter gate list in the former example, then we can fix this part into:

```
            gate = Gate(name, targets=targets, controls=controls,
                            arg_value=arg_value, arg_label=arg_label,
                            classical_controls=classical_controls,
                            control_value=control_value)
            for i, position in enumerate(sorted(index)):
                self.gates.insert(position + i, gate)
```

**Additional context**
This issue is also mentioned in the PR #1489 .



Discussion:
Thanks for the issue @BOBO1997

I agree that it seems a bit odd. One can make an empty list, append old gates and insert new gates one by one to the new list.


Yes, that is one of the solutions.
This issue could be included in the refactoring task of the quantum circuit library.

Resolving patch:
diff --git a/qutip/qip/circuit.py b/qutip/qip/circuit.py
--- a/qutip/qip/circuit.py
+++ b/qutip/qip/circuit.py
@@ -493,7 +493,8 @@ def add_gate(self, gate, targets=None, controls=None, arg_value=None,
         arg_label: string
             Label for gate representation.
         index : list
-            Positions to add the gate.
+            Positions to add the gate. Each index in the supplied list refers
+            to a position in the original list of gates.
         classical_controls : int or list of int, optional
             indices of classical bits to control gate on.
         control_value : int, optional
@@ -524,9 +525,10 @@ def add_gate(self, gate, targets=None, controls=None, arg_value=None,
             self.gates.append(gate)
 
         else:
-            for position in index:
-                num_mes = (sum(isinstance(op, Measurement) for op
-                               in self.gates[:position]))
+            # NOTE: Every insertion shifts the indices in the original list of
+            #       gates by an additional position to the right.
+            shifted_inds = np.sort(index) + np.arange(len(index))
+            for position in shifted_inds:
                 gate = Gate(name, targets=targets, controls=controls,
                             arg_value=arg_value, arg_label=arg_label,
                             classical_controls=classical_controls,


Test patch:
diff --git a/qutip/tests/test_qubitcircuit.py b/qutip/tests/test_qubitcircuit.py
--- a/qutip/tests/test_qubitcircuit.py
+++ b/qutip/tests/test_qubitcircuit.py
@@ -224,6 +224,52 @@ def test_add_gate(self):
             # Single control
             pytest.raises(ValueError, qc.add_gate, gate, [0], [1])
 
+        dummy_gate1 = Gate("DUMMY1")
+        inds = [1, 3, 4, 6]
+        qc.add_gate(dummy_gate1, index=inds)
+
+        # Test adding gates at multiple (sorted) indices at once.
+        # NOTE: Every insertion shifts the indices in the original list of
+        #       gates by an additional position to the right.
+        expected_gate_names = [
+            'CNOT',     # 0
+            'DUMMY1',   # 1
+            'SWAP',     # 2
+            'TOFFOLI',  # 3
+            'DUMMY1',   # 4
+            'SWAP',     # 5
+            'DUMMY1',   # 6
+            'SNOT',     # 7
+            'RY',       # 8
+            'DUMMY1',   # 9
+            'RY',       # 10
+        ]
+        actual_gate_names = [gate.name for gate in qc.gates]
+        assert actual_gate_names == expected_gate_names
+
+        dummy_gate2 = Gate("DUMMY2")
+        inds = [11, 0]
+        qc.add_gate(dummy_gate2, index=inds)
+
+        # Test adding gates at multiple (unsorted) indices at once.
+        expected_gate_names = [
+            'DUMMY2',   # 0
+            'CNOT',     # 1
+            'DUMMY1',   # 2
+            'SWAP',     # 3
+            'TOFFOLI',  # 4
+            'DUMMY1',   # 5
+            'SWAP',     # 6
+            'DUMMY1',   # 7
+            'SNOT',     # 8
+            'RY',       # 9
+            'DUMMY1',   # 10
+            'RY',       # 11
+            'DUMMY2',   # 12
+        ]
+        actual_gate_names = [gate.name for gate in qc.gates]
+        assert actual_gate_names == expected_gate_names
+
     def test_add_circuit(self):
         """
         Addition of a circuit to a `QubitCircuit`
@@ -292,7 +338,7 @@ def customer_gate1(arg_values):
                     qc.gates[i].controls is not None):
                 assert (qc2.gates[i].controls[0]
                         == qc.gates[i].controls[0]+2)
-        
+
         # Test exception when the operators to be added are not gates or measurements
         qc.gates[-1] = 0
         pytest.raises(TypeError, qc2.add_circuit, qc)
@@ -519,7 +565,6 @@ def test_run_teleportation(self):
 
         teleportation_sim_results = teleportation_sim.run(state)
         state_final = teleportation_sim_results.get_final_states(0)
-        probability = teleportation_sim_results.get_probabilities(0)
 
         final_measurement = Measurement("start", targets=[2])
         _, final_probabilities = final_measurement.measurement_comp_basis(state_final)
@@ -608,9 +653,6 @@ def test_wstate(self):
         qc = read_qasm(filepath)
 
         rand_state = rand_ket(2)
-        wstate = (tensor(basis(2, 0), basis(2, 0), basis(2, 1))
-                  + tensor(basis(2, 0), basis(2, 1), basis(2, 0))
-                  + tensor(basis(2, 1), basis(2, 0), basis(2, 0))).unit()
 
         state = tensor(tensor(basis(2, 0), basis(2, 0), basis(2, 0)),
                        rand_state)
@@ -644,7 +686,3 @@ def test_latex_code_non_reversed(self):
         exp = ' &  &  \\meter & \\qw \\\\ \n &  ' + \
               '&  \\qw \\cwx[-1]  & \\qw \\\\ \n'
         assert qc.latex_code() == exp
-
-
-if __name__ == "__main__":
-    run_module_suite()