diff --git a/qiskit/aqua/quantum_instance.py b/qiskit/aqua/quantum_instance.py index 4fa38ee32e3f..87f04b88adb9 100644 --- a/qiskit/aqua/quantum_instance.py +++ b/qiskit/aqua/quantum_instance.py @@ -18,17 +18,20 @@ import logging import time import os +import warnings from qiskit.assembler.run_config import RunConfig +from qiskit import compiler from .aqua_error import AquaError -from .utils import CircuitCache from .utils.backend_utils import (is_ibmq_provider, + is_aer_provider, is_statevector_backend, is_simulator_backend, is_local_backend, is_aer_qasm, support_backend_options) +from .utils.circuit_utils import summarize_circuits logger = logging.getLogger(__name__) @@ -199,23 +202,25 @@ def __init__(self, backend, "and re-build it after that.", self._cals_matrix_refresh_period) # setup others - # TODO: allow an external way to overwrite the setting circuit cache temporally - # when either setup cache via environment variable or constructor, - # the optimization level will be change to 0 to assure cache works. - circuit_cache = None if os.environ.get('QISKIT_AQUA_CIRCUIT_CACHE', False) or circuit_caching: - if optimization_level is None or optimization_level == 0: - self._compile_config['optimization_level'] = 0 - skip_qobj_deepcopy = True if os.environ.get('QISKIT_AQUA_CIRCUIT_CACHE', False) \ - else skip_qobj_deepcopy - circuit_cache = CircuitCache(skip_qobj_deepcopy=skip_qobj_deepcopy, - cache_file=cache_file) - else: - logger.warning('CircuitCache cannot be used with optimization_level %s. ' - 'Caching has been disabled. To re-enable, please set ' - 'optimization_level = 0 or None.', optimization_level) + warnings.warn("circuit_caching will be removed at Qiskit Aqua 0.7+, " + "this setting will be ignored. " + "On the other hand, Qiskit Aqua does support " + "parameterized circuits for adaptive algorithms (e.g. VQE and VQC) " + "to avoid for compiling the circuit with the same topology " + "multiple times", + UserWarning) + if skip_qobj_deepcopy: + warnings.warn("skip_qobj_deepcopy was used along with circuit_caching, and since " + "circuit_cache will be removed at Qiskit Aqua 0.7+, " + "this setting will be ignored, too.", + UserWarning) + if cache_file: + warnings.warn("cache_file was used along with circuit_caching, and since " + "circuit_cache will be removed at Qiskit Aqua 0.7+, " + "this setting will be ignored, too.", + UserWarning) - self._circuit_cache = circuit_cache if is_ibmq_provider(self._backend): if skip_qobj_validation: logger.warning("The skip Qobj validation does not work " @@ -242,28 +247,57 @@ def __str__(self): return info - def execute(self, circuits, **kwargs): + def transpile(self, circuits): + """ + A wrapper to transpile circuits to allow algorithm access the transpiled circuits. + Args: + circuits (QuantumCircuit or list[QuantumCircuit]): circuits to transpile + Returns: + list[QuantumCircuit]: the transpiled circuits, it is always a list even though + the length is one. + """ + transpiled_circuits = compiler.transpile(circuits, self._backend, **self._backend_config, + **self._compile_config) + if not isinstance(transpiled_circuits, list): + transpiled_circuits = [transpiled_circuits] + + if logger.isEnabledFor(logging.DEBUG) and self._circuit_summary: + logger.debug("==== Before transpiler ====") + logger.debug(summarize_circuits(circuits)) + if transpiled_circuits is not None: + logger.debug("==== After transpiler ====") + logger.debug(summarize_circuits(transpiled_circuits)) + + return transpiled_circuits + + def execute(self, circuits, had_transpiled=False, **kwargs): """ A wrapper to interface with quantum backend. Args: circuits (QuantumCircuit or list[QuantumCircuit]): circuits to execute - kwargs (list): additional parameters + had_transpiled (bool, optional): whether or not circuits had been transpiled + kwargs (dict, optional): additional parameters Returns: Result: Result object + + TODO: Maybe we can combine the circuits for the main ones and calibration circuits before + assembling to the qobj. """ from .utils.run_circuits import (run_qobj, - compile_circuits) + maybe_add_aer_expectation_instruction) from .utils.measurement_error_mitigation import (get_measured_qubits_from_qobj, build_measurement_error_mitigation_qobj) + # maybe compile + if not had_transpiled: + circuits = self.transpile(circuits) - qobj = compile_circuits(circuits, self._backend, self._backend_config, - self._compile_config, self._run_config, - show_circuit_summary=self._circuit_summary, - circuit_cache=self._circuit_cache, - **kwargs) + # assemble + qobj = compiler.assemble(circuits, **self._run_config.to_dict()) + if is_aer_provider(self._backend): + qobj = maybe_add_aer_expectation_instruction(qobj, kwargs) if self._meas_error_mitigation_cls is not None: qubit_index = get_measured_qubits_from_qobj(qobj) @@ -319,6 +353,7 @@ def execute(self, circuits, **kwargs): self._backend_options, self._noise_config, self._skip_qobj_validation, self._job_callback) 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, @@ -483,16 +518,6 @@ def is_local(self): """Return True if backend is a local backend.""" return is_local_backend(self._backend) - @property - def circuit_cache(self): - """ returns circuit cache """ - return self._circuit_cache - - @property - def has_circuit_caching(self): - """ checks if it has circuit cache """ - return self._circuit_cache is not None - @property def skip_qobj_validation(self): """ checks if skip qobj validation """ @@ -524,7 +549,7 @@ def cals_matrix(self, qubit_index=None): Get the stored calibration matrices and its timestamp. Args: - qubit_index (int): the qubit index of corresponding calibration matrix. + qubit_index (list[int]): the qubit index of corresponding calibration matrix. If None, return all stored calibration matrices. Returns: diff --git a/qiskit/aqua/utils/measurement_error_mitigation.py b/qiskit/aqua/utils/measurement_error_mitigation.py index 87b36f21ecc5..4a094f5f2e36 100644 --- a/qiskit/aqua/utils/measurement_error_mitigation.py +++ b/qiskit/aqua/utils/measurement_error_mitigation.py @@ -16,10 +16,10 @@ import logging +from qiskit import compiler from qiskit.ignis.mitigation.measurement import (complete_meas_cal, CompleteMeasFitter, TensoredMeasFitter) -from .run_circuits import compile_circuits from ..aqua_error import AquaError logger = logging.getLogger(__name__) @@ -121,7 +121,7 @@ def build_measurement_error_mitigation_qobj(qubit_list, fitter_cls, backend, else: raise AquaError("Unknown fitter {}".format(fitter_cls)) - cals_qobj = compile_circuits(meas_calibs_circuits, - backend, backend_config, compile_config, run_config) - + t_meas_calibs_circuits = compiler.transpile(meas_calibs_circuits, backend, + **backend_config, **compile_config) + cals_qobj = compiler.assemble(t_meas_calibs_circuits, backend, **run_config.to_dict()) return cals_qobj, state_labels, circlabel diff --git a/qiskit/aqua/utils/run_circuits.py b/qiskit/aqua/utils/run_circuits.py index 366d4993700d..faa3996ea567 100644 --- a/qiskit/aqua/utils/run_circuits.py +++ b/qiskit/aqua/utils/run_circuits.py @@ -22,13 +22,11 @@ import uuid import numpy as np -from qiskit import compiler from qiskit.providers import BaseBackend, JobStatus, JobError from qiskit.providers.jobstatus import JOB_FINAL_STATES from qiskit.providers.basicaer import BasicAerJob from qiskit.qobj import QasmQobj from qiskit.aqua.aqua_error import AquaError -from qiskit.aqua.utils import summarize_circuits from qiskit.aqua.utils.backend_utils import (is_aer_provider, is_basicaer_provider, is_simulator_backend, @@ -60,21 +58,6 @@ def find_regs_by_name(circuit, name, qreg=True): return found_reg -def _avoid_empty_circuits(circuits): - new_circuits = [] - for qc in circuits: - if not qc: - tmp_q = None - for q in qc.qregs: - tmp_q = q - break - if tmp_q is None: - raise NameError("A QASM without any quantum register is invalid.") - qc.iden(tmp_q[0]) - new_circuits.append(qc) - return new_circuits - - def _combine_result_objects(results): """Temporary helper function. @@ -93,7 +76,15 @@ def _combine_result_objects(results): # pylint: disable=invalid-name -def _maybe_add_aer_expectation_instruction(qobj, options): +def maybe_add_aer_expectation_instruction(qobj, options): + """ + Add aer expectation instruction if `expectation` in the options. + Args: + qobj (QasmQobj): qobj + options (dict): the setting for aer expectation instruction + Returns: + QasmQobj: a mutated qobj with aer expectation instruction inserted + """ if 'expectation' in options: from qiskit.providers.aer.utils.qobj_utils \ import snapshot_instr, append_instr, get_instr_pos @@ -119,14 +110,6 @@ def _maybe_add_aer_expectation_instruction(qobj, options): return qobj -def _compile_wrapper(circuits, backend, backend_config, compile_config, run_config): - transpiled_circuits = compiler.transpile(circuits, backend, **backend_config, **compile_config) - if not isinstance(transpiled_circuits, list): - transpiled_circuits = [transpiled_circuits] - qobj = compiler.assemble(transpiled_circuits, **run_config.to_dict()) - return qobj, transpiled_circuits - - def _split_qobj_to_qobjs(qobj, chunk_size): qobjs = [] num_chunks = int(np.ceil(len(qobj.experiments) / chunk_size)) @@ -147,119 +130,6 @@ def _split_qobj_to_qobjs(qobj, chunk_size): return qobjs -def compile_circuits(circuits, backend, backend_config=None, compile_config=None, run_config=None, - show_circuit_summary=False, circuit_cache=None, **kwargs): - """ - An execution wrapper with Qiskit-Terra, with job auto recover capability. - - The autorecovery feature is only applied for non-simulator backend. - This wrapper will try to get the result no matter how long it costs. - - Args: - circuits (QuantumCircuit or list[QuantumCircuit]): circuits to execute - backend (BaseBackend): backend instance - backend_config (dict, optional): configuration for backend - compile_config (dict, optional): configuration for compilation - run_config (RunConfig, optional): configuration for running a circuit - show_circuit_summary (bool, optional): showing the summary of submitted circuits. - circuit_cache (CircuitCache, optional): A CircuitCache to use - when calling compile_and_run_circuits - kwargs (optional): special aer instructions to evaluation the expectation of a hamiltonian - - Returns: - QasmObj: compiled qobj. - - Raises: - ValueError: backend type is wrong or not given - ValueError: no circuit in the circuits - - """ - backend_config = backend_config or {} - compile_config = compile_config or {} - run_config = run_config or {} - - if backend is None or not isinstance(backend, BaseBackend): - raise ValueError('Backend is missing or not an instance of BaseBackend') - - if not isinstance(circuits, list): - circuits = [circuits] - - if not circuits: - raise ValueError("The input circuit is empty.") - - if is_simulator_backend(backend): - circuits = _avoid_empty_circuits(circuits) - - if circuit_cache is not None and circuit_cache.try_reusing_qobjs: - # Check if all circuits are the same length. - # If not, don't try to use the same qobj.experiment for all of them. - if len({len(circ.data) for circ in circuits}) > 1: - circuit_cache.try_reusing_qobjs = False - else: # Try setting up the reusable qobj - # Compile and cache first circuit if cache is empty. - # The load method will try to reuse it - if circuit_cache.qobjs is None: - qobj, _ = _compile_wrapper([circuits[0]], backend, backend_config, - compile_config, run_config) - - if is_aer_provider(backend): - qobj = _maybe_add_aer_expectation_instruction(qobj, kwargs) - circuit_cache.cache_circuit(qobj, [circuits[0]], 0) - - transpiled_circuits = None - if circuit_cache is not None and circuit_cache.misses < circuit_cache.allowed_misses: - try: - if circuit_cache.cache_transpiled_circuits: - transpiled_circuits = compiler.transpile(circuits, backend, **backend_config, - **compile_config) - qobj = circuit_cache.load_qobj_from_cache(transpiled_circuits, - 0, run_config=run_config) - else: - qobj = circuit_cache.load_qobj_from_cache(circuits, 0, run_config=run_config) - - if is_aer_provider(backend): - qobj = _maybe_add_aer_expectation_instruction(qobj, kwargs) - # cache miss, fail gracefully - except (TypeError, IndexError, FileNotFoundError, EOFError, AquaError, AttributeError) as e: - circuit_cache.try_reusing_qobjs = False # Reusing Qobj didn't work - if circuit_cache.qobjs: - logger.info('Circuit cache miss, recompiling. Cache miss reason: %s', repr(e)) - circuit_cache.misses += 1 - else: - logger.info('Circuit cache is empty, compiling from scratch.') - circuit_cache.clear_cache() - - qobj, transpiled_circuits = _compile_wrapper(circuits, backend, backend_config, - compile_config, run_config) - if is_aer_provider(backend): - qobj = _maybe_add_aer_expectation_instruction(qobj, kwargs) - try: - circuit_cache.cache_circuit(qobj, circuits, 0) - except (TypeError, IndexError, AquaError, AttributeError, KeyError): - try: - circuit_cache.cache_transpiled_circuits = True - circuit_cache.cache_circuit(qobj, transpiled_circuits, 0) - except (TypeError, IndexError, AquaError, AttributeError, KeyError) as e: - logger.info('Circuit could not be cached for reason: %s', repr(e)) - logger.info('Transpilation may be too aggressive. Try skipping transpiler.') - - else: - qobj, transpiled_circuits = _compile_wrapper(circuits, backend, - backend_config, compile_config, - run_config) - if is_aer_provider(backend): - qobj = _maybe_add_aer_expectation_instruction(qobj, kwargs) - - if logger.isEnabledFor(logging.DEBUG) and show_circuit_summary: - logger.debug("==== Before transpiler ====") - logger.debug(summarize_circuits(circuits)) - if transpiled_circuits is not None: - logger.debug("==== After transpiler ====") - logger.debug(summarize_circuits(transpiled_circuits)) - - return qobj - - def _safe_submit_qobj(qobj, backend, backend_options, noise_config, skip_qobj_validation): # assure get job ids while True: @@ -424,45 +294,6 @@ def run_qobj(qobj, backend, qjob_config=None, backend_options=None, return result -def compile_and_run_circuits(circuits, backend, backend_config=None, - compile_config=None, run_config=None, - qjob_config=None, backend_options=None, - noise_config=None, show_circuit_summary=False, - circuit_cache=None, skip_qobj_validation=False, **kwargs): - """ - An execution wrapper with Qiskit-Terra, with job auto recover capability. - - The autorecovery feature is only applied for non-simulator backend. - This wrapper will try to get the result no matter how long it costs. - - Args: - circuits (Union(QuantumCircuit,list[QuantumCircuit])): circuits to execute - backend (BaseBackend): backend instance - backend_config (Optional(dict)): configuration for backend - compile_config (Optional(dict)): configuration for compilation - run_config (Optional(RunConfig)): configuration for running a circuit - qjob_config (Optional(dict)): configuration for quantum job object - backend_options (Optional(dict)): configuration for simulator - noise_config (Optional(dict)): configuration for noise model - show_circuit_summary (Optional(bool)): showing the summary of submitted circuits. - circuit_cache (Optional(CircuitCache)): A CircuitCache to use when - calling compile_and_run_circuits - skip_qobj_validation (Optional(bool)): Bypass Qobj validation to decrease submission time - kwargs (dict): additional parameters - - Returns: - Result: Result object - - Raises: - AquaError: Any error except for JobError raised by Qiskit Terra - """ - qobjs = compile_circuits(circuits, backend, backend_config, compile_config, run_config, - show_circuit_summary, circuit_cache, **kwargs) - result = run_qobj(qobjs, backend, qjob_config, - backend_options, noise_config, skip_qobj_validation) - return result - - # skip_qobj_validation = True does what backend.run # and aerjob.submit do, but without qobj validation. def run_on_backend(backend, qobj, backend_options=None,