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 (Qiskit/qiskit#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>
  • Loading branch information
3 people authored May 3, 2021
1 parent 85f3432 commit 830ecb9
Show file tree
Hide file tree
Showing 4 changed files with 354 additions and 44 deletions.
11 changes: 9 additions & 2 deletions qiskit_algorithms/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_algorithms/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_algorithms/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 830ecb9

Please sign in to comment.