diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 27ac7788e58d..2abc2048ffe7 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -705,8 +705,9 @@ stages: git clone https://github.com/Qiskit/qiskit-tutorials --depth=1 python -m pip install --upgrade pip pip install -U -r requirements.txt -r requirements-dev.txt -c constraints.txt - pip install -U -c constraints.txt -e . - pip install -U "qiskit-ibmq-provider" "qiskit-aer" "z3-solver" "git+https://github.com/Qiskit/qiskit-ignis" "qiskit-aqua" "pyscf<1.7.4" "matplotlib<3.3.0" sphinx nbsphinx sphinx_rtd_theme cvxpy -c constraints.txt + pip install -c constraints.txt -e . + # TODO: Remove aqua and ignis from source after next aqua release + pip install "qiskit-ibmq-provider" "qiskit-aer" "z3-solver" "git+https://github.com/Qiskit/qiskit-ignis" "git+https://github.com/Qiskit/qiskit-aqua" "pyscf<1.7.4" "matplotlib<3.3.0" sphinx nbsphinx sphinx_rtd_theme cvxpy -c constraints.txt python setup.py build_ext --inplace sudo apt install -y graphviz pandoc pip check diff --git a/qiskit/providers/basicaer/basicaerjob.py b/qiskit/providers/basicaer/basicaerjob.py index 98bbf6124944..88811515b861 100644 --- a/qiskit/providers/basicaer/basicaerjob.py +++ b/qiskit/providers/basicaer/basicaerjob.py @@ -10,51 +10,25 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""This module implements the job class used by Basic Aer Provider.""" - -from concurrent import futures -import sys -import functools - -from qiskit.providers import BaseJob, JobStatus, JobError - +# pylint: disable=abstract-method -def requires_submit(func): - """ - Decorator to ensure that a submit has been performed before - calling the method. - Args: - func (callable): test function to be decorated. +"""This module implements the job class used by Basic Aer Provider.""" - Returns: - callable: the decorated function. - """ - @functools.wraps(func) - def _wrapper(self, *args, **kwargs): - if self._future is None: - raise JobError("Job not submitted yet!. You have to .submit() first!") - return func(self, *args, **kwargs) - return _wrapper +import warnings +from qiskit.providers import JobStatus +from qiskit.providers.job import JobV1 -class BasicAerJob(BaseJob): - """BasicAerJob class. - Attributes: - _executor (futures.Executor): executor to handle asynchronous jobs - """ +class BasicAerJob(JobV1): + """BasicAerJob class.""" - if sys.platform in ['darwin', 'win32']: - _executor = futures.ThreadPoolExecutor() - else: - _executor = futures.ProcessPoolExecutor() + _async = False - def __init__(self, backend, job_id, fn, qobj): + def __init__(self, backend, job_id, result): super().__init__(backend, job_id) - self._fn = fn - self._qobj = qobj - self._future = None + self._result = result def submit(self): """Submit the job to the backend for execution. @@ -62,70 +36,31 @@ def submit(self): Raises: JobError: if trying to re-submit the job. """ - if self._future is not None: - raise JobError("We have already submitted the job!") - - self._future = self._executor.submit(self._fn, self._job_id, self._qobj) + return - @requires_submit def result(self, timeout=None): # pylint: disable=arguments-differ - """Get job result. The behavior is the same as the underlying - concurrent Future objects, - - https://docs.python.org/3/library/concurrent.futures.html#future-objects - - Args: - timeout (float): number of seconds to wait for results. + """Get job result . Returns: qiskit.Result: Result object - - Raises: - concurrent.futures.TimeoutError: if timeout occurred. - concurrent.futures.CancelledError: if job cancelled before completed. """ - return self._future.result(timeout=timeout) + if timeout is not None: + warnings.warn("The timeout kwarg doesn't have any meaning with " + "BasicAer because execution is synchronous and the " + "result already exists when run() returns.", + UserWarning) - @requires_submit - def cancel(self): - return self._future.cancel() + return self._result - @requires_submit def status(self): """Gets the status of the job by querying the Python's future Returns: qiskit.providers.JobStatus: The current JobStatus - - Raises: - JobError: If the future is in unexpected state - concurrent.futures.TimeoutError: if timeout occurred. """ - # The order is important here - if self._future.running(): - _status = JobStatus.RUNNING - elif self._future.cancelled(): - _status = JobStatus.CANCELLED - elif self._future.done(): - _status = JobStatus.DONE if self._future.exception() is None else JobStatus.ERROR - else: - # Note: There is an undocumented Future state: PENDING, that seems to show up when - # the job is enqueued, waiting for someone to pick it up. We need to deal with this - # state but there's no public API for it, so we are assuming that if the job is not - # in any of the previous states, is PENDING, ergo INITIALIZING for us. - _status = JobStatus.INITIALIZING - - return _status + return JobStatus.DONE def backend(self): """Return the instance of the backend used for this job.""" return self._backend - - def qobj(self): - """Return the Qobj submitted for this job. - - Returns: - Qobj: the Qobj submitted for this job. - """ - return self._qobj diff --git a/qiskit/providers/basicaer/basicaerprovider.py b/qiskit/providers/basicaer/basicaerprovider.py index 8bc33fe13dba..b4d4de84103f 100644 --- a/qiskit/providers/basicaer/basicaerprovider.py +++ b/qiskit/providers/basicaer/basicaerprovider.py @@ -17,7 +17,7 @@ import logging from qiskit.exceptions import QiskitError -from qiskit.providers import BaseProvider +from qiskit.providers.provider import ProviderV1 from qiskit.providers.exceptions import QiskitBackendNotFoundError from qiskit.providers.providerutils import resolve_backend_name, filter_backends @@ -35,11 +35,11 @@ ] -class BasicAerProvider(BaseProvider): +class BasicAerProvider(ProviderV1): """Provider for Basic Aer backends.""" - def __init__(self, *args, **kwargs): - super().__init__(args, kwargs) + def __init__(self): + super().__init__() # Populate the list of Basic Aer backends. self._backends = self._verify_backends() @@ -109,14 +109,9 @@ def _verify_backends(self): """ ret = OrderedDict() for backend_cls in SIMULATORS: - try: - backend_instance = self._get_backend_instance(backend_cls) - backend_name = backend_instance.name() - ret[backend_name] = backend_instance - except QiskitError as err: - # Ignore backends that could not be initialized. - logger.info('Basic Aer backend %s is not available: %s', - backend_cls, str(err)) + backend_instance = self._get_backend_instance(backend_cls) + backend_name = backend_instance.name() + ret[backend_name] = backend_instance return ret def _get_backend_instance(self, backend_cls): diff --git a/qiskit/providers/basicaer/qasm_simulator.py b/qiskit/providers/basicaer/qasm_simulator.py index 99fae8ee4b50..f9e58c39f8fa 100644 --- a/qiskit/providers/basicaer/qasm_simulator.py +++ b/qiskit/providers/basicaer/qasm_simulator.py @@ -31,15 +31,18 @@ import uuid import time import logging +import warnings from math import log2 from collections import Counter import numpy as np +from qiskit.circuit.quantumcircuit import QuantumCircuit from qiskit.utils.multiprocessing import local_hardware_info from qiskit.providers.models import QasmBackendConfiguration from qiskit.result import Result -from qiskit.providers import BaseBackend +from qiskit.providers.backend import BackendV1 +from qiskit.providers.options import Options from qiskit.providers.basicaer.basicaerjob import BasicAerJob from .exceptions import BasicAerError from .basicaertools import single_gate_matrix @@ -50,7 +53,7 @@ logger = logging.getLogger(__name__) -class QasmSimulatorPy(BaseBackend): +class QasmSimulatorPy(BackendV1): """Python implementation of a qasm simulator.""" MAX_QUBITS_MEMORY = int(log2(local_hardware_info()['memory'] * (1024 ** 3) / 16)) @@ -127,11 +130,12 @@ class QasmSimulatorPy(BaseBackend): # This should be set to True for the statevector simulator SHOW_FINAL_STATE = False - def __init__(self, configuration=None, provider=None): - super().__init__(configuration=( - configuration or QasmBackendConfiguration.from_dict(self.DEFAULT_CONFIGURATION)), - provider=provider) - + def __init__(self, configuration=None, provider=None, **fields): + super().__init__( + configuration=(configuration or QasmBackendConfiguration.from_dict( + self.DEFAULT_CONFIGURATION)), + provider=provider, + **fields) # Define attributes in __init__. self._local_random = np.random.RandomState() self._classical_memory = 0 @@ -141,12 +145,19 @@ def __init__(self, configuration=None, provider=None): self._number_of_qubits = 0 self._shots = 0 self._memory = False - self._initial_statevector = self.DEFAULT_OPTIONS["initial_statevector"] - self._chop_threshold = self.DEFAULT_OPTIONS["chop_threshold"] + self._initial_statevector = self.options.get('initial_statevector') + self._chop_threshold = self.options.get("chop_threashold") self._qobj_config = None # TEMP self._sample_measure = False + @classmethod + def _default_options(cls): + return Options(shots=1024, memory=False, + initial_statevector=None, chop_threshold=1e-15, + allow_sample_measuring=True, seed_simulator=None, + parameter_binds=None) + def _add_unitary(self, gate, qubits): """Apply an N-qubit unitary matrix. @@ -292,10 +303,10 @@ def _validate_initial_statevector(self): def _set_options(self, qobj_config=None, backend_options=None): """Set the backend options for all experiments in a qobj""" # Reset default options - self._initial_statevector = self.DEFAULT_OPTIONS["initial_statevector"] - self._chop_threshold = self.DEFAULT_OPTIONS["chop_threshold"] - if backend_options is None: - backend_options = {} + self._initial_statevector = self.options.get("initial_statevector") + self._chop_threshold = self.options.get("chop_threshold") + if 'backend_options' in backend_options and backend_options['backend_options']: + backend_options = backend_options['backend_options'] # Check for custom initial statevector in backend_options first, # then config second @@ -377,7 +388,7 @@ def _validate_measure_sampling(self, experiment): # measure sampling is allowed self._sample_measure = True - def run(self, qobj, backend_options=None): + def run(self, qobj, **backend_options): """Run qobj asynchronously. Args: @@ -402,11 +413,28 @@ def run(self, qobj, backend_options=None): "initial_statevector": np.array([1, 0, 0, 1j]) / np.sqrt(2), } """ - self._set_options(qobj_config=qobj.config, + if isinstance(qobj, (QuantumCircuit, list)): + from qiskit.compiler import assemble + out_options = {} + for key in backend_options: + if not hasattr(self.options, key): + warnings.warn( + "Option %s is not used by this backend" % key, + UserWarning, stacklevel=2) + else: + out_options[key] = backend_options[key] + qobj = assemble(qobj, self, **out_options) + qobj_options = qobj.config + else: + warnings.warn('Using a qobj for run() is deprecated and will be ' + 'removed in a future release.', + PendingDeprecationWarning, + stacklevel=2) + qobj_options = qobj.config + self._set_options(qobj_config=qobj_options, backend_options=backend_options) job_id = str(uuid.uuid4()) - job = BasicAerJob(self, job_id, self._run_job, qobj) - job.submit() + job = BasicAerJob(self, job_id, self._run_job(job_id, qobj)) return job def _run_job(self, job_id, qobj): diff --git a/qiskit/providers/basicaer/statevector_simulator.py b/qiskit/providers/basicaer/statevector_simulator.py index 92c3d5bc05a2..a7af1273495b 100644 --- a/qiskit/providers/basicaer/statevector_simulator.py +++ b/qiskit/providers/basicaer/statevector_simulator.py @@ -104,12 +104,12 @@ class StatevectorSimulatorPy(QasmSimulatorPy): # Override base class value to return the final state vector SHOW_FINAL_STATE = True - def __init__(self, configuration=None, provider=None): + def __init__(self, configuration=None, provider=None, **fields): super().__init__(configuration=( configuration or QasmBackendConfiguration.from_dict(self.DEFAULT_CONFIGURATION)), - provider=provider) + provider=provider, **fields) - def run(self, qobj, backend_options=None): + def run(self, qobj, **backend_options): """Run qobj asynchronously. Args: diff --git a/qiskit/providers/basicaer/unitary_simulator.py b/qiskit/providers/basicaer/unitary_simulator.py index 4e2803a15494..04ab0c20a1a0 100644 --- a/qiskit/providers/basicaer/unitary_simulator.py +++ b/qiskit/providers/basicaer/unitary_simulator.py @@ -30,10 +30,15 @@ import uuid import time from math import log2, sqrt +import warnings + import numpy as np + +from qiskit.circuit.quantumcircuit import QuantumCircuit from qiskit.utils.multiprocessing import local_hardware_info from qiskit.providers.models import QasmBackendConfiguration -from qiskit.providers import BaseBackend +from qiskit.providers.backend import BackendV1 +from qiskit.providers.options import Options from qiskit.providers.basicaer.basicaerjob import BasicAerJob from qiskit.result import Result from .exceptions import BasicAerError @@ -49,7 +54,7 @@ # does not show up -class UnitarySimulatorPy(BaseBackend): +class UnitarySimulatorPy(BackendV1): """Python implementation of a unitary simulator.""" MAX_QUBITS_MEMORY = int(log2(sqrt(local_hardware_info()['memory'] * (1024 ** 3) / 16))) @@ -122,17 +127,25 @@ class UnitarySimulatorPy(BaseBackend): "chop_threshold": 1e-15 } - def __init__(self, configuration=None, provider=None): - super().__init__(configuration=( - configuration or QasmBackendConfiguration.from_dict(self.DEFAULT_CONFIGURATION)), - provider=provider) + def __init__(self, configuration=None, provider=None, **fields): + super().__init__( + configuration=(configuration or QasmBackendConfiguration.from_dict( + self.DEFAULT_CONFIGURATION)), + provider=provider, + **fields) # Define attributes inside __init__. self._unitary = None self._number_of_qubits = 0 self._initial_unitary = None - self._chop_threshold = 1e-15 self._global_phase = 0 + self._chop_threshold = self.options.get('chop_threshold') + + @classmethod + def _default_options(cls): + return Options(shots=1, + initial_unitary=None, chop_threshold=1e-15, + parameter_binds=None) def _add_unitary(self, gate, qubits): """Apply an N-qubit unitary matrix. @@ -168,10 +181,10 @@ def _validate_initial_unitary(self): def _set_options(self, qobj_config=None, backend_options=None): """Set the backend options for all experiments in a qobj""" # Reset default options - self._initial_unitary = self.DEFAULT_OPTIONS["initial_unitary"] - self._chop_threshold = self.DEFAULT_OPTIONS["chop_threshold"] - if backend_options is None: - backend_options = {} + self._initial_unitary = self.options.get("initial_unitary") + self._chop_threshold = self.options.get("chop_threshold") + if 'backend_options' in backend_options: + backend_options = backend_options['backend_options'] # Check for custom initial statevector in backend_options first, # then config second @@ -222,7 +235,7 @@ def _get_unitary(self): unitary[abs(unitary) < self._chop_threshold] = 0.0 return unitary - def run(self, qobj, backend_options=None): + def run(self, qobj, **backend_options): """Run qobj asynchronously. Args: @@ -257,11 +270,24 @@ def run(self, qobj, backend_options=None): "chop_threshold": 1e-15 } """ - self._set_options(qobj_config=qobj.config, + if isinstance(qobj, (QuantumCircuit, list)): + from qiskit.compiler import assemble + out_options = {} + for key in backend_options: + if not hasattr(self.options, key): + warnings.warn( + "Option %s is not used by this backend" % key, + UserWarning, stacklevel=2) + else: + out_options[key] = backend_options[key] + qobj = assemble(qobj, self, **out_options) + qobj_options = qobj.config + else: + qobj_options = None + self._set_options(qobj_config=qobj_options, backend_options=backend_options) job_id = str(uuid.uuid4()) - job = BasicAerJob(self, job_id, self._run_job, qobj) - job.submit() + job = BasicAerJob(self, job_id, self._run_job(job_id, qobj)) return job def _run_job(self, job_id, qobj): diff --git a/qiskit/utils/run_circuits.py b/qiskit/utils/run_circuits.py index f91c28fd3fdb..a33c895cdc88 100644 --- a/qiskit/utils/run_circuits.py +++ b/qiskit/utils/run_circuits.py @@ -24,7 +24,6 @@ from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister from qiskit.providers import Backend, BaseBackend, JobStatus, JobError, BaseJob from qiskit.providers.jobstatus import JOB_FINAL_STATES -from qiskit.providers.basicaer import BasicAerJob from qiskit.result import Result from qiskit.qobj import QasmQobj from ..exceptions import QiskitError, MissingOptionalLibraryError @@ -372,10 +371,7 @@ def run_on_backend(backend: Union[Backend, BaseBackend], qobj.config.noise_model = noise_config['noise_model'] job = backend.run(qobj, validate=False) elif is_basicaer_provider(backend): - job_id = str(uuid.uuid4()) - backend._set_options(qobj_config=qobj.config, **backend_options) - job = BasicAerJob(backend, job_id, backend._run_job, qobj) - job._future = job._executor.submit(job._fn, job._job_id, job._qobj) + job = backend.run(qobj, **backend_options) else: logger.info( "Can't skip qobj validation for the %s provider.", diff --git a/releasenotes/notes/basicaer-new-provider-ea7cf756df231c2b.yaml b/releasenotes/notes/basicaer-new-provider-ea7cf756df231c2b.yaml new file mode 100644 index 000000000000..d1ffbac91ea0 --- /dev/null +++ b/releasenotes/notes/basicaer-new-provider-ea7cf756df231c2b.yaml @@ -0,0 +1,38 @@ +--- +features: + - | + The :meth:`~qiskit.providers.basicaer.QasmSimulatorPy.run` method for the + :class:`~qiskit.providers.basicaer.QasmSimulatorPy`, + :class:`~qiskit.providers.basicaer.StatevectorSimulatorPy`, and + :class:`~qiskit.providers.basicaer.UnitarySimulatorPy` backends now takes a + :class:`~qiskit.circuit.QuantumCircuit` (or a list of + :class:`~qiskit.circuit.QuantumCircuit` objects) as its input. + The previous :class:`~qiskit.qobj.QasmQobj` object is still supported for + now, but will be deprecated in a future release. + + For an example of how to use this see:: + + from qiskit import transpile, QuantumCircuit + + from qiskit.providers.basicaer import BasicAer + + backend = BasicAer.get_backend('qasm_simulator') + + circuit = QuantumCircuit(2) + qc.h(0) + qc.cx(0, 1) + qc.measure_all() + + tqc = transpile(circuit, backend) + result = backend.run(tqc, shots=4096).result() +upgrade: + - | + The :class:`~qiskit.providers.basicaer.BasicAerJob` job objects returned + from BasicAer backends are now synchronous instances of + :class:`~qiskit.providers.JobV1`. This means that calls to + the :meth:`~qiskit.providers.basicaer.QasmSimulatorPy.run` will block + until the simulation finishes executing. If you want to restore the + previous async behavior you'll need to wrap the + :meth:`~qiskit.providers.basicaer.QasmSimulatorPy.run` with something that + will run in a seperate thread or process like ``futures.ThreadPoolExecutor`` + or ``futures.ProcessPoolExecutor``. diff --git a/test/python/basicaer/test_basicaer_integration.py b/test/python/basicaer/test_basicaer_integration.py index 13ac3ff9cff0..73c7446c97b5 100644 --- a/test/python/basicaer/test_basicaer_integration.py +++ b/test/python/basicaer/test_basicaer_integration.py @@ -75,8 +75,8 @@ def test_basicaer_num_qubits(self): qc = QuantumCircuit(50, 1) qc.x(0) qc.measure(0, 0) - job = execute(qc, self.backend) - self.assertRaises(BasicAerError, job.result) + with self.assertRaises(BasicAerError): + execute(qc, self.backend) if __name__ == '__main__': diff --git a/test/python/basicaer/test_basicaer_qobj_headers.py b/test/python/basicaer/test_basicaer_qobj_headers.py index 396846295110..427a1c958718 100644 --- a/test/python/basicaer/test_basicaer_qobj_headers.py +++ b/test/python/basicaer/test_basicaer_qobj_headers.py @@ -65,11 +65,3 @@ def test_qobj_headers_in_result(self): result = backend.run(qobj).result() self.assertEqual(result.header.to_dict(), custom_qobj_header) self.assertEqual(result.results[0].header.some_field, 'extra info') - - def test_job_qobj(self): - """Test job.qobj().""" - for backend in BasicAer.backends(): - with self.subTest(backend=backend): - qobj = assemble(self.qc1) - job = backend.run(qobj) - self.assertEqual(job.qobj(), qobj) diff --git a/test/python/basicaer/test_qasm_simulator.py b/test/python/basicaer/test_qasm_simulator.py index e1fe991ff28a..2b37bd3ebdb1 100644 --- a/test/python/basicaer/test_qasm_simulator.py +++ b/test/python/basicaer/test_qasm_simulator.py @@ -276,7 +276,7 @@ def test_unitary(self): for _ in range(i): multi_x = np.kron(multi_x, x_mat) # Target counts - shots = 100 + shots = 1024 target_counts = {num_qubits * '1': shots} # Test circuit qr = QuantumRegister(num_qubits, 'qr') diff --git a/test/python/providers/test_jobmethods.py b/test/python/providers/test_jobmethods.py deleted file mode 100644 index 40c70a0c8468..000000000000 --- a/test/python/providers/test_jobmethods.py +++ /dev/null @@ -1,144 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017. -# -# 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. - -# pylint: disable=missing-function-docstring - -"""BasicAerJob creation and test suite.""" - -import uuid -from contextlib import contextmanager -from os import path -import unittest - -from unittest.mock import patch -from qiskit.test import QiskitTestCase -from qiskit.test.mock import FakeQobj, FakeRueschlikon -from qiskit.providers.jobstatus import JobStatus -from qiskit.providers.exceptions import JobTimeoutError - - -class TestSimulatorsJob(QiskitTestCase): - """Test how backends create BasicAerJob objects and the BasicAerJob class.""" - - def test_multiple_execution(self): - # Notice that it is Python responsibility to test the executors - # can run several tasks at the same time. It is our responsibility to - # use the executor correctly. That is what this test checks. - - taskcount = 10 - target_tasks = [lambda: None for _ in range(taskcount)] - - job_id = str(uuid.uuid4()) - backend = FakeRueschlikon() - with mocked_executor() as (SimulatorJob, executor): - for index in range(taskcount): - job = SimulatorJob(backend, job_id, target_tasks[index], FakeQobj()) - job.submit() - - self.assertEqual(executor.submit.call_count, taskcount) - for index in range(taskcount): - _, callargs, _ = executor.submit.mock_calls[index] - submitted_task = callargs[0] - target_task = target_tasks[index] - self.assertEqual(submitted_task, target_task) - - def test_cancel(self): - # Again, cancelling jobs is beyond our responsibility. In this test - # we only check if we delegate on the proper method of the underlaying - # future object. - - job_id = str(uuid.uuid4()) - backend = FakeRueschlikon() - with mocked_executor() as (BasicAerJob, executor): - job = BasicAerJob(backend, job_id, lambda: None, FakeQobj()) - job.submit() - job.cancel() - - self.assertCalledOnce(executor.submit) - mocked_future = executor.submit.return_value - self.assertCalledOnce(mocked_future.cancel) - - def test_wait_for_final_state(self): - """Test waiting for job to reach a final state.""" - def _job_call_back(c_job_id, c_job_status, c_job): - """Job status query callback function.""" - self.assertEqual(c_job_id, job_id) - self.assertEqual(c_job_status, JobStatus.RUNNING) - self.assertEqual(c_job, job) - mocked_future.running.return_value = False - mocked_future.done.return_value = True - - job_id = str(uuid.uuid4()) - backend = FakeRueschlikon() - with mocked_executor() as (BasicAerJob, executor): - job = BasicAerJob(backend, job_id, lambda: None, FakeQobj()) - job.submit() - - mocked_future = executor.submit.return_value - mocked_future.running.return_value = True - mocked_future.cancelled.return_value = False - mocked_future.done.return_value = False - job.wait_for_final_state(callback=_job_call_back) - - def test_wait_for_final_state_timeout(self): - """Test timeout waiting for job to reach a final state.""" - job_id = str(uuid.uuid4()) - backend = FakeRueschlikon() - with mocked_executor() as (BasicAerJob, executor): - job = BasicAerJob(backend, job_id, lambda: None, FakeQobj()) - job.submit() - - mocked_future = executor.submit.return_value - mocked_future.running.return_value = True - mocked_future.cancelled.return_value = False - mocked_future.done.return_value = False - self.assertRaises(JobTimeoutError, job.wait_for_final_state, timeout=0.5) - - def assertCalledOnce(self, mocked_callable): - """Assert a mocked callable has been called once.""" - call_count = mocked_callable.call_count - self.assertEqual( - call_count, 1, - 'Callable object has been called more than once ({})'.format( - call_count)) - - -@contextmanager -def mocked_executor(): - """Context that patches the derived executor classes to return the same - executor object. Also patches the future object returned by executor's - submit().""" - - import importlib - import concurrent.futures as futures - import qiskit.providers.basicaer.basicaerjob as basicaerjob - - executor = unittest.mock.MagicMock(spec=futures.Executor) - executor.submit.return_value = unittest.mock.MagicMock(spec=futures.Future) - mock_options = {'return_value': executor, 'autospec': True} - with patch.object(futures, 'ProcessPoolExecutor', **mock_options),\ - patch.object(futures, 'ThreadPoolExecutor', **mock_options): - importlib.reload(basicaerjob) - yield basicaerjob.BasicAerJob, executor - - -@contextmanager -def mocked_simulator_binaries(): - """Context to force binary-based simulators to think the simulators exist. - """ - with patch.object(path, 'exists', return_value=True, autospec=True),\ - patch.object(path, 'getsize', return_value=1000, autospec=True): - yield - - -if __name__ == '__main__': - unittest.main(verbosity=2)