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

Add support executing circuits with a SamplerV2 instance #1470

Merged
merged 14 commits into from
Oct 25, 2024
7 changes: 7 additions & 0 deletions docs/howtos/artifacts.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ Viewing artifacts
Here we run a parallel experiment consisting of two :class:`.T1` experiments in parallel and then view the output
artifacts as a list of :class:`.ArtifactData` objects accessed by :meth:`.ExperimentData.artifacts`:

.. jupyter-execute::
:hide-code:

# Temporary workaround for missing support in Qiskit and qiskit-ibm-runtime
from qiskit_experiments.test.patching import patch_sampler_test_support
patch_sampler_test_support()

.. jupyter-execute::

from qiskit_ibm_runtime.fake_provider import FakePerth
Expand Down
47 changes: 43 additions & 4 deletions docs/howtos/runtime_sessions.rst
Original file line number Diff line number Diff line change
@@ -1,5 +1,45 @@
Use Experiments with Runtime sessions
=====================================
Use Experiments with Runtime sessions and sampler
=================================================

Problem
-------

You want to run experiments with a custom `SamplerV2
<https://docs.quantum.ibm.com/api/qiskit-ibm-runtime/qiskit_ibm_runtime.SamplerV2>`_ service.
dcmckayibm marked this conversation as resolved.
Show resolved Hide resolved

.. note::
All jobs, by default, run using the ``SamplerV2`` service. When calling ``exp.run`` a
``SamplerV2`` object will be automatically generated from the specified backend.
dcmckayibm marked this conversation as resolved.
Show resolved Hide resolved

Solution
--------

In this example, we will pass in a ``SamplerV2`` object to a tomography experiment.

.. note::
If a sampler object is passed to ``exp.run`` then the `run options
<https://docs.quantum.ibm.com/api/qiskit-ibm-runtime/qiskit_ibm_runtime.options.SamplerExecutionOptionsV2>`_ of the
sampler object are used. The execution options set by the experiment are ignored.

.. jupyter-input::

from qiskit_ibm_runtime import SamplerV2 as Sampler
from qiskit_experiments.library.tomography import ProcessTomography
from qiskit import QuantumCircuit

service = QiskitRuntimeService(channel="ibm_quantum")
backend = service.backend("ibm_osaka")
qc = QuantumCircuit(1)
qc.x(0)

sampler = Sampler(backed)
# set the shots in the sampler object
sampler.options.default_shots = 300
exp = ProcessTomography(qc)
# Artificially lower circuits per job, adjust value for your own application
exp.set_experiment_options(max_circuits=3)
# pass the sampler into the experiment
exp_data = exp.run(sampler)
dcmckayibm marked this conversation as resolved.
Show resolved Hide resolved

Problem
-------
Expand Down Expand Up @@ -40,5 +80,4 @@ large number of circuits that can't fit in a single job, it may be helpful to fo
# This will prevent further jobs from being submitted without terminating current jobs
backend.close_session()

Note that runtime primitives are not currently supported natively in Qiskit Experiments, so
the ``backend.run()`` path is required to run experiments.

dcmckayibm marked this conversation as resolved.
Show resolved Hide resolved
7 changes: 7 additions & 0 deletions docs/manuals/characterization/t1.rst
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ for qubit 0.
packages to run simulations. You can install them with ``python -m pip
install qiskit-aer qiskit-ibm-runtime``.

.. jupyter-execute::
:hide-code:

# Temporary workaround for missing support in Qiskit and qiskit-ibm-runtime
from qiskit_experiments.test.patching import patch_sampler_test_support
patch_sampler_test_support()

.. jupyter-execute::

import numpy as np
Expand Down
7 changes: 7 additions & 0 deletions docs/manuals/characterization/t2ramsey.rst
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,13 @@ pure T1/T2 relaxation noise model.
packages to run simulations. You can install them with ``python -m pip
install qiskit-aer qiskit-ibm-runtime``.

.. jupyter-execute::
:hide-code:

# Temporary workaround for missing support in Qiskit and qiskit-ibm-runtime
from qiskit_experiments.test.patching import patch_sampler_test_support
patch_sampler_test_support()

.. jupyter-execute::

# A T1 simulator
Expand Down
7 changes: 7 additions & 0 deletions docs/manuals/characterization/tphi.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ From the :math:`T_1` and :math:`T_2` estimates, we compute the results for
packages to run simulations. You can install them with ``python -m pip
install qiskit-aer qiskit-ibm-runtime``.

.. jupyter-execute::
:hide-code:

# Temporary workaround for missing support in Qiskit and qiskit-ibm-runtime
from qiskit_experiments.test.patching import patch_sampler_test_support
patch_sampler_test_support()

.. jupyter-execute::

import numpy as np
Expand Down
7 changes: 7 additions & 0 deletions docs/manuals/measurement/readout_mitigation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ experiments to generate the corresponding mitigators.
packages to run simulations. You can install them with ``python -m pip
install qiskit-aer qiskit-ibm-runtime``.

.. jupyter-execute::
:hide-code:

# Temporary workaround for missing support in Qiskit and qiskit-ibm-runtime
from qiskit_experiments.test.patching import patch_sampler_test_support
patch_sampler_test_support()

.. jupyter-execute::

import numpy as np
Expand Down
7 changes: 7 additions & 0 deletions docs/manuals/measurement/restless_measurements.rst
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,13 @@ they use always starts with the qubits in the ground state.
This tutorial requires the :external+qiskit_ibm_runtime:doc:`qiskit-ibm-runtime <index>` package to model a
backend. You can install it with ``python -m pip install qiskit-ibm-runtime``.

.. jupyter-execute::
:hide-code:

# Temporary workaround for missing support in Qiskit and qiskit-ibm-runtime
from qiskit_experiments.test.patching import patch_sampler_test_support
patch_sampler_test_support()

.. jupyter-execute::

from qiskit_ibm_runtime.fake_provider import FakePerth
Expand Down
7 changes: 7 additions & 0 deletions docs/manuals/verification/quantum_volume.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@ z_value = 2), and at least 100 trials have been ran.
packages to run simulations. You can install them with ``python -m pip
install qiskit-aer qiskit-ibm-runtime``.

.. jupyter-execute::
:hide-code:

# Temporary workaround for missing support in Qiskit and qiskit-ibm-runtime
from qiskit_experiments.test.patching import patch_sampler_test_support
patch_sampler_test_support()

.. jupyter-execute::

from qiskit_experiments.framework import BatchExperiment
Expand Down
7 changes: 7 additions & 0 deletions docs/manuals/verification/randomized_benchmarking.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ explanation on the RB method, which is based on Refs. [1]_ [2]_.
packages to run simulations. You can install them with ``python -m pip
install qiskit-aer qiskit-ibm-runtime``.

.. jupyter-execute::
:hide-code:

# Temporary workaround for missing support in Qiskit and qiskit-ibm-runtime
from qiskit_experiments.test.patching import patch_sampler_test_support
patch_sampler_test_support()

.. jupyter-execute::

import numpy as np
Expand Down
7 changes: 7 additions & 0 deletions docs/manuals/verification/state_tomography.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ complete basis of measurement operators.

We first initialize a simulator to run the experiments on.

.. jupyter-execute::
:hide-code:

# Temporary workaround for missing support in Qiskit and qiskit-ibm-runtime
from qiskit_experiments.test.patching import patch_sampler_test_support
patch_sampler_test_support()

.. jupyter-execute::

from qiskit_aer import AerSimulator
Expand Down
7 changes: 7 additions & 0 deletions docs/tutorials/custom_experiment.rst
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,13 @@ To test our code, we first simulate a noisy backend with asymmetric readout erro
You can install it with ``python -m pip install qiskit-aer``.


.. jupyter-execute::
:hide-code:

# Temporary workaround for missing support in Qiskit and qiskit-ibm-runtime
from qiskit_experiments.test.patching import patch_sampler_test_support
patch_sampler_test_support()

.. jupyter-execute::

from qiskit_aer import AerSimulator, noise
Expand Down
7 changes: 7 additions & 0 deletions docs/tutorials/getting_started.rst
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,13 @@ backend, real or simulated, that you can access through Qiskit.
packages to run simulations. You can install them with ``python -m pip
install qiskit-aer qiskit-ibm-runtime``.

.. jupyter-execute::
:hide-code:

# Temporary workaround for missing support in Qiskit and qiskit-ibm-runtime
from qiskit_experiments.test.patching import patch_sampler_test_support
patch_sampler_test_support()

.. jupyter-execute::

from qiskit_ibm_runtime.fake_provider import FakePerth
Expand Down
4 changes: 4 additions & 0 deletions qiskit_experiments/framework/backend_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
Since `BackendV1` and `BackendV2` do not share the same interface, this
class unifies data access for various data fields.
"""
import warnings
from qiskit.providers.models import PulseBackendConfiguration # pylint: disable=no-name-in-module
from qiskit.providers import BackendV1, BackendV2

Expand All @@ -24,6 +25,9 @@ class BackendData:

def __init__(self, backend):
"""Inits the backend and verifies version"""
warnings.filterwarnings(
"ignore", message=".*qiskit.qobj.pulse_qobj.*", category=DeprecationWarning
)
dcmckayibm marked this conversation as resolved.
Show resolved Hide resolved
self._backend = backend
self._v1 = isinstance(backend, BackendV1)
self._v2 = isinstance(backend, BackendV2)
Expand Down
86 changes: 76 additions & 10 deletions qiskit_experiments/framework/base_experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
from qiskit.exceptions import QiskitError
from qiskit.qobj.utils import MeasLevel
from qiskit.providers.options import Options
from qiskit.primitives.base import BaseSamplerV2
from qiskit_ibm_runtime import SamplerV2 as Sampler
dcmckayibm marked this conversation as resolved.
Show resolved Hide resolved
from qiskit_experiments.framework import BackendData
from qiskit_experiments.framework.store_init_args import StoreInitArgs
from qiskit_experiments.framework.base_analysis import BaseAnalysis
Expand All @@ -40,6 +42,7 @@ def __init__(
analysis: Optional[BaseAnalysis] = None,
backend: Optional[Backend] = None,
experiment_type: Optional[str] = None,
backend_run: Options[bool] = False,
):
"""Initialize the experiment object.

Expand All @@ -48,7 +51,7 @@ def __init__(
analysis: Optional, the analysis to use for the experiment.
backend: Optional, the backend to run the experiment on.
experiment_type: Optional, the experiment type string.

backend_run: Optional, use backend run vs the sampler (temporary)
Raises:
QiskitError: If qubits contains duplicates.
"""
Expand Down Expand Up @@ -82,6 +85,7 @@ def __init__(
# attributes created during initialization
self._backend = None
self._backend_data = None
self._backend_run = backend_run
if isinstance(backend, Backend):
self._set_backend(backend)

Expand Down Expand Up @@ -197,22 +201,26 @@ def from_config(cls, config: Union[ExperimentConfig, Dict]) -> "BaseExperiment":
def run(
self,
backend: Optional[Backend] = None,
sampler: Optional[BaseSamplerV2] = None,
analysis: Optional[Union[BaseAnalysis, None]] = "default",
timeout: Optional[float] = None,
backend_run: Optional[bool] = None,
**run_options,
) -> ExperimentData:
"""Run an experiment and perform analysis.

Args:
backend: Optional, the backend to run the experiment on. This
will override any currently set backends for the single
execution.
backend: Optional, the backend to run on. Will override existing backend settings.
sampler: Optional, the sampler to run the experiment on.
If None then a sampler will be invoked from previously
set backend
analysis: Optional, a custom analysis instance to use for performing
analysis. If None analysis will not be run. If ``"default"``
the experiments :meth:`analysis` instance will be used if
it contains one.
timeout: Time to wait for experiment jobs to finish running before
cancelling.
backend_run: Use backend run (temp option for testing)
run_options: backend runtime options used for circuit execution.

Returns:
Expand All @@ -223,11 +231,31 @@ def run(
ExperimentData container.
"""

if backend is not None or analysis != "default" or run_options:
if (
(backend is not None)
or (sampler is not None)
or analysis != "default"
or run_options
or (backend_run is not None)
):
# Make a copy to update analysis or backend if one is provided at runtime
experiment = self.copy()
if backend:
experiment._set_backend(backend)
if backend_run is not None:
experiment._backend_run = backend_run
# we specified a backend OR a sampler
if (backend is not None) or (sampler is not None):
if sampler is None:
# backend only specified
experiment._set_backend(backend)
elif backend is None:
# sampler only specifid
experiment._set_backend(sampler._backend)
else:
# we specified both a sampler and a backend
if self._backend_run:
experiment._set_backend(backend)
else:
experiment._set_backend(sampler._backend)
if isinstance(analysis, BaseAnalysis):
experiment.analysis = analysis
if run_options:
Expand All @@ -251,7 +279,7 @@ def run(
run_opts = experiment.run_options.__dict__

# Run jobs
jobs = experiment._run_jobs(transpiled_circuits, **run_opts)
jobs = experiment._run_jobs(transpiled_circuits, sampler=sampler, **run_opts)
experiment_data.add_jobs(jobs, timeout=timeout)

# Optionally run analysis
Expand Down Expand Up @@ -333,7 +361,9 @@ def job_info(self, backend: Backend = None):
"Total number of jobs": num_jobs,
}

def _run_jobs(self, circuits: List[QuantumCircuit], **run_options) -> List[Job]:
def _run_jobs(
self, circuits: List[QuantumCircuit], sampler: BaseSamplerV2 = None, **run_options
) -> List[Job]:
"""Run circuits on backend as 1 or more jobs."""
max_circuits = self._max_circuits(self.backend)

Expand All @@ -348,7 +378,43 @@ def _run_jobs(self, circuits: List[QuantumCircuit], **run_options) -> List[Job]:
job_circuits = [circuits]

# Run jobs
jobs = [self.backend.run(circs, **run_options) for circs in job_circuits]
if not self._backend_run:
if sampler is None:
# instantiate a sampler from the backend
sampler = Sampler(self.backend)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Hmm, I was expecting a check for qiskit_ibm_runtime.IBMBackend and giving an error otherwise. I see though that qiskit-ibm-runtime does that check itself and re-routes down a local path if it is passed a backend that is not a qiskit_ibm_runtime.IBMBackend`. I have not played with that. I suppose it works well enough? Aer also has its own primitive implementations that might be more efficient so the tests probably should be switched to those some time.


# have to hand set some of these options
# see https://docs.quantum.ibm.com/api/qiskit-ibm-runtime
# /qiskit_ibm_runtime.options.SamplerExecutionOptionsV2
if "init_qubits" in run_options:
sampler.options.execution.init_qubits = run_options["init_qubits"]
if "rep_delay" in run_options:
sampler.options.execution.rep_delay = run_options["rep_delay"]
if "meas_level" in run_options:
if run_options["meas_level"] == 2:
sampler.options.execution.meas_type = "classified"
elif run_options["meas_level"] == 1:
if "meas_return" in run_options:
if run_options["meas_return"] == "avg":
sampler.options.execution.meas_type = "avg_kerneled"
else:
sampler.options.execution.meas_type = "kerneled"
else:
# assume this is what is wanted if no meas return specified
sampler.options.execution.meas_type = "kerneled"
else:
raise QiskitError("Only meas level 1 + 2 supported by sampler")
if "noise_model" in run_options:
sampler.options.simulator.noise_model = run_options["noise_model"]
if "seed_simulator" in run_options:
sampler.options.simulator.seed_simulator = run_options["seed_simulator"]

if run_options.get("shots") is not None:
sampler.options.default_shots = run_options.get("shots")

jobs = [sampler.run(circs) for circs in job_circuits]
else:
jobs = [self.backend.run(circs, **run_options) for circs in job_circuits]

return jobs

Expand Down
Loading