Repository: Qiskit/qiskit

Issue:
Unreferenced variable CompleteMeasFitter
<!-- ⚠️ If you do not respect this template, your issue will be closed -->
<!-- ⚠️ Make sure to browse the opened and closed issues -->

### Information

- **Qiskit Nature version**: 0.1.4
- **Python version**: 3.8
- **Operating system**: Windows 10

### What is the current behavior?

Running one of the algoritms results in an error `UnboundLocalError: local variable 'CompleteMeasFitter' referenced before assignment` due to a missing import.

### Steps to reproduce the problem

A minimal example is:
```
from qiskit.utils import QuantumInstance
from qiskit.ignis.mitigation.measurement import CompleteMeasFitter
from qiskit.test.mock import FakeVigo
from qiskit import Aer

from qiskit_nature.drivers import UnitsType, Molecule
from qiskit_nature.drivers.second_quantization import PySCFDriver
from qiskit_nature.problems.second_quantization import ElectronicStructureProblem
from qiskit_nature.converters.second_quantization import QubitConverter
from qiskit_nature.mappers.second_quantization import JordanWignerMapper
from qiskit_nature.algorithms import VQEUCCFactory
from qiskit_nature.algorithms import GroundStateEigensolver

molecule = Molecule(geometry=[['H', [0., 0., 0.]], ['H', [0., 0., 0.735]]],
                     charge=0, multiplicity=1)
driver = PySCFDriver(molecule = molecule, unit=UnitsType.ANGSTROM, basis='sto3g')

es_problem = ElectronicStructureProblem(driver)
qubit_converter = QubitConverter(JordanWignerMapper())

backend = Aer.get_backend("qasm_simulator")
device = FakeVigo()
coupling_map = device.configuration().coupling_map
quantum_instance = QuantumInstance(backend=backend, 
                                   shots=400, 
                                   noise_model=None, 
                                   coupling_map=coupling_map,
                                   measurement_error_mitigation_cls=CompleteMeasFitter,
                                   cals_matrix_refresh_period=30)

vqe_solver = VQEUCCFactory(quantum_instance)
calc = GroundStateEigensolver(qubit_converter, vqe_solver)
res = calc.solve(es_problem) # fails with the combination coupling map and CompleteMeasFitter
```


### What is the expected behavior?

The algorithm should have executed without an error.

### Suggested solutions

Add an import of the `CompleteMeasFitter`




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
@@ -13,6 +13,7 @@
 """ Quantum Instance module """
 
 from typing import Optional, List, Union, Dict, Callable, Tuple
+from enum import Enum
 import copy
 import logging
 import time
@@ -34,6 +35,59 @@
 logger = logging.getLogger(__name__)
 
 
+class _MeasFitterType(Enum):
+    """Meas Fitter Type."""
+
+    COMPLETE_MEAS_FITTER = 0
+    TENSORED_MEAS_FITTER = 1
+
+    @staticmethod
+    def type_from_class(meas_class):
+        """
+        Returns fitter type from class
+        """
+        try:
+            from qiskit.ignis.mitigation.measurement import (
+                CompleteMeasFitter,
+                TensoredMeasFitter,
+            )
+        except ImportError as ex:
+            raise MissingOptionalLibraryError(
+                libname="qiskit-ignis",
+                name="QuantumInstance",
+                pip_install="pip install qiskit-ignis",
+            ) from ex
+        if meas_class == CompleteMeasFitter:
+            return _MeasFitterType.COMPLETE_MEAS_FITTER
+        elif meas_class == TensoredMeasFitter:
+            return _MeasFitterType.TENSORED_MEAS_FITTER
+        else:
+            raise QiskitError(f"Unknown fitter {meas_class}")
+
+    @staticmethod
+    def type_from_instance(meas_instance):
+        """
+        Returns fitter type from instance
+        """
+        try:
+            from qiskit.ignis.mitigation.measurement import (
+                CompleteMeasFitter,
+                TensoredMeasFitter,
+            )
+        except ImportError as ex:
+            raise MissingOptionalLibraryError(
+                libname="qiskit-ignis",
+                name="QuantumInstance",
+                pip_install="pip install qiskit-ignis",
+            ) from ex
+        if isinstance(meas_instance, CompleteMeasFitter):
+            return _MeasFitterType.COMPLETE_MEAS_FITTER
+        elif isinstance(meas_instance, TensoredMeasFitter):
+            return _MeasFitterType.TENSORED_MEAS_FITTER
+        else:
+            raise QiskitError(f"Unknown fitter {meas_instance}")
+
+
 class QuantumInstance:
     """Quantum Backend including execution setting."""
 
@@ -534,28 +588,15 @@ def _find_save_state(data):
                         cals_result = result
 
                 logger.info("Building calibration matrix for measurement error mitigation.")
-                try:
-                    from qiskit.ignis.mitigation.measurement import (
-                        CompleteMeasFitter,
-                        TensoredMeasFitter,
-                    )
-                except ImportError as ex:
-                    raise MissingOptionalLibraryError(
-                        libname="qiskit-ignis",
-                        name="execute",
-                        pip_install="pip install qiskit-ignis",
-                    ) from ex
-                if self._meas_error_mitigation_cls == CompleteMeasFitter:
+                meas_type = _MeasFitterType.type_from_class(self._meas_error_mitigation_cls)
+                if meas_type == _MeasFitterType.COMPLETE_MEAS_FITTER:
                     meas_error_mitigation_fitter = self._meas_error_mitigation_cls(
                         cals_result, state_labels, qubit_list=qubit_index, circlabel=circuit_labels
                     )
-                elif self._meas_error_mitigation_cls == TensoredMeasFitter:
+                elif meas_type == _MeasFitterType.TENSORED_MEAS_FITTER:
                     meas_error_mitigation_fitter = self._meas_error_mitigation_cls(
                         cals_result, mit_pattern=state_labels, circlabel=circuit_labels
                     )
-                else:
-                    raise QiskitError(f"Unknown fitter {self._meas_error_mitigation_cls}")
-
                 self._meas_error_mitigation_fitters[qubit_index_str] = (
                     meas_error_mitigation_fitter,
                     time.time(),
@@ -607,7 +648,9 @@ def _find_save_state(data):
                     tmp_result.results = [result.results[i] for i in c_idx]
                     if curr_qubit_index == qubit_index:
                         tmp_fitter = meas_error_mitigation_fitter
-                    elif isinstance(meas_error_mitigation_fitter, CompleteMeasFitter):
+                    elif _MeasFitterType.COMPLETE_MEAS_FITTER == _MeasFitterType.type_from_instance(
+                        meas_error_mitigation_fitter
+                    ):
                         tmp_fitter = meas_error_mitigation_fitter.subset_fitter(curr_qubit_index)
                     else:
                         raise QiskitError(
diff --git a/qiskit/utils/run_circuits.py b/qiskit/utils/run_circuits.py
--- a/qiskit/utils/run_circuits.py
+++ b/qiskit/utils/run_circuits.py
@@ -106,8 +106,8 @@ def _maybe_split_qobj_by_gates(qobjs: List[QasmQobj], qobj: QasmQobj) -> List[Qa
     if MAX_GATES_PER_JOB is not None:
         max_gates_per_job = int(MAX_GATES_PER_JOB)
         total_num_gates = 0
-        for j in range(len(qobj.experiments)):
-            total_num_gates += len(qobj.experiments[j].instructions)
+        for experiment in qobj.experiments:
+            total_num_gates += len(experiment.instructions)
         # split by gates if total number of gates in a qobj exceed MAX_GATES_PER_JOB
         if total_num_gates > max_gates_per_job:
             qobj_template = QasmQobj(
@@ -117,17 +117,17 @@ def _maybe_split_qobj_by_gates(qobjs: List[QasmQobj], qobj: QasmQobj) -> List[Qa
             temp_qobj.qobj_id = str(uuid.uuid4())
             temp_qobj.experiments = []
             num_gates = 0
-            for i in range(len(qobj.experiments)):
-                num_gates += len(qobj.experiments[i].instructions)
+            for experiment in qobj.experiments:
+                num_gates += len(experiment.instructions)
                 if num_gates <= max_gates_per_job:
-                    temp_qobj.experiments.append(qobj.experiments[i])
+                    temp_qobj.experiments.append(experiment)
                 else:
                     qobjs.append(temp_qobj)
                     # Initialize for next temp_qobj
                     temp_qobj = copy.deepcopy(qobj_template)
                     temp_qobj.qobj_id = str(uuid.uuid4())
-                    temp_qobj.experiments.append(qobj.experiments[i])
-                    num_gates = len(qobj.experiments[i].instructions)
+                    temp_qobj.experiments.append(experiment)
+                    num_gates = len(experiment.instructions)
 
             qobjs.append(temp_qobj)
         else:


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
@@ -16,12 +16,15 @@
 
 from test.python.algorithms import QiskitAlgorithmsTestCase
 from ddt import ddt, data
+import numpy as np
+import retworkx as rx
 from qiskit import QuantumCircuit
+from qiskit.quantum_info import Pauli
 from qiskit.exceptions import QiskitError
 from qiskit.utils import QuantumInstance, algorithm_globals
-from qiskit.algorithms import VQE
-from qiskit.opflow import I, X, Z
-from qiskit.algorithms.optimizers import SPSA
+from qiskit.algorithms import VQE, QAOA
+from qiskit.opflow import I, X, Z, PauliSumOp
+from qiskit.algorithms.optimizers import SPSA, COBYLA
 from qiskit.circuit.library import EfficientSU2
 
 try:
@@ -148,6 +151,67 @@ def test_measurement_error_mitigation_with_vqe(self, config):
         quantum_instance.reset_execution_results()
         self.assertAlmostEqual(result.eigenvalue.real, -1.86, delta=0.05)
 
+    def _get_operator(self, weight_matrix):
+        """Generate Hamiltonian for the max-cut problem of a graph.
+
+        Args:
+            weight_matrix (numpy.ndarray) : adjacency matrix.
+
+        Returns:
+            PauliSumOp: operator for the Hamiltonian
+            float: a constant shift for the obj function.
+
+        """
+        num_nodes = weight_matrix.shape[0]
+        pauli_list = []
+        shift = 0
+        for i in range(num_nodes):
+            for j in range(i):
+                if weight_matrix[i, j] != 0:
+                    x_p = np.zeros(num_nodes, dtype=bool)
+                    z_p = np.zeros(num_nodes, dtype=bool)
+                    z_p[i] = True
+                    z_p[j] = True
+                    pauli_list.append([0.5 * weight_matrix[i, j], Pauli((z_p, x_p))])
+                    shift -= 0.5 * weight_matrix[i, j]
+        opflow_list = [(pauli[1].to_label(), pauli[0]) for pauli in pauli_list]
+        return PauliSumOp.from_list(opflow_list), shift
+
+    def test_measurement_error_mitigation_qaoa(self):
+        """measurement error mitigation test with QAOA"""
+        if _ERROR_MITIGATION_IMPORT_ERROR is not None:
+            self.skipTest(
+                f"Package doesn't appear to be installed. Error: '{_ERROR_MITIGATION_IMPORT_ERROR}'"
+            )
+            return
+
+        algorithm_globals.random_seed = 167
+
+        # build noise model
+        noise_model = noise.NoiseModel()
+        read_err = noise.errors.readout_error.ReadoutError([[0.9, 0.1], [0.25, 0.75]])
+        noise_model.add_all_qubit_readout_error(read_err)
+
+        backend = Aer.get_backend("aer_simulator")
+        quantum_instance = QuantumInstance(
+            backend=backend,
+            seed_simulator=algorithm_globals.random_seed,
+            seed_transpiler=algorithm_globals.random_seed,
+            noise_model=noise_model,
+            measurement_error_mitigation_cls=CompleteMeasFitter,
+        )
+        w = rx.adjacency_matrix(
+            rx.undirected_gnp_random_graph(5, 0.5, seed=algorithm_globals.random_seed)
+        )
+        qubit_op, _ = self._get_operator(w)
+        qaoa = QAOA(
+            optimizer=COBYLA(maxiter=1),
+            quantum_instance=quantum_instance,
+            initial_point=np.asarray([0.0, 0.0]),
+        )
+        result = qaoa.compute_minimum_eigenvalue(operator=qubit_op)
+        self.assertAlmostEqual(result.eigenvalue.real, 3.49, delta=0.05)
+
 
 if __name__ == "__main__":
     unittest.main()