From ff92fafc3912d74666eb6c1bca022ceed621191b Mon Sep 17 00:00:00 2001 From: Manoel Marques Date: Thu, 22 Apr 2021 16:05:54 -0400 Subject: [PATCH 1/6] Support BackendV1 --- qiskit/utils/quantum_instance.py | 22 ++++- qiskit/utils/run_circuits.py | 152 +++++++++++++++++++++++++++++++ 2 files changed, 172 insertions(+), 2 deletions(-) diff --git a/qiskit/utils/quantum_instance.py b/qiskit/utils/quantum_instance.py index f031e11520ec..d28f1945c0c4 100644 --- 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, 2020. +# (C) Copyright IBM 2018, 2021. # # 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 @@ -22,6 +22,7 @@ from qiskit.utils import circuit_utils from qiskit.exceptions import QiskitError from .backend_utils import (is_ibmq_provider, + is_aer_provider, is_statevector_backend, is_simulator_backend, is_local_backend, @@ -295,7 +296,7 @@ def execute(self, 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 + from qiskit.utils.run_circuits import run_qobj, run_circuits from qiskit.utils.measurement_error_mitigation import \ (get_measured_qubits_from_qobj, build_measurement_error_mitigation_qobj) @@ -304,6 +305,23 @@ def execute(self, if not had_transpiled: circuits = self.transpile(circuits) + # fix for providers that don't support QasmQobj until a bigger refactor happens + from qiskit.providers import BackendV1 + if isinstance(self._backend, BackendV1) and \ + not is_aer_provider(self._backend) and \ + not is_basicaer_provider(self._backend) and \ + not is_ibmq_provider(self._backend): + result = run_circuits(circuits, + self._backend, + self.backend_options, + self.qjob_config, + self.run_config, + self._job_callback) + self._time_taken += result.time_taken + if self._circuit_summary: + self._circuit_summary = False + return result + # assemble qobj = self.assemble(circuits) diff --git a/qiskit/utils/run_circuits.py b/qiskit/utils/run_circuits.py index de8f2af94b53..1ea2d73dd0cd 100644 --- a/qiskit/utils/run_circuits.py +++ b/qiskit/utils/run_circuits.py @@ -26,6 +26,8 @@ from qiskit.providers.jobstatus import JOB_FINAL_STATES from qiskit.result import Result from qiskit.qobj import QasmQobj +from qiskit.qobj.utils import MeasLevel, MeasReturnType +from qiskit.assembler.run_config import RunConfig from ..exceptions import QiskitError, MissingOptionalLibraryError from .backend_utils import (is_aer_provider, is_basicaer_provider, @@ -381,3 +383,153 @@ def run_on_backend(backend: Union[Backend, BaseBackend], else: job = backend.run(qobj, **backend_options, **noise_config) return job + + +def run_circuits(circuits: Union[QuantumCircuit, List[QuantumCircuit]], + backend: Backend, + backend_options: Dict, + qjob_config: Dict, + run_config: RunConfig, + job_callback: Optional[Callable] = None) -> Result: + """ + An execution wrapper with Qiskit-Terra, with job auto recover capability. + + The auto-recovery feature is only applied for non-simulator backend. + This wrapper will try to get the result no matter how long it takes. + + Args: + circuits: circuits to execute + backend: backend instance + backend_options: backend options + qjob_config: configuration for quantum job object + run_config: configuration for run + job_callback: callback used in querying info of the submitted job, and + providing the following arguments: + job_id, job_status, queue_position, job + + Returns: + Result object + + Raises: + QiskitError: Any error except for JobError raised by Qiskit Terra + """ + with_autorecover = not is_simulator_backend(backend) + run_config = run_config.to_dict() + job = _execute_circuits(circuits, + backend, + shots=run_config.get('shots'), + memory=backend_options.get('memory', False), + max_credits=run_config.get('max_credits'), + seed_simulator=run_config.get('seed_simulator')) + job_id = job.job_id() + + result = None + if with_autorecover: + logger.info("Backend status: %s", backend.status()) + logger.info("There is one jobs are submitted: id: %s", job_id) + while True: + logger.info("Running job id: %s", job_id) + # try to get result if possible + while True: + job_status = _safe_get_job_status(job, job_id) + queue_position = 0 + if job_status in JOB_FINAL_STATES: + # do callback again after the job is in the final states + if job_callback is not None: + job_callback(job_id, job_status, queue_position, job) + break + if job_status == JobStatus.QUEUED: + queue_position = job.queue_position() + logger.info("Job id: %s is queued at position %s", job_id, queue_position) + else: + logger.info("Job id: %s, status: %s", job_id, job_status) + if job_callback is not None: + job_callback(job_id, job_status, queue_position, job) + time.sleep(qjob_config['wait']) + + # get result after the status is DONE + if job_status == JobStatus.DONE: + while True: + result = job.result(**qjob_config) + if result.success: + logger.info("COMPLETED: job id: %s", job_id) + break + + logger.warning("FAILURE: Job id: %s", job_id) + logger.warning("Job (%s) is completed anyway, retrieve result " + "from backend again.", job_id) + job = backend.retrieve_job(job_id) + break + # for other cases, resubmit the circuit until the result is available. + # since if there is no result returned, there is no way algorithm can do any process + if job_status == JobStatus.CANCELLED: + logger.warning("FAILURE: Job id: %s is cancelled. Re-submit the circuits.", + job_id) + elif job_status == JobStatus.ERROR: + logger.warning("FAILURE: Job id: %s encounters the error. " + "Error is : %s. Re-submit the circuits.", + job_id, job.error_message()) + else: + logging.warning("FAILURE: Job id: %s. Unknown status: %s. " + "Re-submit the circuits.", job_id, job_status) + + job = _execute_circuits(circuits, + backend, + shots=run_config.get('shots'), + memory=backend_options.get('memory', False), + max_credits=run_config.get('max_credits'), + seed_simulator=run_config.get('seed_simulator')) + job_id = job.job_id() + else: + result = job.result(**qjob_config) + + # If result was not successful then raise an exception with either the status msg or + # extra information if this was an Aer partial result return + if not result.success: + msg = result.status + if result.status == 'PARTIAL COMPLETED': + # Aer can return partial results which Aqua algorithms cannot process and signals + # using partial completed status where each returned result has a success and status. + # We use the status from the first result that was not successful + for res in result.results: + if not res.success: + msg += ', ' + res.status + break + raise QiskitError('Circuit execution failed: {}'.format(msg)) + + if not hasattr(result, 'time_taken'): + setattr(result, 'time_taken', 0.) + + return result + + +def _execute_circuits(experiments, + backend, + shots, + memory, + max_credits, + seed_simulator): + run_kwargs = { + 'shots': shots, + 'memory': memory, + 'max_credits': max_credits, + 'seed_simulator': seed_simulator, + } + for key in list(run_kwargs.keys()): + if not hasattr(backend.options, key): + if run_kwargs[key] is not None: + logger.info("%s backend doesn't support option %s so not " + "passing that kwarg to run()", backend.name, key) + del run_kwargs[key] + elif key == 'shots' and run_kwargs[key] is None: + run_kwargs[key] = 1024 + elif key == 'max_credits' and run_kwargs[key] is None: + run_kwargs[key] = 10 + elif key == 'meas_level' and run_kwargs[key] is None: + run_kwargs[key] = MeasLevel.CLASSIFIED + elif key == 'meas_return' and run_kwargs[key] is None: + run_kwargs[key] = MeasReturnType.AVERAGE + elif key == 'memory_slot_size' and run_kwargs[key] is None: + run_kwargs[key] = 100 + + return backend.run(experiments, **run_kwargs) From 6ec170087bf7107b645e8db1a5c2e66298b95fec Mon Sep 17 00:00:00 2001 From: Manoel Marques Date: Wed, 28 Apr 2021 17:38:03 -0400 Subject: [PATCH 2/6] Add error mitigation logic. Co-authored-by: Matthew Treinish --- qiskit/utils/backend_utils.py | 11 +- qiskit/utils/measurement_error_mitigation.py | 57 ++++++- qiskit/utils/quantum_instance.py | 149 ++++++++++++------- qiskit/utils/run_circuits.py | 4 +- 4 files changed, 162 insertions(+), 59 deletions(-) diff --git a/qiskit/utils/backend_utils.py b/qiskit/utils/backend_utils.py index 034905afe08a..9e4f9b43d269 100644 --- a/qiskit/utils/backend_utils.py +++ b/qiskit/utils/backend_utils.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2018, 2020. +# (C) Copyright IBM 2018, 2021. # # 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 @@ -75,7 +75,10 @@ def is_aer_provider(backend): """ if has_aer(): from qiskit.providers.aer import AerProvider - return isinstance(backend.provider(), AerProvider) + if isinstance(backend.provider(), AerProvider): + return True + from qiskit.providers.aer.backends.aerbackend import AerBackend + return isinstance(backend, AerBackend) return False @@ -129,6 +132,10 @@ def is_statevector_backend(backend): Returns: bool: True is statevector """ + if has_aer(): + from qiskit.providers.aer.backends import StatevectorSimulator + if isinstance(backend, StatevectorSimulator): + return True return backend.name().startswith('statevector') if backend is not None else False diff --git a/qiskit/utils/measurement_error_mitigation.py b/qiskit/utils/measurement_error_mitigation.py index 2e0369e8e03e..2046af19792d 100644 --- a/qiskit/utils/measurement_error_mitigation.py +++ b/qiskit/utils/measurement_error_mitigation.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2019, 2020. +# (C) Copyright IBM 2019, 2021. # # 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 @@ -17,6 +17,8 @@ from qiskit import compiler from ..exceptions import QiskitError, MissingOptionalLibraryError +# pylint: disable=invalid-name + def get_measured_qubits(transpiled_circuits): """ @@ -39,7 +41,7 @@ def get_measured_qubits(transpiled_circuits): for inst, qargs, _ in qc.data: if inst.name != 'measure': continue - measured_qubits.append(qargs[0][1]) + measured_qubits.append(qargs[0].index) measured_qubits_str = '_'.join([str(x) for x in measured_qubits]) if measured_qubits_str not in qubit_mappings: qubit_mappings[measured_qubits_str] = [] @@ -93,7 +95,56 @@ def get_measured_qubits_from_qobj(qobj): return sorted(qubit_index), qubit_mappings -# pylint: disable=invalid-name +def build_measurement_error_mitigation_circuits(qubit_list, fitter_cls, backend, + backend_config=None, compile_config=None): + """Build measurement error mitigation circuits + Args: + qubit_list (list[int]): list of ordered qubits used in the algorithm + fitter_cls (callable): CompleteMeasFitter or TensoredMeasFitter + backend (BaseBackend): backend instance + backend_config (dict, optional): configuration for backend + compile_config (dict, optional): configuration for compilation + Returns: + QasmQobj: the Qobj with calibration circuits at the beginning + list[str]: the state labels for build MeasFitter + list[str]: the labels of the calibration circuits + Raises: + QiskitError: when the fitter_cls is not recognizable. + MissingOptionalLibraryError: Qiskit-Ignis not installed + """ + try: + from qiskit.ignis.mitigation.measurement import (complete_meas_cal, + CompleteMeasFitter, TensoredMeasFitter) + except ImportError as ex: + raise MissingOptionalLibraryError( + libname='qiskit-ignis', + name='build_measurement_error_mitigation_qobj', + pip_install='pip install qiskit-ignis') from ex + + circlabel = 'mcal' + + if not qubit_list: + raise QiskitError("The measured qubit list can not be [].") + + if fitter_cls == CompleteMeasFitter: + meas_calibs_circuits, state_labels = \ + complete_meas_cal(qubit_list=range(len(qubit_list)), circlabel=circlabel) + elif fitter_cls == TensoredMeasFitter: + # TODO support different calibration + raise QiskitError("Does not support TensoredMeasFitter yet.") + else: + raise QiskitError("Unknown fitter {}".format(fitter_cls)) + + # the provided `qubit_list` would be used as the initial layout to + # assure the consistent qubit mapping used in the main circuits. + + tmp_compile_config = copy.deepcopy(compile_config) + tmp_compile_config['initial_layout'] = qubit_list + t_meas_calibs_circuits = compiler.transpile(meas_calibs_circuits, backend, + **backend_config, **tmp_compile_config) + return t_meas_calibs_circuits, state_labels, circlabel + + def build_measurement_error_mitigation_qobj(qubit_list, fitter_cls, backend, backend_config=None, compile_config=None, run_config=None): diff --git a/qiskit/utils/quantum_instance.py b/qiskit/utils/quantum_instance.py index d28f1945c0c4..6ba1ce112770 100644 --- a/qiskit/utils/quantum_instance.py +++ b/qiskit/utils/quantum_instance.py @@ -299,7 +299,9 @@ def execute(self, from qiskit.utils.run_circuits import run_qobj, run_circuits from qiskit.utils.measurement_error_mitigation import \ - (get_measured_qubits_from_qobj, build_measurement_error_mitigation_qobj) + (get_measured_qubits_from_qobj, get_measured_qubits, + build_measurement_error_mitigation_circuits, + build_measurement_error_mitigation_qobj) # maybe compile if not had_transpiled: @@ -307,26 +309,18 @@ def execute(self, # fix for providers that don't support QasmQobj until a bigger refactor happens from qiskit.providers import BackendV1 - if isinstance(self._backend, BackendV1) and \ - not is_aer_provider(self._backend) and \ - not is_basicaer_provider(self._backend) and \ - not is_ibmq_provider(self._backend): - result = run_circuits(circuits, - self._backend, - self.backend_options, - self.qjob_config, - self.run_config, - self._job_callback) - self._time_taken += result.time_taken - if self._circuit_summary: - self._circuit_summary = False - return result + circuit_job = isinstance(self._backend, BackendV1) and \ + not is_aer_provider(self._backend) and \ + not is_basicaer_provider(self._backend) and \ + not is_ibmq_provider(self._backend) # assemble - qobj = self.assemble(circuits) + if not circuit_job: + qobj = self.assemble(circuits) if self._meas_error_mitigation_cls is not None: - qubit_index, qubit_mappings = get_measured_qubits_from_qobj(qobj) + qubit_index, qubit_mappings = get_measured_qubits(circuits) \ + if circuit_job else get_measured_qubits_from_qobj(qobj) 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 = \ @@ -355,39 +349,80 @@ def execute(self, meas_error_mitigation_fitter is None if build_cals_matrix: - 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) - 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._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._time_taken += result.time_taken + 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) + if use_different_shots: + cals_result = run_circuits(cal_circuits, self._backend, + self._backend_options, + self._qjob_config, + self._noise_config, + self._job_callback) + self._time_taken += cals_result.time_taken + result = run_circuits(circuits, + self._backend, + self.backend_options, + self.qjob_config, + self.run_config.to_dict(), + self._job_callback) + self._time_taken += result.time_taken + else: + circuits[0:0] = cal_circuits + result = run_circuits(circuits, + self._backend, + self.backend_options, + self.qjob_config, + self._noise_config, + self._job_callback) + self._time_taken += result.time_taken + cals_result = result + 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._time_taken += result.time_taken - cals_result = result + 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) + 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._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._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._time_taken += result.time_taken + cals_result = result logger.info("Building calibration matrix for measurement error mitigation.") meas_error_mitigation_fitter = \ @@ -398,7 +433,13 @@ def execute(self, self._meas_error_mitigation_fitters[qubit_index_str] = \ (meas_error_mitigation_fitter, time.time()) else: - result = run_qobj(qobj, self._backend, self._qjob_config, + result = run_circuits(circuits, + self._backend, + self.backend_options, + self.qjob_config, + self._noise_config, + self._job_callback) 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._time_taken += result.time_taken @@ -424,7 +465,13 @@ def execute(self, result.results[n] = tmp_result.results[i] else: - result = run_qobj(qobj, self._backend, self._qjob_config, + result = run_circuits(circuits, + self._backend, + self.backend_options, + self.qjob_config, + self._noise_config, + self._job_callback) 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._time_taken += result.time_taken diff --git a/qiskit/utils/run_circuits.py b/qiskit/utils/run_circuits.py index 1ea2d73dd0cd..666c28f6ab5f 100644 --- a/qiskit/utils/run_circuits.py +++ b/qiskit/utils/run_circuits.py @@ -27,7 +27,6 @@ from qiskit.result import Result from qiskit.qobj import QasmQobj from qiskit.qobj.utils import MeasLevel, MeasReturnType -from qiskit.assembler.run_config import RunConfig from ..exceptions import QiskitError, MissingOptionalLibraryError from .backend_utils import (is_aer_provider, is_basicaer_provider, @@ -389,7 +388,7 @@ def run_circuits(circuits: Union[QuantumCircuit, List[QuantumCircuit]], backend: Backend, backend_options: Dict, qjob_config: Dict, - run_config: RunConfig, + run_config: Dict, job_callback: Optional[Callable] = None) -> Result: """ An execution wrapper with Qiskit-Terra, with job auto recover capability. @@ -414,7 +413,6 @@ def run_circuits(circuits: Union[QuantumCircuit, List[QuantumCircuit]], QiskitError: Any error except for JobError raised by Qiskit Terra """ with_autorecover = not is_simulator_backend(backend) - run_config = run_config.to_dict() job = _execute_circuits(circuits, backend, shots=run_config.get('shots'), From b1243985585a3cf7c1409438cbf54c7ecbae9675 Mon Sep 17 00:00:00 2001 From: Manoel Marques Date: Fri, 30 Apr 2021 12:35:45 -0400 Subject: [PATCH 3/6] Fix run_circuits calls --- qiskit/utils/quantum_instance.py | 48 ++++++----- qiskit/utils/run_circuits.py | 133 +++++++++++++++++++------------ 2 files changed, 109 insertions(+), 72 deletions(-) diff --git a/qiskit/utils/quantum_instance.py b/qiskit/utils/quantum_instance.py index 6ba1ce112770..ba99cb427f2e 100644 --- a/qiskit/utils/quantum_instance.py +++ b/qiskit/utils/quantum_instance.py @@ -365,27 +365,31 @@ def execute(self, self._backend_config, self._compile_config) if use_different_shots: - cals_result = run_circuits(cal_circuits, self._backend, - self._backend_options, - self._qjob_config, - self._noise_config, - self._job_callback) + 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) self._time_taken += cals_result.time_taken result = run_circuits(circuits, self._backend, - self.backend_options, - self.qjob_config, - self.run_config.to_dict(), - self._job_callback) + 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) self._time_taken += result.time_taken else: circuits[0:0] = cal_circuits result = run_circuits(circuits, self._backend, - self.backend_options, - self.qjob_config, - self._noise_config, - self._job_callback) + 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) self._time_taken += result.time_taken cals_result = result @@ -435,10 +439,11 @@ def execute(self, else: result = run_circuits(circuits, self._backend, - self.backend_options, - self.qjob_config, - self._noise_config, - self._job_callback) if circuit_job else \ + 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) 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) @@ -467,10 +472,11 @@ def execute(self, else: result = run_circuits(circuits, self._backend, - self.backend_options, - self.qjob_config, - self._noise_config, - self._job_callback) if circuit_job else \ + 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) 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) diff --git a/qiskit/utils/run_circuits.py b/qiskit/utils/run_circuits.py index 666c28f6ab5f..097bf3fac943 100644 --- a/qiskit/utils/run_circuits.py +++ b/qiskit/utils/run_circuits.py @@ -26,7 +26,6 @@ from qiskit.providers.jobstatus import JOB_FINAL_STATES from qiskit.result import Result from qiskit.qobj import QasmQobj -from qiskit.qobj.utils import MeasLevel, MeasReturnType from ..exceptions import QiskitError, MissingOptionalLibraryError from .backend_utils import (is_aer_provider, is_basicaer_provider, @@ -385,10 +384,11 @@ def run_on_backend(backend: Union[Backend, BaseBackend], def run_circuits(circuits: Union[QuantumCircuit, List[QuantumCircuit]], - backend: Backend, - backend_options: Dict, + backend: Union[Backend, BaseBackend], qjob_config: Dict, - run_config: Dict, + backend_options: Optional[Dict] = None, + noise_config: Optional[Dict] = None, + run_config: Optional[Dict] = None, job_callback: Optional[Callable] = None) -> Result: """ An execution wrapper with Qiskit-Terra, with job auto recover capability. @@ -399,8 +399,9 @@ def run_circuits(circuits: Union[QuantumCircuit, List[QuantumCircuit]], Args: circuits: circuits to execute backend: backend instance - backend_options: backend options qjob_config: configuration for quantum job object + backend_options: backend options + noise_config: configuration for noise model run_config: configuration for run job_callback: callback used in querying info of the submitted job, and providing the following arguments: @@ -412,15 +413,17 @@ def run_circuits(circuits: Union[QuantumCircuit, List[QuantumCircuit]], Raises: QiskitError: Any error except for JobError raised by Qiskit Terra """ + backend_options = backend_options or {} + noise_config = noise_config or {} + run_config = run_config or {} with_autorecover = not is_simulator_backend(backend) - job = _execute_circuits(circuits, - backend, - shots=run_config.get('shots'), - memory=backend_options.get('memory', False), - max_credits=run_config.get('max_credits'), - seed_simulator=run_config.get('seed_simulator')) - job_id = job.job_id() + job, job_id = _safe_submit_circuits(circuits, + backend, + qjob_config=qjob_config, + backend_options=backend_options, + noise_config=noise_config, + run_config=run_config) result = None if with_autorecover: logger.info("Backend status: %s", backend.status()) @@ -448,7 +451,7 @@ def run_circuits(circuits: Union[QuantumCircuit, List[QuantumCircuit]], # get result after the status is DONE if job_status == JobStatus.DONE: while True: - result = job.result(**qjob_config) + result = job.result() if result.success: logger.info("COMPLETED: job id: %s", job_id) break @@ -471,15 +474,14 @@ def run_circuits(circuits: Union[QuantumCircuit, List[QuantumCircuit]], logging.warning("FAILURE: Job id: %s. Unknown status: %s. " "Re-submit the circuits.", job_id, job_status) - job = _execute_circuits(circuits, - backend, - shots=run_config.get('shots'), - memory=backend_options.get('memory', False), - max_credits=run_config.get('max_credits'), - seed_simulator=run_config.get('seed_simulator')) - job_id = job.job_id() + job, job_id = _safe_submit_circuits(circuits, + backend, + qjob_config=qjob_config, + backend_options=backend_options, + noise_config=noise_config, + run_config=run_config) else: - result = job.result(**qjob_config) + result = job.result() # If result was not successful then raise an exception with either the status msg or # extra information if this was an Aer partial result return @@ -501,33 +503,62 @@ def run_circuits(circuits: Union[QuantumCircuit, List[QuantumCircuit]], return result -def _execute_circuits(experiments, - backend, - shots, - memory, - max_credits, - seed_simulator): - run_kwargs = { - 'shots': shots, - 'memory': memory, - 'max_credits': max_credits, - 'seed_simulator': seed_simulator, - } - for key in list(run_kwargs.keys()): - if not hasattr(backend.options, key): - if run_kwargs[key] is not None: - logger.info("%s backend doesn't support option %s so not " - "passing that kwarg to run()", backend.name, key) - del run_kwargs[key] - elif key == 'shots' and run_kwargs[key] is None: - run_kwargs[key] = 1024 - elif key == 'max_credits' and run_kwargs[key] is None: - run_kwargs[key] = 10 - elif key == 'meas_level' and run_kwargs[key] is None: - run_kwargs[key] = MeasLevel.CLASSIFIED - elif key == 'meas_return' and run_kwargs[key] is None: - run_kwargs[key] = MeasReturnType.AVERAGE - elif key == 'memory_slot_size' and run_kwargs[key] is None: - run_kwargs[key] = 100 - - return backend.run(experiments, **run_kwargs) +def _safe_submit_circuits(circuits: Union[QuantumCircuit, List[QuantumCircuit]], + backend: Union[Backend, BaseBackend], + qjob_config: Dict, + backend_options: Dict, + noise_config: Dict, + run_config: Dict) -> Tuple[BaseJob, str]: + # assure get job ids + while True: + try: + job = _run_circuits_on_backend(backend, + circuits, + backend_options=backend_options, + noise_config=noise_config, + run_config=run_config) + job_id = job.job_id() + break + except QiskitError as ex: + failure_warn = True + if is_ibmq_provider(backend): + try: + from qiskit.providers.ibmq import IBMQBackendJobLimitError + except ImportError as ex1: + raise MissingOptionalLibraryError( + libname='qiskit-ibmq-provider', + name='_safe_submit_circuits', + pip_install='pip install qiskit-ibmq-provider') from ex1 + if isinstance(ex, IBMQBackendJobLimitError): + + oldest_running = backend.jobs(limit=1, descending=False, + status=['QUEUED', 'VALIDATING', 'RUNNING']) + if oldest_running: + oldest_running = oldest_running[0] + logger.warning("Job limit reached, waiting for job %s to finish " + "before submitting the next one.", oldest_running.job_id()) + failure_warn = False # Don't issue a second warning. + try: + oldest_running.wait_for_final_state(timeout=qjob_config['timeout'], + wait=qjob_config['wait']) + except Exception: # pylint: disable=broad-except + # If the wait somehow fails or times out, we'll just re-try + # the job submit and see if it works now. + pass + if failure_warn: + logger.warning("FAILURE: Can not get job id, Resubmit the qobj to get job id. " + "Terra job error: %s ", ex) + except Exception as ex: # pylint: disable=broad-except + logger.warning("FAILURE: Can not get job id, Resubmit the qobj to get job id." + "Error: %s ", ex) + + return job, job_id + + +def _run_circuits_on_backend(backend: Union[Backend, BaseBackend], + circuits: Union[QuantumCircuit, List[QuantumCircuit]], + backend_options: Dict, + noise_config: Dict, + run_config: Dict) -> BaseJob: + """ run on backend """ + return backend.run(circuits, **backend_options, **noise_config, **run_config) From bf5c7dbf2c71496870f18a6f1b887ff645a5a670 Mon Sep 17 00:00:00 2001 From: Manoel Marques Date: Fri, 30 Apr 2021 14:01:28 -0400 Subject: [PATCH 4/6] add unit tests --- test/python/algorithms/test_backendv1.py | 84 ++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 test/python/algorithms/test_backendv1.py diff --git a/test/python/algorithms/test_backendv1.py b/test/python/algorithms/test_backendv1.py new file mode 100644 index 000000000000..a7f64973a0bd --- /dev/null +++ b/test/python/algorithms/test_backendv1.py @@ -0,0 +1,84 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021. +# +# 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 +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" Test Providers that support BackendV1 interface """ + +import unittest +from test.python.algorithms import QiskitAlgorithmsTestCase +from qiskit import QuantumCircuit +from qiskit.test.mock import FakeProvider +from qiskit.utils import QuantumInstance +from qiskit.algorithms import (Shor, VQE, + Grover, AmplificationProblem) +from qiskit.opflow import X, Z, I +from qiskit.algorithms.optimizers import SPSA +from qiskit.circuit.library import TwoLocal + + +class TestBackendV1(QiskitAlgorithmsTestCase): + """test BackendV1 interface """ + + def setUp(self): + super().setUp() + self._provider = FakeProvider() + self._qasm = self._provider.get_backend('fake_qasm_simulator') + self.seed = 50 + + def test_shor_factoring(self): + """ shor factoring test """ + n_v = 15 + factors = [3, 5] + qasm_simulator = QuantumInstance(self._qasm, + shots=1000, + seed_simulator=self.seed, + seed_transpiler=self.seed) + shor = Shor(quantum_instance=qasm_simulator) + result = shor.factor(N=n_v) + self.assertListEqual(result.factors[0], factors) + self.assertTrue(result.total_counts >= result.successful_counts) + + def test_vqe_qasm(self): + """Test the VQE on QASM simulator.""" + h2_op = -1.052373245772859 * (I ^ I) \ + + 0.39793742484318045 * (I ^ Z) \ + - 0.39793742484318045 * (Z ^ I) \ + - 0.01128010425623538 * (Z ^ Z) \ + + 0.18093119978423156 * (X ^ X) + optimizer = SPSA(maxiter=300, last_avg=5) + wavefunction = TwoLocal(rotation_blocks='ry', entanglement_blocks='cz') + qasm_simulator = QuantumInstance(self._qasm, + shots=1024, + seed_simulator=self.seed, + seed_transpiler=self.seed) + vqe = VQE(ansatz=wavefunction, + optimizer=optimizer, + max_evals_grouped=1, + quantum_instance=qasm_simulator) + + result = vqe.compute_minimum_eigenvalue(operator=h2_op) + self.assertAlmostEqual(result.eigenvalue.real, -1.86, delta=0.05) + + def test_run_circuit_oracle(self): + """Test execution with a quantum circuit oracle""" + oracle = QuantumCircuit(2) + oracle.cz(0, 1) + problem = AmplificationProblem(oracle, is_good_state=['11']) + qi = QuantumInstance(self._provider.get_backend('fake_yorktown'), + seed_simulator=12, + seed_transpiler=32) + grover = Grover(quantum_instance=qi) + result = grover.amplify(problem) + self.assertIn(result.top_measurement, ['11']) + + +if __name__ == '__main__': + unittest.main() From 19a2b74d87113022ba7936750c19be0b88c467b3 Mon Sep 17 00:00:00 2001 From: Manoel Marques Date: Fri, 30 Apr 2021 16:55:43 -0400 Subject: [PATCH 5/6] Update qiskit/utils/run_circuits.py Add check for providers that don't support it. Co-authored-by: Matthew Treinish --- qiskit/utils/run_circuits.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/utils/run_circuits.py b/qiskit/utils/run_circuits.py index 097bf3fac943..c9895be31361 100644 --- a/qiskit/utils/run_circuits.py +++ b/qiskit/utils/run_circuits.py @@ -439,7 +439,7 @@ def run_circuits(circuits: Union[QuantumCircuit, List[QuantumCircuit]], if job_callback is not None: job_callback(job_id, job_status, queue_position, job) break - if job_status == JobStatus.QUEUED: + if job_status == JobStatus.QUEUED and hasattr(job, queue_position): queue_position = job.queue_position() logger.info("Job id: %s is queued at position %s", job_id, queue_position) else: From 09a8c849bad08c696952b9899a4a0311c3a8ec9d Mon Sep 17 00:00:00 2001 From: Manoel Marques Date: Fri, 30 Apr 2021 17:03:13 -0400 Subject: [PATCH 6/6] Add error mitigation test --- test/python/algorithms/test_backendv1.py | 48 +++++++++++++++++++++++- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/test/python/algorithms/test_backendv1.py b/test/python/algorithms/test_backendv1.py index a7f64973a0bd..dd49d25f00ab 100644 --- a/test/python/algorithms/test_backendv1.py +++ b/test/python/algorithms/test_backendv1.py @@ -16,12 +16,12 @@ from test.python.algorithms import QiskitAlgorithmsTestCase from qiskit import QuantumCircuit from qiskit.test.mock import FakeProvider -from qiskit.utils import QuantumInstance +from qiskit.utils import QuantumInstance, algorithm_globals from qiskit.algorithms import (Shor, VQE, Grover, AmplificationProblem) from qiskit.opflow import X, Z, I from qiskit.algorithms.optimizers import SPSA -from qiskit.circuit.library import TwoLocal +from qiskit.circuit.library import TwoLocal, EfficientSU2 class TestBackendV1(QiskitAlgorithmsTestCase): @@ -79,6 +79,50 @@ def test_run_circuit_oracle(self): result = grover.amplify(problem) self.assertIn(result.top_measurement, ['11']) + def test_measurement_error_mitigation_with_vqe(self): + """ measurement error mitigation test with vqe """ + try: + from qiskit.ignis.mitigation.measurement import CompleteMeasFitter + from qiskit.providers.aer import noise + except ImportError as ex: + self.skipTest("Package doesn't appear to be installed. Error: '{}'".format(str(ex))) + return + + algorithm_globals.random_seed = 0 + + # 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 = self._qasm + + quantum_instance = QuantumInstance( + backend=backend, + seed_simulator=167, + seed_transpiler=167, + noise_model=noise_model, + measurement_error_mitigation_cls=CompleteMeasFitter + ) + + h2_hamiltonian = -1.052373245772859 * (I ^ I) \ + + 0.39793742484318045 * (I ^ Z) \ + - 0.39793742484318045 * (Z ^ I) \ + - 0.01128010425623538 * (Z ^ Z) \ + + 0.18093119978423156 * (X ^ X) + optimizer = SPSA(maxiter=200) + ansatz = EfficientSU2(2, reps=1) + + vqe = VQE( + ansatz=ansatz, + optimizer=optimizer, + quantum_instance=quantum_instance + ) + result = vqe.compute_minimum_eigenvalue(operator=h2_hamiltonian) + self.assertGreater(quantum_instance.time_taken, 0.) + quantum_instance.reset_execution_results() + self.assertAlmostEqual(result.eigenvalue.real, -1.86, delta=0.05) + if __name__ == '__main__': unittest.main()