Repository: Qiskit/qiskit

Issue:
QuantumInstance.execute mutates circuit list when performing measurement error mitigation
### Environment

- **Qiskit Terra version**: 0.19.1 
- **Python version**: 3.9.7

### What is happening?

When using a `QuantumInstance` with `CompleteMeasFitter`, the circuits passed to `execute` are mutated on [this](https://github.com/Qiskit/qiskit-terra/blob/2db16853bf6d890e0ccf87509c5b88d198c81838/qiskit/utils/quantum_instance.py#L562)  line

```python
circuits[0:0] = cal_circuits
```

This becomes problematic when e.g. using `NeuralNetwork` from `qiskit_machine_learning`, see example below.

### How can we reproduce the issue?

The following code throws an error due to the bug described above:

```python
import numpy as np
from qiskit import Aer, QuantumCircuit
from qiskit.providers.aer import noise
from qiskit.utils import QuantumInstance
from qiskit.utils.mitigation import CompleteMeasFitter
from qiskit.circuit.library import RealAmplitudes, ZZFeatureMap
from qiskit_machine_learning.algorithms import VQC

num_qubits = 3
qi = QuantumInstance(Aer.get_backend('qasm_simulator'),
                     shots=4096,
                     measurement_error_mitigation_cls=CompleteMeasFitter)

feature_map = ZZFeatureMap(num_qubits, reps=2, entanglement='linear')
ansatz = RealAmplitudes(num_qubits, reps=1, entanglement='linear')
model = VQC(num_qubits, feature_map, ansatz, quantum_instance=qi)

x = np.random.rand(1, num_qubits)
weights = np.random.rand(2*num_qubits)
model.neural_network.forward(x, weights)
```

### What should happen?

The circuit list should not be mutated.

### Any suggestions?

A simple fix is to instead use

```python
circuits = cal_circuits + circuits
```


Discussion:

Resolving patch:
diff --git a/qiskit/utils/quantum_instance.py b/qiskit/utils/quantum_instance.py
--- a/qiskit/utils/quantum_instance.py
+++ b/qiskit/utils/quantum_instance.py
@@ -1,6 +1,6 @@
 # This code is part of Qiskit.
 #
-# (C) Copyright IBM 2018, 2021.
+# (C) Copyright IBM 2018, 2022.
 #
 # This code is licensed under the Apache License, Version 2.0. You may
 # obtain a copy of this license in the LICENSE.txt file in the root directory
@@ -491,8 +491,18 @@ def execute(self, circuits, had_transpiled: bool = False):
             build_measurement_error_mitigation_qobj,
         )
 
-        # maybe compile
-        if not had_transpiled:
+        if had_transpiled:
+            # Convert to a list or make a copy.
+            # The measurement mitigation logic expects a list and
+            # may change it in place. This makes sure that logic works
+            # and any future logic that may change the input.
+            # It also makes the code easier: it will always deal with a list.
+            if isinstance(circuits, list):
+                circuits = circuits.copy()
+            else:
+                circuits = [circuits]
+        else:
+            # transpile here, the method always returns a copied list
             circuits = self.transpile(circuits)
 
         from qiskit.providers import BackendV1


Test patch:
diff --git a/test/python/algorithms/test_measure_error_mitigation.py b/test/python/algorithms/test_measure_error_mitigation.py
--- a/test/python/algorithms/test_measure_error_mitigation.py
+++ b/test/python/algorithms/test_measure_error_mitigation.py
@@ -1,6 +1,6 @@
 # This code is part of Qiskit.
 #
-# (C) Copyright IBM 2019, 2021.
+# (C) Copyright IBM 2019, 2022.
 #
 # This code is licensed under the Apache License, Version 2.0. You may
 # obtain a copy of this license in the LICENSE.txt file in the root directory
@@ -332,7 +332,7 @@ def test_measurement_error_mitigation_with_vqe_ignis(self, config):
 
     @unittest.skipUnless(HAS_AER, "qiskit-aer is required for this test")
     @unittest.skipUnless(HAS_IGNIS, "qiskit-ignis is required to run this test")
-    def test_callibration_results(self):
+    def test_calibration_results(self):
         """check that results counts are the same with/without error mitigation"""
         algorithm_globals.random_seed = 1679
         np.random.seed(algorithm_globals.random_seed)
@@ -367,6 +367,32 @@ def test_callibration_results(self):
             counts_array[0], counts_array[1], msg="Counts different with/without fitter."
         )
 
+    @unittest.skipUnless(HAS_AER, "qiskit-aer is required for this test")
+    def test_circuit_modified(self):
+        """tests that circuits don't get modified on QI execute with error mitigation
+        as per issue #7449
+        """
+        algorithm_globals.random_seed = 1679
+        np.random.seed(algorithm_globals.random_seed)
+        circuit = QuantumCircuit(1)
+        circuit.x(0)
+        circuit.measure_all()
+
+        qi = QuantumInstance(
+            Aer.get_backend("aer_simulator"),
+            seed_simulator=algorithm_globals.random_seed,
+            seed_transpiler=algorithm_globals.random_seed,
+            shots=1024,
+            measurement_error_mitigation_cls=CompleteMeasFitter,
+        )
+        # The error happens on transpiled circuits since "execute" was changing the input array
+        # Non transpiled circuits didn't have a problem because a new transpiled array was created
+        # internally.
+        circuits_ref = qi.transpile(circuit)  # always returns a new array
+        circuits_input = circuits_ref.copy()
+        _ = qi.execute(circuits_input, had_transpiled=True)
+        self.assertEqual(circuits_ref, circuits_input, msg="Transpiled circuit array modified.")
+
 
 if __name__ == "__main__":
     unittest.main()