Skip to content

Commit

Permalink
Add local mode for v1 primitives (#1495)
Browse files Browse the repository at this point in the history
* add local mode for v1 primitives

* black

* fix type hint

* add tests for batch

* move to fake_provider

* move local service to fake provider

* black

* fix tests

* Apply suggestions from code review

Co-authored-by: Kevin Tian <[email protected]>

* move release note

---------

Co-authored-by: Kevin Tian <[email protected]>
  • Loading branch information
jyu00 and kt474 authored Mar 15, 2024
1 parent cae09c1 commit 0106964
Show file tree
Hide file tree
Showing 20 changed files with 725 additions and 310 deletions.
42 changes: 41 additions & 1 deletion qiskit_ibm_runtime/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2022.
# (C) Copyright IBM 2022, 2024.
#
# 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
Expand Down Expand Up @@ -83,6 +83,46 @@
pub_result = job.result()[0]
print(f"Expectation values: {pub_result.data.evs}")
Local testing mode
==================
You can validate your quantum programs before sending them to a physical system using
the local testing mode. The local testing mode is activated if one of the fake
backends in ``qiskit_ibm_runtime.fake_provider`` or a Qiskit Aer backend
instance is used when instantiating a primitive or a session. For example::
from qiskit_aer import AerSimulator
from qiskit.circuit.library import RealAmplitudes
from qiskit.circuit import QuantumCircuit, QuantumRegister, ClassicalRegister
from qiskit.quantum_info import SparsePauliOp
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import Session
from qiskit_ibm_runtime import Sampler
from qiskit_ibm_runtime.fake_provider import FakeManilaV2
# Bell Circuit
qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0, 1)
qc.measure_all()
# Run the sampler job locally using FakeManilaV2
fake_manila = FakeManilaV2()
pm = generate_preset_pass_manager(backend=fake_manila, optimization_level=1)
isa_qc = pm.run(qc)
sampler = Sampler(backend=fake_manila)
result = sampler.run([isa_qc]).result()
# Run the sampler job locally using AerSimulator.
# Session syntax is supported but ignored.
aer_sim = AerSimulator()
pm = generate_preset_pass_manager(backend=aer_sim, optimization_level=1)
isa_qc = pm.run(qc)
with Session(backend=aer_sim) as session:
sampler = Sampler(session=session)
result = sampler.run([isa_qc]).result()
Backend data
============
Expand Down
47 changes: 30 additions & 17 deletions qiskit_ibm_runtime/base_primitive.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from qiskit.primitives.containers.estimator_pub import EstimatorPub
from qiskit.primitives.containers.sampler_pub import SamplerPub
from qiskit.providers.options import Options as TerraOptions
from qiskit.providers.backend import BackendV1, BackendV2

from .provider_session import get_cm_session as get_cm_provider_session

Expand All @@ -37,6 +38,7 @@
from .utils.utils import validate_isa_circuits
from .constants import DEFAULT_DECODERS
from .qiskit_runtime_service import QiskitRuntimeService
from .fake_provider.local_service import QiskitRuntimeLocalService


# pylint: disable=unused-import,cyclic-import
Expand Down Expand Up @@ -223,15 +225,15 @@ class BasePrimitiveV1(ABC):

def __init__(
self,
backend: Optional[Union[str, IBMBackend]] = None,
session: Optional[Union[Session, str, IBMBackend]] = None,
backend: Optional[Union[str, BackendV1, BackendV2]] = None,
session: Optional[Session] = None,
options: Optional[Union[Dict, Options]] = None,
):
"""Initializes the primitive.
Args:
backend: Backend to run the primitive. This can be a backend name or an :class:`IBMBackend`
backend: Backend to run the primitive. This can be a backend name or a ``Backend``
instance. If a name is specified, the default account (e.g. ``QiskitRuntimeService()``)
is used.
Expand All @@ -253,8 +255,8 @@ def __init__(
# qiskit.providers.Options. We largely ignore this _run_options because we use
# a nested dictionary to categorize options.
self._session: Optional[Session] = None
self._service: QiskitRuntimeService = None
self._backend: Optional[IBMBackend] = None
self._service: QiskitRuntimeService | QiskitRuntimeLocalService = None
self._backend: Optional[BackendV1 | BackendV2] = None

if options is None:
self._options = asdict(Options())
Expand All @@ -268,16 +270,17 @@ def __init__(
if isinstance(session, Session):
self._session = session
self._service = self._session.service
self._backend = self._service.backend(
name=self._session.backend(), instance=self._session._instance
)
self._backend = self._session._backend
return
elif session is not None:
elif session is not None: # type: ignore[unreachable]
raise ValueError("session must be of type Session or None")

if isinstance(backend, IBMBackend):
if isinstance(backend, IBMBackend): # type: ignore[unreachable]
self._service = backend.service
self._backend = backend
elif isinstance(backend, (BackendV1, BackendV2)):
self._service = QiskitRuntimeLocalService()
self._backend = backend
elif isinstance(backend, str):
self._service = (
QiskitRuntimeService()
Expand Down Expand Up @@ -323,6 +326,7 @@ def _run_primitive(self, primitive_inputs: Dict, user_kwargs: Dict) -> RuntimeJo
Returns:
Submitted job.
"""
# TODO: Don't check service / backend
if (
self._backend # pylint: disable=too-many-boolean-expressions
and isinstance(self._backend, IBMBackend)
Expand Down Expand Up @@ -353,7 +357,10 @@ def _run_primitive(self, primitive_inputs: Dict, user_kwargs: Dict) -> RuntimeJo

primitive_inputs.update(Options._get_program_inputs(combined))

if self._backend and combined["transpilation"]["skip_transpilation"]:
if (
isinstance(self._backend, IBMBackend)
and combined["transpilation"]["skip_transpilation"]
):
for circ in primitive_inputs["circuits"]:
self._backend.check_faulty(circ)

Expand All @@ -370,16 +377,22 @@ def _run_primitive(self, primitive_inputs: Dict, user_kwargs: Dict) -> RuntimeJo
)

if self._backend:
runtime_options["backend"] = self._backend.name
if "instance" not in runtime_options:
runtime_options["backend"] = self._backend
if "instance" not in runtime_options and isinstance(self._backend, IBMBackend):
runtime_options["instance"] = self._backend._instance

return self._service.run(
program_id=self._program_id(),
if isinstance(self._service, QiskitRuntimeService):
return self._service.run(
program_id=self._program_id(), # type: ignore[arg-type]
options=runtime_options,
inputs=primitive_inputs,
callback=combined.get("environment", {}).get("callback", None),
result_decoder=DEFAULT_DECODERS.get(self._program_id()),
)
return self._service.run( # type: ignore[call-arg]
program_id=self._program_id(), # type: ignore[arg-type]
options=runtime_options,
inputs=primitive_inputs,
callback=combined.get("environment", {}).get("callback", None),
result_decoder=DEFAULT_DECODERS.get(self._program_id()),
)

@property
Expand Down
18 changes: 10 additions & 8 deletions qiskit_ibm_runtime/batch.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@

from typing import Optional, Union

from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit.providers.backend import BackendV1, BackendV2

from .ibm_backend import IBMBackend
from qiskit_ibm_runtime import QiskitRuntimeService
from .session import Session


Expand All @@ -26,14 +26,16 @@ class Batch(Session):
def __init__(
self,
service: Optional[QiskitRuntimeService] = None,
backend: Optional[Union[str, IBMBackend]] = None,
backend: Optional[Union[str, BackendV1, BackendV2]] = None,
max_time: Optional[Union[int, str]] = None,
):
super().__init__(service=service, backend=backend, max_time=max_time)

def _create_session(self) -> str:
def _create_session(self) -> Optional[str]:
"""Create a session."""
session = self._service._api_client.create_session(
self._backend, self._instance, self._max_time, self._service.channel, "batch"
)
return session.get("id")
if isinstance(self._service, QiskitRuntimeService):
session = self._service._api_client.create_session(
self.backend(), self._instance, self._max_time, self._service.channel, "batch"
)
return session.get("id")
return None
4 changes: 2 additions & 2 deletions qiskit_ibm_runtime/estimator.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ class EstimatorV2(BasePrimitiveV2[EstimatorOptions], Estimator, BaseEstimatorV2)
def __init__(
self,
backend: Optional[Union[str, IBMBackend]] = None,
session: Optional[Union[Session, str, IBMBackend]] = None,
session: Optional[Session] = None,
options: Optional[Union[Dict, EstimatorOptions]] = None,
):
"""Initializes the Estimator primitive.
Expand Down Expand Up @@ -228,7 +228,7 @@ class EstimatorV1(BasePrimitiveV1, Estimator, BaseEstimator):
def __init__(
self,
backend: Optional[Union[str, IBMBackend]] = None,
session: Optional[Union[Session, str, IBMBackend]] = None,
session: Optional[Session] = None,
options: Optional[Union[Dict, Options]] = None,
):
"""Initializes the Estimator primitive.
Expand Down
Loading

0 comments on commit 0106964

Please sign in to comment.