-
Notifications
You must be signed in to change notification settings - Fork 159
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support local execution on simulators #1243
Changes from 43 commits
ef7956f
d6ab634
fc6e323
d105760
80ff68f
7b8578d
e55831b
9afe8b3
9e0d7ff
a56063f
959445b
86b402b
0bb8f03
a83701e
12fe17c
c4fbb9f
b4072af
fb28323
1918431
a59784b
ee0ed3e
2eae3b5
a6516a3
0eb8c6a
e1166f8
d851705
3d150f1
86f23fd
8f2f568
5a251be
6da6e58
bc13543
1b07547
d8b6b86
625066b
2157aad
1959df6
35678a3
a6e9379
09aadbe
343a0e5
0d28bf4
be47496
e077401
acfeb20
c5ca5fc
afa0c1a
af5fcef
ea59600
22773c5
97a6868
606c902
6b8d07f
8830e76
9bbac3d
d3e030b
e4422a9
5171d7e
74ed194
3a90e02
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,10 +20,13 @@ | |
from dataclasses import asdict | ||
import warnings | ||
|
||
from qiskit_aer import AerSimulator | ||
|
||
from qiskit.providers.options import Options as TerraOptions | ||
|
||
from qiskit_ibm_provider.session import get_cm_session as get_cm_provider_session | ||
|
||
from .fake_provider.fake_provider import FakeProviderForBackendV2 | ||
from .options import Options | ||
from .options.utils import set_default_error_levels | ||
from .runtime_job import RuntimeJob | ||
|
@@ -98,6 +101,13 @@ def __init__( | |
if isinstance(backend, IBMBackend): | ||
self._service = backend.service | ||
self._backend = backend | ||
elif isinstance(backend, AerSimulator): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should check for both Aer and FakeBackend There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good comment. I actually only supported |
||
self._backend = backend | ||
self._service = ( | ||
QiskitRuntimeService() | ||
if QiskitRuntimeService.global_service is None | ||
else QiskitRuntimeService.global_service | ||
) | ||
Comment on lines
+116
to
+120
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of adding all the local logic into There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am not sure I understand your intention in this comment. Do you mean that when running on a simulator, we don't need a real service, and don't need to have a real account? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, so instead of modifying There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I will try this. Are you also saying that the local run will not need an account? because in a comment above, @kt474 thought it should have an account. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can do it this way, without an account There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
elif isinstance(backend, str): | ||
self._service = ( | ||
QiskitRuntimeService() | ||
|
@@ -137,6 +147,11 @@ def _run_primitive(self, primitive_inputs: Dict, user_kwargs: Dict) -> RuntimeJo | |
Returns: | ||
Submitted job. | ||
""" | ||
run_simulator = isinstance(self._backend, AerSimulator) or ( | ||
FakeProviderForBackendV2().get_backend(self._backend.name) is not None | ||
and not isinstance(self._backend, IBMBackend) | ||
) | ||
|
||
combined = Options._merge_options(self._options, user_kwargs) | ||
|
||
if self._backend: | ||
|
@@ -154,16 +169,30 @@ def _run_primitive(self, primitive_inputs: Dict, user_kwargs: Dict) -> RuntimeJo | |
|
||
combined = Options._set_default_resilience_options(combined) | ||
combined = Options._remove_none_values(combined) | ||
|
||
primitive_inputs.update(Options._get_program_inputs(combined)) | ||
|
||
if self._backend and combined["transpilation"]["skip_transpilation"]: | ||
if self._backend and combined["transpilation"]["skip_transpilation"] and not run_simulator: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is currently no |
||
for circ in primitive_inputs["circuits"]: | ||
self._backend.check_faulty(circ) | ||
|
||
logger.info("Submitting job using options %s", combined) | ||
|
||
runtime_options = Options._get_runtime_options(combined) | ||
|
||
if run_simulator: | ||
# do we need to keep the other 'flat' options as well, | ||
# for passing to terra directly? | ||
primitive_inputs["optimization_level"] = combined["optimization_level"] | ||
runtime_options["backend"] = self._backend | ||
|
||
return self._service.run( | ||
program_id=self._program_id(), | ||
options=runtime_options, | ||
inputs=primitive_inputs, | ||
# are the following relevant for simulators? | ||
# callback=combined.get("environment", {}).get("callback", None), | ||
# result_decoder=DEFAULT_DECODERS.get(self._program_id()), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we can leave these out for simulators There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In terms of the other options, I think it'd be nice to have the run options like:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
) | ||
if self._session: | ||
return self._session.run( | ||
program_id=self._program_id(), | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,8 +16,11 @@ | |
Fake provider class that provides access to fake backends. | ||
""" | ||
|
||
from typing import List | ||
|
||
from qiskit.providers.provider import ProviderV1 | ||
from qiskit.providers.exceptions import QiskitBackendNotFoundError | ||
from qiskit.providers.fake_provider.fake_backend import FakeBackendV2 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have recently opened a PR that migrates the fake provider base classes from qiskit (#1270), should it be merged before this PR, this import path would change to |
||
|
||
from .backends import * | ||
|
||
|
@@ -69,18 +72,19 @@ class FakeProviderForBackendV2(ProviderV1): | |
available in the :mod:`qiskit_ibm_runtime.fake_provider`. | ||
""" | ||
|
||
def get_backend(self, name=None, **kwargs): # type: ignore | ||
backend = self._backends[0] | ||
def get_backend(self, name=None, **kwargs) -> FakeBackendV2: # type: ignore | ||
# backend = self._backends[0] | ||
# when name=None, this will simply return Almaden. Is that what we want? | ||
merav-aharoni marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if name: | ||
filtered_backends = [backend for backend in self._backends if backend.name() == name] | ||
if not filtered_backends: | ||
raise QiskitBackendNotFoundError() | ||
|
||
backend = filtered_backends[0] | ||
filtered_backends = [backend for backend in self.backends() if backend.name == name] | ||
# if not filtered_backends: | ||
# raise QiskitBackendNotFoundError() | ||
# I prefer returning None rather than throwing an error. Any objection? | ||
merav-aharoni marked this conversation as resolved.
Show resolved
Hide resolved
|
||
backend = filtered_backends[0] if len(filtered_backends) > 0 else None | ||
|
||
return backend | ||
|
||
def backends(self, name=None, **kwargs): # type: ignore | ||
def backends(self, name=None, **kwargs) -> List[FakeBackendV2]: # type: ignore | ||
return self._backends | ||
|
||
def __init__(self) -> None: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
# This code is part of Qiskit. | ||
# | ||
# (C) Copyright IBM 2023. | ||
# | ||
# 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. | ||
|
||
"""Qiskit fake runtime job.""" | ||
|
||
from typing import Optional, Dict, Any | ||
|
||
from qiskit.primitives.primitive_job import PrimitiveJob | ||
from qiskit.providers import JobStatus, JobV1 | ||
from qiskit.providers.fake_provider import FakeBackendV2 as FakeBackend | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same comment as above regarding the import path |
||
|
||
# pylint: disable=cyclic-import | ||
# from .qiskit_runtime_service import QiskitRuntimeService | ||
|
||
|
||
class FakeRuntimeJob(JobV1): | ||
"""Representation of a runtime program execution on a simulator.""" | ||
|
||
def __init__( | ||
self, | ||
primitive_job: PrimitiveJob, | ||
backend: FakeBackend, | ||
job_id: str, | ||
program_id: str, | ||
# service: "qiskit_runtime_service.QiskitRuntimeService", | ||
params: Optional[Dict] = None, | ||
# creation_date: Optional[str] = None, | ||
# user_callback: Optional[Callable] = None, | ||
# result_decoder: Optional[Union[Type[ResultDecoder], Sequence[Type[ResultDecoder]]]] = None, | ||
# tags: Optional[List] = None, | ||
) -> None: | ||
"""FakeRuntimeJob constructor.""" | ||
super().__init__(backend=backend, job_id=job_id) | ||
# self._service = service # do we need this? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we need service, but it would be nice to have the other ones - creation_date, user_callback, result_decoder, tags There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added |
||
self._primitive_job = primitive_job | ||
self._job_id = job_id | ||
self._params = params or {} | ||
self._program_id = program_id | ||
|
||
def result(self) -> Any: | ||
"""Return the results of the job.""" | ||
return self._primitive_job.result() | ||
|
||
def cancel(self) -> None: | ||
self._primitive_job.cancel() | ||
|
||
def status(self) -> JobStatus: | ||
return self._primitive_job.status() | ||
Comment on lines
+48
to
+56
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not just inherit There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I tried, but couldn't manage to do that, because I am receiving |
||
|
||
@property | ||
def inputs(self) -> Dict: | ||
"""Job input parameters. | ||
|
||
Returns: | ||
Input parameters used in this job. | ||
""" | ||
return self._params | ||
|
||
def submit(self) -> None: | ||
"""Unsupported method. | ||
Note: | ||
This method is not supported, please use | ||
:meth:`~qiskit_ibm_runtime.QiskitRuntimeService.run` | ||
to submit a job. | ||
Raises: | ||
NotImplementedError: Upon invocation. | ||
""" | ||
raise NotImplementedError( | ||
"job.submit() is not supported. Please use " | ||
"QiskitRuntimeService.run() to submit a job." | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,6 +12,7 @@ | |
|
||
"""Utility functions for options.""" | ||
|
||
from qiskit.providers.fake_provider import fake_backend | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here too |
||
from ..ibm_backend import IBMBackend | ||
|
||
|
||
|
@@ -32,20 +33,17 @@ def set_default_error_levels( | |
Returns: | ||
options with correct error level defaults. | ||
""" | ||
is_simulator = ( | ||
isinstance(backend, fake_backend.FakeBackendV2) or backend.configuration().simulator | ||
) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is no need to change optimization_level and resilience_level for fake backends. Fake backends should always have noise model (from the real backend it mimics), so one doesn't have to pass it in. And none supports resilience_level. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Changed so we don't apply this to a FakeBackend. |
||
if options.get("optimization_level") is None: | ||
if ( | ||
backend.configuration().simulator | ||
and options.get("simulator", {}).get("noise_model") is None | ||
): | ||
if is_simulator and options.get("simulator", {}).get("noise_model") is None: | ||
options["optimization_level"] = 1 | ||
else: | ||
options["optimization_level"] = default_optimization_level | ||
|
||
if options.get("resilience_level") is None: | ||
if ( | ||
backend.configuration().simulator | ||
and options.get("simulator", {}).get("noise_model") is None | ||
): | ||
if is_simulator and options.get("simulator", {}).get("noise_model") is None: | ||
options["resilience_level"] = 0 | ||
else: | ||
options["resilience_level"] = default_resilience_level | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aer is not a required package, so this import be wrapped with try/except
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added
try
and raise exception when attempting to run with a simulator and not aer.Also added
skip
for relevant tests.Please have a look if it is done properly.