Skip to content
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

Closed
wants to merge 60 commits into from
Closed
Show file tree
Hide file tree
Changes from 43 commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
ef7956f
Recognized fake backends
merav-aharoni Nov 15, 2023
d6ab634
Continuing support for fake backends in Sampler
merav-aharoni Nov 23, 2023
fc6e323
Merge branch 'main' into fake_backends
merav-aharoni Nov 23, 2023
d105760
Created FakeRuntimeJob
merav-aharoni Nov 23, 2023
80ff68f
Added AerSimulator
merav-aharoni Nov 28, 2023
7b8578d
Merged with main
merav-aharoni Nov 29, 2023
e55831b
Small fixes
merav-aharoni Nov 29, 2023
9afe8b3
Cleaning up
merav-aharoni Nov 29, 2023
9e0d7ff
Added sample test
merav-aharoni Nov 29, 2023
a56063f
Fixed passing options to fake backend
merav-aharoni Nov 29, 2023
959445b
Added set_transpile_options
merav-aharoni Dec 4, 2023
86b402b
Merge branch 'main' into fake_backends
merav-aharoni Dec 4, 2023
0bb8f03
Fixed options parameters
merav-aharoni Dec 5, 2023
a83701e
lint and black
merav-aharoni Dec 5, 2023
12fe17c
Use fake_provider.py to get fake backends
merav-aharoni Dec 5, 2023
c4fbb9f
Merge branch 'fake_backends' of github.com:merav-aharoni/qiskit-ibm-r…
merav-aharoni Dec 5, 2023
b4072af
Added estimator to test
merav-aharoni Dec 5, 2023
fb28323
Added check that fake_backend is not IBMBackend, for test suite
merav-aharoni Dec 5, 2023
1918431
lint
merav-aharoni Dec 5, 2023
a59784b
Fixes for mypy
merav-aharoni Dec 6, 2023
ee0ed3e
black
merav-aharoni Dec 6, 2023
2eae3b5
Merge branch 'main' into fake_backends
merav-aharoni Dec 6, 2023
a6516a3
black
merav-aharoni Dec 6, 2023
0eb8c6a
Merge branch 'fake_backends' of github.com:merav-aharoni/qiskit-ibm-r…
merav-aharoni Dec 6, 2023
e1166f8
Fixed integration tests
merav-aharoni Dec 6, 2023
d851705
Merge branch 'main' into fake_backends
merav-aharoni Dec 6, 2023
3d150f1
Added channel to constructor of FakeRuntimeService
merav-aharoni Dec 6, 2023
86f23fd
Merge branch 'fake_backends' of github.com:merav-aharoni/qiskit-ibm-r…
merav-aharoni Dec 6, 2023
8f2f568
Added transfer of AerSimulator options along with test
merav-aharoni Dec 6, 2023
5a251be
Merge branch 'main' into fake_backends
merav-aharoni Dec 6, 2023
6da6e58
attempt to fix account problem
merav-aharoni Dec 6, 2023
bc13543
Merge branch 'fake_backends' of github.com:merav-aharoni/qiskit-ibm-r…
merav-aharoni Dec 6, 2023
1b07547
Added doc string
merav-aharoni Dec 6, 2023
d8b6b86
black
merav-aharoni Dec 6, 2023
625066b
mypy
merav-aharoni Dec 6, 2023
2157aad
Another attempt at fixing account. Removed print.
merav-aharoni Dec 7, 2023
1959df6
Removed unnecessary import
merav-aharoni Dec 7, 2023
35678a3
lint
merav-aharoni Dec 7, 2023
a6e9379
cleaning up
merav-aharoni Dec 7, 2023
09aadbe
Release note
merav-aharoni Dec 7, 2023
343a0e5
Merge branch 'main' into fake_backends
merav-aharoni Dec 7, 2023
0d28bf4
Merge branch 'main' into fake_backends
kt474 Dec 8, 2023
be47496
Merge branch 'main' into fake_backends
merav-aharoni Dec 11, 2023
e077401
Merge branch 'main' into fake_backends
merav-aharoni Dec 13, 2023
acfeb20
Added creation_date to FakeRuntimeJob
merav-aharoni Dec 13, 2023
c5ca5fc
Merge branch 'fake_backends' of github.com:merav-aharoni/qiskit-ibm-r…
merav-aharoni Dec 13, 2023
afa0c1a
Added creation_date and tags to FakeRuntimeJob
merav-aharoni Dec 13, 2023
af5fcef
black
merav-aharoni Dec 13, 2023
ea59600
Added docstring. Removed comments that contained questions
merav-aharoni Dec 13, 2023
22773c5
Added support for FakeBackendV2 also when given as an object, not onl…
merav-aharoni Dec 13, 2023
97a6868
mypy
merav-aharoni Dec 13, 2023
606c902
Added checks on importing Aer
merav-aharoni Dec 13, 2023
6b8d07f
When backend is FakeBackendV2, don't modify resilience and optimizati…
merav-aharoni Dec 14, 2023
8830e76
Added test for backend.run() on simulator
merav-aharoni Dec 14, 2023
9bbac3d
Merge branch 'main' into fake_backends
merav-aharoni Dec 14, 2023
d3e030b
Merge branch 'main' into fake_backends
merav-aharoni Dec 19, 2023
e4422a9
Merged with main after transfer of FakeBackendV2 to here
merav-aharoni Dec 25, 2023
5171d7e
black
merav-aharoni Dec 25, 2023
74ed194
lint
merav-aharoni Dec 25, 2023
3a90e02
Fixed path in base_primitive
merav-aharoni Dec 25, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 31 additions & 2 deletions qiskit_ibm_runtime/base_primitive.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,13 @@
from dataclasses import asdict
import warnings

from qiskit_aer import AerSimulator
Copy link
Collaborator

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

Copy link
Contributor Author

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.


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
Expand Down Expand Up @@ -98,6 +101,13 @@ def __init__(
if isinstance(backend, IBMBackend):
self._service = backend.service
self._backend = backend
elif isinstance(backend, AerSimulator):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should check for both Aer and FakeBackend

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good comment. I actually only supported FakeBackendV2 when given as a string. I modified here and added to the test TestRunSimulation.test_basic_flow.

self._backend = backend
self._service = (
QiskitRuntimeService()
if QiskitRuntimeService.global_service is None
else QiskitRuntimeService.global_service
)
Comment on lines +116 to +120
Copy link
Collaborator

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 new LocalRuntimeService to "mock" any server requests.

Copy link
Contributor Author

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?

Copy link
Collaborator

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 set self._service to a mock (local) service. And its run() method would handle the local operation.

Copy link
Contributor Author

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.

Copy link
Member

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

Copy link
Contributor Author

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.

elif isinstance(backend, str):
self._service = (
QiskitRuntimeService()
Expand Down Expand Up @@ -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:
Expand All @@ -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:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check_faulty should apply to fake backends as well, if we ever get one that mimics a real backend with faulty edge/qubits

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is currently no check_faulty method in FakeBackendV2 nor in AerSimulator. To support this, we would need to add such a method to FakeBackendV2 after it is transferred to Runtime. So I suggest we defer this to a separate issue.

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()),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can leave these out for simulators

Copy link
Member

Choose a reason for hiding this comment

The 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:

{'shots': 400, 'init_qubits': True, 'noise_model': None, 'seed_simulator': None}
and
{'max_execution_time': None, 'log_level': 'WARNING', 'job_tags': []}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • shots and seed_simulator are already supported.
  • I couldn't fine init_qubits or anything similar in the AerSimulator, so I don't think there is any point of passing it on. Let me know if you know otherwise. I did find it as a parameter of assemble_circuits in Terra, but I am not sure how and if this is called.
  • Same as above for max_execution_time.
  • noise_model - I think this is only relevant when calling AerSimulator and not for fake backends, because the latter define their own noise models. For Aer, I pass along all the options that are AerSimulator._default_options().
  • Regarding log_level, in Terra and in Aer, I only found setting the log_level globally as an environment variable, so I am not sure what we can do here.
  • job_tags - I added to FakeRuntimeJob.

)
if self._session:
return self._session.run(
program_id=self._program_id(),
Expand Down
20 changes: 12 additions & 8 deletions qiskit_ibm_runtime/fake_provider/fake_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Collaborator

@ElePT ElePT Dec 8, 2023

Choose a reason for hiding this comment

The 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 qiskit_ibm_runtime.fake_provider.fake_backend import FakeBackendV2. I'm happy to monitor it on my side, but thought it would make sense to bring it to your attention too.


from .backends import *

Expand Down Expand Up @@ -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:
Expand Down
80 changes: 80 additions & 0 deletions qiskit_ibm_runtime/fake_runtime_job.py
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
Copy link
Collaborator

Choose a reason for hiding this comment

The 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?
Copy link
Member

Choose a reason for hiding this comment

The 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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added creation_date and tags.
Removed user_callback as you wrote below and also because it refers to interim results.
Removed result_decoder as you wrote below, because results will not be returned encoded.

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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just inherit PrimitiveJob, then you only need to add methods not already supported.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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 primitive_job as an object and couldn't find a way to construct the FakeRuntimeObject with a pre-constructed super() object. I would need something like a copy constructor.
I could simply use PrimitiveJob directly, but then I wouldn't be able to add any fields/methods.
Can you advise how to do this with inheritance?


@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."
)
14 changes: 6 additions & 8 deletions qiskit_ibm_runtime/options/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

"""Utility functions for options."""

from qiskit.providers.fake_provider import fake_backend
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here too

from ..ibm_backend import IBMBackend


Expand All @@ -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
)
Copy link
Collaborator

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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
Expand Down
Loading
Loading