Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
…nto debugger
  • Loading branch information
SamFerracin committed Aug 21, 2024
2 parents da946c9 + 848b6ac commit 46cba8b
Show file tree
Hide file tree
Showing 42 changed files with 232 additions and 3,862 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ There are several different options you can specify when calling the primitives.

### Primitive versions

Version 2 of the primitives is introduced by `qiskit-ibm-runtime` release 0.21.0. If you are using V1 primitives, refer to [Migrate to the V2 primitives](https://docs.quantum.ibm.com/migration-guides/v2-primitives) on how to migratie to V2 primitives. The examples below all use V2 primitives.
Version 2 of the primitives is introduced by `qiskit-ibm-runtime` release 0.21.0. Version 1 of the primitives is no longer supported. Refer to [Migrate to the V2 primitives](https://docs.quantum.ibm.com/migration-guides/v2-primitives) on how to migratie to V2 primitives. The examples below all use V2 primitives.

### Sampler

Expand Down
1 change: 1 addition & 0 deletions docs/apidocs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ qiskit-ibm-runtime API reference

runtime_service
noise_learner
noise_learner_result
options
transpiler
qiskit_ibm_runtime.transpiler.passes.scheduling
Expand Down
4 changes: 4 additions & 0 deletions docs/apidocs/noise_learner_result.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.. automodule:: qiskit_ibm_runtime.utils.noise_learner_result
:no-members:
:no-inherited-members:
:no-special-members:
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
# The short X.Y version
version = ''
# The full version, including alpha/beta/rc tags
release = '0.28.0'
release = '0.29.0'

# -- General configuration ---------------------------------------------------

Expand Down
2 changes: 1 addition & 1 deletion qiskit_ibm_runtime/VERSION.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.28.0
0.29.0
16 changes: 7 additions & 9 deletions qiskit_ibm_runtime/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,10 +186,8 @@
QiskitRuntimeService
Estimator
EstimatorV1
EstimatorV2
Sampler
SamplerV1
SamplerV2
Session
Batch
Expand Down Expand Up @@ -218,15 +216,15 @@

from .estimator import ( # pylint: disable=reimported
EstimatorV2,
EstimatorV1,
EstimatorV1 as Estimator,
EstimatorV2 as Estimator,
)
from .sampler import ( # pylint: disable=reimported
SamplerV2,
SamplerV1,
SamplerV1 as Sampler,
from .sampler import SamplerV2, SamplerV2 as Sampler # pylint: disable=reimported
from .options import ( # pylint: disable=reimported
EstimatorOptions,
SamplerOptions,
OptionsV2,
OptionsV2 as Options,
)
from .options import Options, EstimatorOptions, SamplerOptions, OptionsV2

# Setup the logger for the IBM Quantum Provider package.
logger = logging.getLogger(__name__)
Expand Down
225 changes: 2 additions & 223 deletions qiskit_ibm_runtime/base_primitive.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@

from __future__ import annotations
from abc import ABC, abstractmethod
from typing import Dict, Optional, Any, Union, TypeVar, Generic, Type
import copy
from typing import Dict, Optional, Union, TypeVar, Generic, Type
import logging
from dataclasses import asdict, replace
import warnings
Expand All @@ -24,15 +23,10 @@

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

from .options import Options
from .options.options import BaseOptions, OptionsV2
from .options.utils import merge_options, set_default_error_levels, merge_options_v2
from .runtime_job import RuntimeJob
from .options.utils import merge_options, merge_options_v2
from .runtime_job_v2 import RuntimeJobV2
from .ibm_backend import IBMBackend
from .utils import validate_isa_circuits, validate_no_dd_with_dynamic_circuits
Expand Down Expand Up @@ -271,218 +265,3 @@ def _validate_options(self, options: dict) -> None:
def _program_id(cls) -> str:
"""Return the program ID."""
raise NotImplementedError()


class BasePrimitiveV1(ABC):
"""Base class for Qiskit Runtime primitives."""

version = 1

def __init__(
self,
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 a ``Backend``
instance. If a name is specified, the default account (e.g. ``QiskitRuntimeService()``)
is used.
session: Session in which to call the primitive.
If both ``session`` and ``backend`` are specified, ``session`` takes precedence.
If neither is specified, and the primitive is created inside a
:class:`qiskit_ibm_runtime.Session` context manager, then the session is used.
Otherwise if IBM Cloud channel is used, a default backend is selected.
options: Primitive options, see :class:`Options` for detailed description.
The ``backend`` keyword is still supported but is deprecated.
Raises:
ValueError: Invalid arguments are given.
"""
# `self._options` in this class is a Dict.
# The base class, however, uses a `_run_options` which is an instance of
# 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 | QiskitRuntimeLocalService = None
self._backend: Optional[BackendV1 | BackendV2] = None

issue_deprecation_msg(
"The Sampler and Estimator V1 primitives have been deprecated",
"0.23.0",
"Please use the V2 Primitives. See the `V2 migration guide "
"<https://docs.quantum.ibm.com/migration-guides/v2-primitives>`_. for more details",
3,
)

if options is None:
self._options = asdict(Options())
elif isinstance(options, Options):
self._options = asdict(copy.deepcopy(options))
else:
options_copy = copy.deepcopy(options)
default_options = asdict(Options())
self._options = merge_options(default_options, options_copy)

if isinstance(session, Session):
self._session = session
self._service = self._session.service
self._backend = self._session._backend
return
elif session is not None: # type: ignore[unreachable]
raise ValueError("session must be of type Session or None")

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()
if QiskitRuntimeService.global_service is None
else QiskitRuntimeService.global_service
)
self._backend = self._service.backend(backend)
elif get_cm_session():
self._session = get_cm_session()
self._service = self._session.service
self._backend = self._service.backend(
name=self._session.backend(), instance=self._session._instance
)
else:
raise ValueError("A backend or session must be specified.")

# Check if initialized within a IBMBackend session. If so, issue a warning.
if get_cm_provider_session():
warnings.warn(
"A Backend.run() session is open but Primitives will not be run within this session"
)

def _run_primitive(self, primitive_inputs: Dict, user_kwargs: Dict) -> RuntimeJob:
"""Run the primitive.
Args:
primitive_inputs: Inputs to pass to the primitive.
user_kwargs: Individual options to overwrite the default primitive options.
Returns:
Submitted job.
"""
# TODO: Don't check service / backend
if (
self._backend # pylint: disable=too-many-boolean-expressions
and isinstance(self._backend, IBMBackend)
and isinstance(self._backend.service, QiskitRuntimeService)
and not self._backend.simulator
and getattr(self._backend, "target", None)
and self._service._channel_strategy != "q-ctrl"
):
validate_isa_circuits(primitive_inputs["circuits"], self._backend.target)

combined = Options._merge_options(self._options, user_kwargs)

if self._backend:
combined = set_default_error_levels(
combined,
self._backend,
Options._DEFAULT_OPTIMIZATION_LEVEL,
Options._DEFAULT_RESILIENCE_LEVEL,
)
else:
combined["optimization_level"] = Options._DEFAULT_OPTIMIZATION_LEVEL
combined["resilience_level"] = Options._DEFAULT_RESILIENCE_LEVEL

self._validate_options(combined)

combined = Options._set_default_resilience_options(combined)
combined = Options._remove_none_values(combined)

primitive_inputs.update(Options._get_program_inputs(combined))

if (
isinstance(self._backend, IBMBackend)
and combined["transpilation"]["skip_transpilation"]
):
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 self._session:
return self._session.run(
program_id=self._program_id(),
inputs=primitive_inputs,
options=runtime_options,
callback=combined.get("environment", {}).get("callback", None),
result_decoder=DEFAULT_DECODERS.get(self._program_id()),
)

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

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,
)

@property
def session(self) -> Optional[Session]:
"""Return session used by this primitive.
Returns:
Session used by this primitive, or ``None`` if session is not used.
"""
return self._session

@property
def options(self) -> TerraOptions:
"""Return options values for the sampler.
Returns:
options
"""
return TerraOptions(**self._options)

def set_options(self, **fields: Any) -> None:
"""Set options values for the sampler.
Args:
**fields: The fields to update the options
"""
self._options = merge_options( # pylint: disable=attribute-defined-outside-init
self._options, fields
)

@abstractmethod
def _validate_options(self, options: dict) -> None:
"""Validate that primitive inputs (options) are valid
Raises:
ValueError: if resilience_level is out of the allowed range.
"""
raise NotImplementedError()

@classmethod
@abstractmethod
def _program_id(cls) -> str:
"""Return the program ID."""
raise NotImplementedError()
37 changes: 23 additions & 14 deletions qiskit_ibm_runtime/batch.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,29 +46,38 @@ class Batch(Session):
For example::
from qiskit.circuit.random import random_circuit
import numpy as np
from qiskit.circuit.library import IQP
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import Batch, SamplerV2 as Sampler, QiskitRuntimeService
from qiskit.quantum_info import random_hermitian
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler, Batch
n_qubits = 127
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=n_qubits)
# generate fifty unique three-qubit random circuits
circuits = [pm.run(random_circuit(3, 2, measure=True)) for _ in range(50)]
rng = np.random.default_rng()
mats = [np.real(random_hermitian(n_qubits, seed=rng)) for _ in range(30)]
circuits = [IQP(mat) for mat in mats]
for circuit in circuits:
circuit.measure_all()
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuits = pm.run(circuits)
# split up the list of circuits into partitions
max_circuits = 10
partitions = [circuits[i : i + max_circuits] for i in range(0, len(circuits), max_circuits)]
all_partitioned_circuits = []
for i in range(0, len(isa_circuits), max_circuits):
all_partitioned_circuits.append(isa_circuits[i : i + max_circuits])
jobs = []
start_idx = 0
# run the circuits in batched mode
with Batch(backend=backend):
sampler = Sampler()
for partition in partitions:
job = sampler.run(partition)
pub_result = job.result()[0]
print(f"Sampler job ID: {job.job_id()}")
print(f"Counts for the first PUB: {pub_result.data.cr.get_counts()}")
for partitioned_circuits in all_partitioned_circuits:
job = sampler.run(partitioned_circuits)
jobs.append(job)
For more details, check the "`Run jobs in a batch
<https://docs.quantum.ibm.com/guides/run-jobs-batch>`_" page.
Expand Down
Loading

0 comments on commit 46cba8b

Please sign in to comment.