Example #1
0
    def execute(self, circuits, had_transpiled: bool = False):
        """
        A wrapper to interface with quantum backend.

        Args:
            circuits (Union['QuantumCircuit', List['QuantumCircuit']]):
                        circuits to execute
            had_transpiled: whether or not circuits had been transpiled

        Raises:
            QiskitError: Invalid error mitigation fitter class
            QiskitError: TensoredMeasFitter class doesn't support subset fitter
            MissingOptionalLibraryError: Ignis not installed


        Returns:
            Result: result object

        TODO: Maybe we can combine the circuits for the main ones and calibration circuits before
              assembling to the qobj.
        """
        from qiskit.utils.run_circuits import run_qobj, run_circuits
        from qiskit.utils.measurement_error_mitigation import (
            get_measured_qubits_from_qobj,
            get_measured_qubits,
            build_measurement_error_mitigation_circuits,
            build_measurement_error_mitigation_qobj,
        )

        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 Backend

        circuit_job = isinstance(self._backend, Backend)
        if self.is_statevector and self.backend_name == "aer_simulator_statevector":
            try:
                from qiskit.providers.aer.library import SaveStatevector

                def _find_save_state(data):
                    for instr, _, _ in reversed(data):
                        if isinstance(instr, SaveStatevector):
                            return True
                    return False

                if isinstance(circuits, list):
                    for circuit in circuits:
                        if not _find_save_state(circuit.data):
                            circuit.save_statevector()
                else:
                    if not _find_save_state(circuits.data):
                        circuits.save_statevector()
            except ImportError:
                pass

        # assemble
        if not circuit_job:
            qobj = self.assemble(circuits)

        if self._meas_error_mitigation_cls is not None:
            qubit_index, qubit_mappings = (
                get_measured_qubits(circuits)
                if circuit_job
                else get_measured_qubits_from_qobj(qobj)
            )
            mit_pattern = self._mit_pattern
            if mit_pattern is None:
                mit_pattern = [[i] for i in range(len(qubit_index))]
            qubit_index_str = "_".join([str(x) for x in qubit_index]) + "_{}".format(
                self._meas_error_mitigation_shots or self._run_config.shots
            )
            meas_error_mitigation_fitter, timestamp = self._meas_error_mitigation_fitters.get(
                qubit_index_str, (None, 0.0)
            )

            if meas_error_mitigation_fitter is None:
                # check the asked qubit_index are the subset of build matrices
                for key, _ in self._meas_error_mitigation_fitters.items():
                    stored_qubit_index = [int(x) for x in key.split("_")[:-1]]
                    stored_shots = int(key.split("_")[-1])
                    if len(qubit_index) < len(stored_qubit_index):
                        tmp = list(set(qubit_index + stored_qubit_index))
                        if (
                            sorted(tmp) == sorted(stored_qubit_index)
                            and self._run_config.shots == stored_shots
                        ):
                            # the qubit used in current job is the subset and shots are the same
                            (
                                meas_error_mitigation_fitter,
                                timestamp,
                            ) = self._meas_error_mitigation_fitters.get(key, (None, 0.0))
                            meas_error_mitigation_fitter = (
                                meas_error_mitigation_fitter.subset_fitter(
                                    qubit_sublist=qubit_index
                                )
                            )
                            logger.info(
                                "The qubits used in the current job is the subset of "
                                "previous jobs, "
                                "reusing the calibration matrix if it is not out-of-date."
                            )

            build_cals_matrix = (
                self.maybe_refresh_cals_matrix(timestamp) or meas_error_mitigation_fitter is None
            )

            cal_circuits = None
            prepended_calibration_circuits: int = 0
            if build_cals_matrix:
                if circuit_job:
                    logger.info("Updating to also run measurement error mitigation.")
                    use_different_shots = not (
                        self._meas_error_mitigation_shots is None
                        or self._meas_error_mitigation_shots == self._run_config.shots
                    )
                    temp_run_config = copy.deepcopy(self._run_config)
                    if use_different_shots:
                        temp_run_config.shots = self._meas_error_mitigation_shots
                    (
                        cal_circuits,
                        state_labels,
                        circuit_labels,
                    ) = build_measurement_error_mitigation_circuits(
                        qubit_index,
                        self._meas_error_mitigation_cls,
                        self._backend,
                        self._backend_config,
                        self._compile_config,
                        mit_pattern=mit_pattern,
                    )
                    if use_different_shots:
                        cals_result = run_circuits(
                            cal_circuits,
                            self._backend,
                            qjob_config=self._qjob_config,
                            backend_options=self._backend_options,
                            noise_config=self._noise_config,
                            run_config=self._run_config.to_dict(),
                            job_callback=self._job_callback,
                            max_job_retries=self._max_job_retries,
                        )
                        self._time_taken += cals_result.time_taken
                        result = run_circuits(
                            circuits,
                            self._backend,
                            qjob_config=self.qjob_config,
                            backend_options=self.backend_options,
                            noise_config=self._noise_config,
                            run_config=self.run_config.to_dict(),
                            job_callback=self._job_callback,
                            max_job_retries=self._max_job_retries,
                        )
                        self._time_taken += result.time_taken
                    else:
                        circuits[0:0] = cal_circuits
                        prepended_calibration_circuits = len(cal_circuits)
                        result = run_circuits(
                            circuits,
                            self._backend,
                            qjob_config=self.qjob_config,
                            backend_options=self.backend_options,
                            noise_config=self._noise_config,
                            run_config=self.run_config.to_dict(),
                            job_callback=self._job_callback,
                            max_job_retries=self._max_job_retries,
                        )
                        self._time_taken += result.time_taken
                        cals_result = result

                else:
                    logger.info("Updating qobj with the circuits for measurement error mitigation.")
                    use_different_shots = not (
                        self._meas_error_mitigation_shots is None
                        or self._meas_error_mitigation_shots == self._run_config.shots
                    )
                    temp_run_config = copy.deepcopy(self._run_config)
                    if use_different_shots:
                        temp_run_config.shots = self._meas_error_mitigation_shots

                    (
                        cals_qobj,
                        state_labels,
                        circuit_labels,
                    ) = build_measurement_error_mitigation_qobj(
                        qubit_index,
                        self._meas_error_mitigation_cls,
                        self._backend,
                        self._backend_config,
                        self._compile_config,
                        temp_run_config,
                        mit_pattern=mit_pattern,
                    )
                    if use_different_shots or is_aer_qasm(self._backend):
                        cals_result = run_qobj(
                            cals_qobj,
                            self._backend,
                            self._qjob_config,
                            self._backend_options,
                            self._noise_config,
                            self._skip_qobj_validation,
                            self._job_callback,
                            self._max_job_retries,
                        )
                        self._time_taken += cals_result.time_taken
                        result = run_qobj(
                            qobj,
                            self._backend,
                            self._qjob_config,
                            self._backend_options,
                            self._noise_config,
                            self._skip_qobj_validation,
                            self._job_callback,
                            self._max_job_retries,
                        )
                        self._time_taken += result.time_taken
                    else:
                        # insert the calibration circuit into main qobj if the shots are the same
                        qobj.experiments[0:0] = cals_qobj.experiments
                        result = run_qobj(
                            qobj,
                            self._backend,
                            self._qjob_config,
                            self._backend_options,
                            self._noise_config,
                            self._skip_qobj_validation,
                            self._job_callback,
                            self._max_job_retries,
                        )
                        self._time_taken += result.time_taken
                        cals_result = result

                logger.info("Building calibration matrix for measurement error mitigation.")
                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 meas_type == _MeasFitterType.TENSORED_MEAS_FITTER:
                    meas_error_mitigation_fitter = self._meas_error_mitigation_cls(
                        cals_result, mit_pattern=state_labels, circlabel=circuit_labels
                    )
                self._meas_error_mitigation_fitters[qubit_index_str] = (
                    meas_error_mitigation_fitter,
                    time.time(),
                )
            else:
                result = (
                    run_circuits(
                        circuits,
                        self._backend,
                        qjob_config=self.qjob_config,
                        backend_options=self.backend_options,
                        noise_config=self._noise_config,
                        run_config=self._run_config.to_dict(),
                        job_callback=self._job_callback,
                        max_job_retries=self._max_job_retries,
                    )
                    if circuit_job
                    else run_qobj(
                        qobj,
                        self._backend,
                        self._qjob_config,
                        self._backend_options,
                        self._noise_config,
                        self._skip_qobj_validation,
                        self._job_callback,
                        self._max_job_retries,
                    )
                )
                self._time_taken += result.time_taken

            if meas_error_mitigation_fitter is not None:
                logger.info("Performing measurement error mitigation.")
                if (
                    hasattr(self._run_config, "parameterizations")
                    and len(self._run_config.parameterizations) > 0
                    and len(self._run_config.parameterizations[0]) > 0
                    and len(self._run_config.parameterizations[0][0]) > 0
                ):
                    num_circuit_templates = len(self._run_config.parameterizations)
                    num_param_variations = len(self._run_config.parameterizations[0][0])
                    num_circuits = num_circuit_templates * num_param_variations
                else:
                    input_circuits = circuits[prepended_calibration_circuits:]
                    num_circuits = len(input_circuits)
                skip_num_circuits = len(result.results) - num_circuits
                #  remove the calibration counts from result object to assure the length of
                #  ExperimentalResult is equal length to input circuits
                result.results = result.results[skip_num_circuits:]
                tmp_result = copy.deepcopy(result)
                for qubit_index_str, c_idx in qubit_mappings.items():
                    curr_qubit_index = [int(x) for x in qubit_index_str.split("_")]
                    tmp_result.results = [result.results[i] for i in c_idx]
                    if curr_qubit_index == qubit_index:
                        tmp_fitter = meas_error_mitigation_fitter
                    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(
                            "{} doesn't support subset_fitter.".format(
                                meas_error_mitigation_fitter.__class__.__name__
                            )
                        )
                    tmp_result = tmp_fitter.filter.apply(
                        tmp_result, self._meas_error_mitigation_method
                    )
                    for i, n in enumerate(c_idx):
                        # convert counts to integer and remove 0 values
                        tmp_result.results[i].data.counts = {
                            k: round(v)
                            for k, v in tmp_result.results[i].data.counts.items()
                            if round(v) != 0
                        }
                        result.results[n] = tmp_result.results[i]

        else:
            result = (
                run_circuits(
                    circuits,
                    self._backend,
                    qjob_config=self.qjob_config,
                    backend_options=self.backend_options,
                    noise_config=self._noise_config,
                    run_config=self._run_config.to_dict(),
                    job_callback=self._job_callback,
                    max_job_retries=self._max_job_retries,
                )
                if circuit_job
                else run_qobj(
                    qobj,
                    self._backend,
                    self._qjob_config,
                    self._backend_options,
                    self._noise_config,
                    self._skip_qobj_validation,
                    self._job_callback,
                    self._max_job_retries,
                )
            )
            self._time_taken += result.time_taken

        if self._circuit_summary:
            self._circuit_summary = False

        return result
    def build(operator: OperatorBase,
              backend: Optional[Union[Backend, BaseBackend,
                                      QuantumInstance]] = None,
              include_custom: bool = True) -> ExpectationBase:
        """
        A factory method for convenient automatic selection of an Expectation based on the
        Operator to be converted and backend used to sample the expectation value.

        Args:
            operator: The Operator whose expectation value will be taken.
            backend: The backend which will be used to sample the expectation value.
            include_custom: Whether the factory will include the (Aer) specific custom
                expectations if their behavior against the backend might not be as expected.
                For instance when using Aer qasm_simulator with paulis the Aer snapshot can
                be used but the outcome lacks shot noise and hence does not intuitively behave
                overall as people might expect when choosing a qasm_simulator. It is however
                fast as long as the more state vector like behavior is acceptable.

        Returns:
            The expectation algorithm which best fits the Operator and backend.

        Raises:
            ValueError: If operator is not of a composition for which we know the best Expectation
                method.
        """
        backend_to_check = backend.backend if isinstance(
            backend, QuantumInstance) else backend

        # pylint: disable=cyclic-import,import-outside-toplevel
        primitives = operator.primitive_strings()
        if primitives in ({'Pauli'}, {'SparsePauliOp'}):

            if backend_to_check is None:
                # If user has Aer but didn't specify a backend, use the Aer fast expectation
                if has_aer():
                    from qiskit import Aer
                    backend_to_check = Aer.get_backend('qasm_simulator')
                # If user doesn't have Aer, use statevector_simulator
                # for < 16 qubits, and qasm with warning for more.
                else:
                    if operator.num_qubits <= 16:
                        backend_to_check = BasicAer.get_backend(
                            'statevector_simulator')
                    else:
                        logging.warning(
                            '%d qubits is a very large expectation value. '
                            'Consider installing Aer to use '
                            'Aer\'s fast expectation, which will perform better here. We\'ll use '
                            'the BasicAer qasm backend for this expectation to avoid having to '
                            'construct the %dx%d operator matrix.',
                            operator.num_qubits, 2**operator.num_qubits,
                            2**operator.num_qubits)
                        backend_to_check = BasicAer.get_backend(
                            'qasm_simulator')

            # If the user specified Aer qasm backend and is using a
            # Pauli operator, use the Aer fast expectation if we are including such
            # custom behaviors.
            if is_aer_qasm(backend_to_check) and include_custom:
                return AerPauliExpectation()

            # If the user specified a statevector backend (either Aer or BasicAer),
            # use a converter to produce a
            # Matrix operator and compute using matmul
            elif is_statevector_backend(backend_to_check):
                if operator.num_qubits >= 16:
                    logging.warning(
                        'Note: Using a statevector_simulator with %d qubits can be very expensive. '
                        'Consider using the Aer qasm_simulator instead to take advantage of Aer\'s '
                        'built-in fast Pauli Expectation', operator.num_qubits)
                return MatrixExpectation()

            # All other backends, including IBMQ, BasicAer QASM, go here.
            else:
                return PauliExpectation()

        elif primitives == {'Matrix'}:
            return MatrixExpectation()

        else:
            raise ValueError(
                'Expectations of Mixed Operators not yet supported.')