-
Notifications
You must be signed in to change notification settings - Fork 157
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 all 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,20 @@ | |
from dataclasses import asdict | ||
import warnings | ||
|
||
try: | ||
from qiskit_aer import AerSimulator | ||
|
||
HAS_AER_SIMULATOR = True | ||
except ImportError: | ||
HAS_AER_SIMULATOR = False | ||
|
||
from qiskit.providers.options import Options as TerraOptions | ||
from qiskit.providers.exceptions import QiskitBackendNotFoundError | ||
|
||
from qiskit_ibm_provider.session import get_cm_session as get_cm_provider_session | ||
|
||
from qiskit_ibm_runtime.fake_provider.fake_backend import FakeBackendV2 | ||
from .fake_provider.fake_provider import FakeProviderForBackendV2 | ||
from .options import Options | ||
from .options.utils import set_default_error_levels | ||
from .runtime_job import RuntimeJob | ||
|
@@ -43,7 +53,7 @@ class BasePrimitive(ABC): | |
|
||
def __init__( | ||
self, | ||
backend: Optional[Union[str, IBMBackend]] = None, | ||
backend: Optional[Union[str, IBMBackend, FakeBackendV2]] = None, | ||
session: Optional[Union[Session, str, IBMBackend]] = None, | ||
options: Optional[Union[Dict, Options]] = None, | ||
): | ||
|
@@ -74,7 +84,10 @@ def __init__( | |
# a nested dictionary to categorize options. | ||
self._session: Optional[Session] = None | ||
self._service: QiskitRuntimeService = None | ||
self._backend: Optional[IBMBackend] = None | ||
self._backend: Optional[Union[IBMBackend, FakeBackendV2]] = None | ||
|
||
if isinstance(backend, (AerSimulator, FakeBackendV2)) and not HAS_AER_SIMULATOR: | ||
raise QiskitBackendNotFoundError("To use an Aer Simulator, you must install aer") | ||
|
||
if options is None: | ||
self._options = asdict(Options()) | ||
|
@@ -98,6 +111,13 @@ def __init__( | |
if isinstance(backend, IBMBackend): | ||
self._service = backend.service | ||
self._backend = backend | ||
elif isinstance(backend, (AerSimulator, FakeBackendV2)): | ||
self._backend = backend | ||
self._service = ( | ||
QiskitRuntimeService() | ||
if QiskitRuntimeService.global_service is None | ||
else QiskitRuntimeService.global_service | ||
) | ||
elif isinstance(backend, str): | ||
self._service = ( | ||
QiskitRuntimeService() | ||
|
@@ -137,6 +157,11 @@ def _run_primitive(self, primitive_inputs: Dict, user_kwargs: Dict) -> RuntimeJo | |
Returns: | ||
Submitted job. | ||
""" | ||
run_simulator = isinstance(self._backend, AerSimulator) or ( | ||
FakeProviderForBackendV2().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 +179,27 @@ 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, | ||
) | ||
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,21 +72,15 @@ class FakeProviderForBackendV2(ProviderV1): | |
available in the :mod:`qiskit_ibm_runtime.fake_provider`. | ||
""" | ||
|
||
def backend(self, name=None, **kwargs): # type: ignore | ||
""" | ||
Filter backends in provider by name. | ||
""" | ||
backend = self._backends[0] | ||
def backend(self, name: str = None, **kwargs) -> FakeBackendV2: # type: ignore | ||
"""Return the backend according to its name. If kwargs are defined, then | ||
additional filters may be applied. If no such backend, return None.""" | ||
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] | ||
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,98 @@ | ||
# 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, List | ||
from datetime import datetime | ||
|
||
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, | ||
params: Optional[Dict] = None, | ||
creation_date: Optional[str] = None, | ||
tags: Optional[List] = None, | ||
) -> None: | ||
"""FakeRuntimeJob constructor.""" | ||
super().__init__(backend=backend, job_id=job_id) | ||
self._primitive_job = primitive_job | ||
self._job_id = job_id | ||
self._params = params or {} | ||
self._program_id = program_id | ||
self._creation_date = creation_date | ||
self._tags = tags | ||
|
||
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 | ||
|
||
@property | ||
def creation_date(self) -> Optional[datetime]: | ||
"""Job creation date in local time. | ||
|
||
Returns: | ||
The job creation date as a datetime object, in local time, or | ||
``None`` if creation date is not available. | ||
""" | ||
return self._creation_date | ||
|
||
@property | ||
def tags(self) -> List: | ||
"""Job tags. | ||
|
||
Returns: | ||
Tags assigned to the job that can be used for filtering. | ||
""" | ||
return self._tags | ||
|
||
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." | ||
) |
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.
Instead of adding all the local logic into
QiskitRuntimeService
, it'd be much cleaner to just create a newLocalRuntimeService
to "mock" any server requests.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.
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 comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, so instead of modifying
QiskitRuntimeService
,base_primitive
can setself._service
to a mock (local) service. And itsrun()
method would handle the local operation.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.
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 comment
The 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 comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't get a chance to complete this item. I think it might be best to do in a separate PR. In any case, it is up to you @kt474 or @jyu00 to continue from here.