Skip to content

Commit

Permalink
Add support on QuantumInstance/run_circuits for Backends that don't h…
Browse files Browse the repository at this point in the history
…andle QasmQobj (backport #6299) (#6343)

* Add support on QuantumInstance/run_circuits for Backends that don't handle QasmQobj (#6299)

* Support BackendV1

* Add error mitigation logic.
Co-authored-by: Matthew Treinish <[email protected]>

* Fix run_circuits calls

* add unit tests

* Update qiskit/utils/run_circuits.py

Add check for providers that don't support it.

Co-authored-by: Matthew Treinish <[email protected]>

* Add error mitigation test

Co-authored-by: Matthew Treinish <[email protected]>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
(cherry picked from commit a0ed7ff)

* Remove tests that depend on BackendV1 fake backends

2 of the tests added in #6299 depend on the fake backends in
qiskit.test.mock being strict BackendV1 backends (that reject qobj) and
also having other aspects of the interface implemented (mainly run
kwargs). However the change which updated the fake backends to use
BackendV1 (#6286) is not backportable as it's an API change, so these
tests are runnable in stable/0.17. This commit just removes these tests
because even if we could get them running without #6286 we wouldn't be
actually testing the v1 interface (which is their purpose).

* Fix lint

Co-authored-by: Manoel Marques <[email protected]>
Co-authored-by: Matthew Treinish <[email protected]>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
3 people authored May 3, 2021
1 parent 7a212fc commit e73b3c6
Show file tree
Hide file tree
Showing 5 changed files with 424 additions and 44 deletions.
11 changes: 9 additions & 2 deletions qiskit/utils/backend_utils.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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


Expand Down
57 changes: 54 additions & 3 deletions qiskit/utils/measurement_error_mitigation.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -17,6 +17,8 @@
from qiskit import compiler
from ..exceptions import QiskitError, MissingOptionalLibraryError

# pylint: disable=invalid-name


def get_measured_qubits(transpiled_circuits):
"""
Expand All @@ -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] = []
Expand Down Expand Up @@ -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):
Expand Down
149 changes: 110 additions & 39 deletions qiskit/utils/quantum_instance.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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,
Expand Down Expand Up @@ -295,20 +296,31 @@ 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)
(get_measured_qubits_from_qobj, get_measured_qubits,
build_measurement_error_mitigation_circuits,
build_measurement_error_mitigation_qobj)

# maybe compile
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
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 = \
Expand Down Expand Up @@ -337,39 +349,84 @@ 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,
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,
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,
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

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 = \
Expand All @@ -380,7 +437,14 @@ 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,
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)
self._time_taken += result.time_taken
Expand All @@ -406,7 +470,14 @@ 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,
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)
self._time_taken += result.time_taken
Expand Down
Loading

0 comments on commit e73b3c6

Please sign in to comment.