qiskit-documentation/docs/migration-guides/qiskit-runtime-examples.mdx

511 lines
16 KiB
Plaintext

---
title: End-to-end examples
description: Examples of migrating from using backend.run to using Qiskit Runtime primitives
---
# End-to-end examples
Follow these examples to design a Qiskit Runtime algorithm.
<span id="estimator-algorithm"></span>
## Use Estimator to design an algorithm
Use the Estimator primitive to design an algorithm that calculates expectation values.
**Backend.run() model:** In this model, you accessed real QPUs (quantum processing units) and remote simulators by using the `qiskit-ibmq-provider` or the `qiskit-ibm-provider` module. To run **local** simulations, you could import a specific simulator from `qiskit-aer`. All of them followed the `backend.run()` interface. The following examples assume you have defined `isa_circuits`, circuits that follow the instruction set architecture (ISA) of the backend after undergoing transpilation.
<Tabs>
<TabItem value="provider" label="qiskit-ibm-provider">
``` python
from qiskit_ibm_provider import IBMProvider
# Select provider
provider = IBMProvider()
# Get backend
backend = provider.get_backend("ibmq_qasm_simulator") # cloud simulator
# Run
result = backend.run(isa_circuits)
```
</TabItem>
<TabItem value="aer" label="qiskit-aer">
``` python
from qiskit_aer import AerSimulator # former import: from qiskit import Aer
# Get local simulator backend
backend = AerSimulator()
# Run
result = backend.run(isa_circuits)
```
</TabItem>
</Tabs>
**Primitives model:** Access real QPUs through the `qiskit-ibm-runtime` **primitives** (Sampler and Estimator). Use **Local testing mode** to run local simulations on Qiskit Runtime fake backends or Aer simulators. The following examples assume you have defined circuits `isa_circuits` and observables `isa_observables`.
<Tabs>
<TabItem value="hw" label="Run on QPUs">
``` python
from qiskit_ibm_runtime import EstimatorV2 as Estimator, QiskitRuntimeService
# Define the service. This allows you to access IBM QPUs.
service = QiskitRuntimeService()
# Get a backend
backend = service.least_busy(operational=True, simulator=False)
# Define Estimator
estimator = Estimator(backend)
# Run an expectation value calculation
job = estimator.run([(isa_circuits, isa_observables)])
result = job.result()
```
</TabItem>
<TabItem value="local" label="Run in local testing mode">
``` python
from qiskit_ibm_runtime import EstimatorV2 as Estimator
from qiskit_ibm_runtime.fake_provider import FakeManilaV2
# Run the sampler job locally using FakeManilaV2
fake_manila = FakeManilaV2()
# You can use a fixed seed to get fixed results.
options = {"simulator": {"seed_simulator": 42}}
# Define Estimator
estimator = Estimator(mode=fake_manila, options=options)
# Run an expectation value calculation
job = estimator.run([(isa_circuits, isa_observables)])
result = job.result()
```
</TabItem>
</Tabs>
<span id="estimator-example"></span>
### End-to-end example
If your code previously calculated expectation values using
`backend.run()`, you likely used the `qiskit.opflow` module to
handle operators and state functions. To support this scenario, the
following migration example shows how to replace the `backend.run()` plus `qiskit.opflow` workflow with an Estimator-based workflow.
#### 1. Problem definition
We want to compute the expectation value of a quantum state (circuit)
with respect to a certain operator. This example uses the H2
molecule and an arbitrary circuit as the quantum state:
<Tabs>
<TabItem value="PrimV2" label="Runtime Estimator">
``` python
from qiskit import QuantumCircuit
from qiskit.quantum_info import SparsePauliOp
# Step 1: Define operator
op = SparsePauliOp.from_list(
[
("II", -1.052373245772859),
("IZ", 0.39793742484318045),
("ZI", -0.39793742484318045),
("ZZ", -0.01128010425623538),
("XX", 0.18093119978423156),
]
)
# Step 2: Define quantum state
circuit = QuantumCircuit(2)
circuit.x(0)
circuit.x(1)
# Define a local backend
from qiskit_ibm_runtime.fake_provider import FakeManilaV2
backend = FakeManilaV2()
# Or define a real backend
# from qiskit_ibm_runtime import QiskitRuntimeService
# service = QiskitRuntimeService()
# backend = service.least_busy(operational=True, simulator=False)
# Circuits and parameters must obey the Instruction Set Architecture (ISA) of a particular backend.
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuit = pm.run(circuit)
isa_observable = op.apply_layout(isa_circuit.layout)
```
</TabItem>
<TabItem value="backend.run" label="backend.run">
This code block shows a legacy workflow that handles operators using `qiskit.opflow`. `qiskit.opflow` provided custom classes to represent operators and quantum states (`CircuitStateFn`, `PauliSumOp`), which were required to wrap the standard Qiskit objects. This extra step is not necessary in the updated workflow. Note that in this case we are not running the pass manager to transpile the circuit (and observable) to ISA. This operation had to be performed in the later step, also through custom wrappers that are no longer necessary. For more detailed instructions to migrate from `qiskit.opflow`, see the [Opflow migration guide](./qiskit-opflow-module).
``` python
from qiskit import QuantumCircuit
from qiskit.quantum_info import SparsePauliOp
# Step 1: Define operator
op = SparsePauliOp.from_list(
[
("II", -1.052373245772859),
("IZ", 0.39793742484318045),
("ZI", -0.39793742484318045),
("ZZ", -0.01128010425623538),
("XX", 0.18093119978423156),
]
)
# Step 2: Define quantum state
state = QuantumCircuit(2)
state.x(0)
state.x(1)
# Define provider and simulator backend
from qiskit_ibm_provider import IBMProvider
provider = IBMProvider()
backend = provider.get_backend("ibmq_qasm_simulator")
# Define a statevector simulator
# from qiskit_aer import AerSimulator
# backend = AerSimulator(method="statevector", shots=100)
# Define a real backend
# backend = provider.get_backend("ibm_brisbane")
# Circuits must obey the Instruction Set Architecture (ISA) of a particular backend.
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
from qiskit.opflow import CircuitStateFn, PauliSumOp
opflow_op = PauliSumOp(op)
opflow_state = CircuitStateFn(isa_state)
```
</TabItem>
</Tabs>
#### 2. Calculate expectation values
<Tabs>
<TabItem value="updated" label="Runtime Estimator">
Estimator simplifies the user-side syntax compared to the legacy approaches, making it a more
convenient tool for algorithm design.
``` python
from qiskit_ibm_runtime import EstimatorV2 as Estimator
estimator = Estimator(backend, options={"default_shots": int(1e4)})
job = estimator.run([(isa_circuit, isa_observable)])
# Get results for the first (and only) PUB
pub_result = job.result()[0]
print(f">>> Expectation value: {pub_result.data.evs}")
```
Output
```text
>>> Expectation value: [-0.8879899326312926]
```
</TabItem>
<TabItem value="legacy" label="backend.run()">
The legacy workflow required additional elements and steps to compute an expectation value. The `QuantumInstance` class stored a `PassManager` and a `Backend` instance, and wrapped the conversion step to ISA circuits and `backend.run` calls. The `CircuitSampler` class from `qiskit.opflow` wrapped the `QuantumInstance`, `run`, and `transpile` methods. This degree of nesting is no longer supported; the new workflow allows you to access and directly manipulate all the key components of the computation (backend, pass manager, circuits and observables) at all stages of the algorithm.
``` python
from qiskit.opflow import StateFn, PauliExpectation, CircuitSampler
from qiskit.utils import QuantumInstance
# Define the state to sample
measurable_expression = StateFn(opflow_op, is_measurement=True).compose(opflow_state)
# Convert to expectation value calculation object
expectation = PauliExpectation().convert(measurable_expression)
# Define quantum instance with backend and pass manager
quantum_instance = QuantumInstance(backend, pass_manager = pm)
# Inject quantum instance into circuit sampler and convert
sampler = CircuitSampler(quantum_instance).convert(expectation)
# Evaluate
expectation_value = sampler.eval().real
```
``` python
>>> print("expectation: ", expectation_value)
expectation: -1.065734058826613
```
</TabItem>
</Tabs>
<span id="sampler-algorithm"></span>
## Use Sampler to design an algorithm
The Sampler primitive is used to design an algorithm that samples
circuits and extracts probability distributions.
Both `Sampler` and `backend.run()` take in circuits as inputs. The main
difference is the format of the output: `backend.run()` outputs
**counts**, while `Sampler` returns **per-shot measurements** (but has convenience methods to also return counts).
**Backend.run() model:** In this model, you used the `qiskit-ibmq-provider` or the `qiskit-ibm-provider` module to access real QPUs and remote simulators. To run **local** simulations, you could import a specific simulator from `qiskit-aer`. All of them followed the `backend.run()` interface.
<Tabs>
<TabItem value="provider" label="qiskit-ibm-provider">
``` python
from qiskit_ibm_provider import IBMProvider
# Select provider
provider = IBMProvider()
# Get backend
backend = provider.get_backend("ibmq_qasm_simulator") # Use the cloud simulator
# Run
result = backend.run(isa_circuits)
```
</TabItem>
<TabItem value="aer" label="qiskit-aer">
``` python
from qiskit_aer import AerSimulator # former import: from qiskit import Aer
# Get local simulator backend
backend = AerSimulator()
# Run
result = backend.run(isa_circuits)
```
</TabItem>
</Tabs>
**Primitives model:** Access real QPUs through the `qiskit-ibm-runtime` Sampler and Estimator *primitives*. Use **local testing mode** to run local simulations by using Qiskit Runtime fake backends or Aer simulators.
<Tabs>
<TabItem value="hw" label="Run on QPUs">
``` python
from qiskit_ibm_runtime import SamplerV2 as Sampler, QiskitRuntimeService
# Define the service. This allows you to access IBM QPUs.
service = QiskitRuntimeService()
# Get a backend
backend = service.least_busy(operational=True, simulator=False)
# Define Sampler
sampler = Sampler(mode=backend)
# Run calculation
job = sampler.run([isa_circuit])
result = job.result()
```
</TabItem>
<TabItem value="local" label="Run in local testing mode">
``` python
from qiskit_ibm_runtime import SamplerV2 as Sampler
from qiskit_ibm_runtime.fake_provider import FakeManilaV2
# Run the sampler job locally using FakeManilaV2
fake_manila = FakeManilaV2()
# You can use a fixed seed to get fixed results.
options = {"simulator": {"seed_simulator": 42}}
# Define Sampler
sampler = Sampler(mode=fake_manila, options=options)
# Run calculation
job = sampler.run([isa_circuit])
result = job.result()
```
</TabItem>
</Tabs>
<span id="sampler-example"></span>
### End-to-end example
The following example shows an end-to-end example of sampling a circuit by using `backend.run()` and Sampler.
#### 1. Problem definition
We want to find the probability distribution
associated with a quantum state:
<Admonition type="attention" title="Important">
When using the Sampler primitive, the circuit **must contain measurements**.
</Admonition>
<Tabs>
<TabItem value="PrimV2" label="Runtime Sampler">
``` python
from qiskit_ibm_runtime import SamplerV2 as Sampler
# Define a local backend
from qiskit_ibm_runtime.fake_provider import FakeManilaV2
backend = FakeManilaV2()
# Define a real backend
# from qiskit_ibm_runtime import QiskitRuntimeService
# service = QiskitRuntimeService()
# backend = service.least_busy(operational=True, simulator=False)
from qiskit import QuantumCircuit
circuit = QuantumCircuit(4)
circuit.h(range(2))
circuit.cx(0,1)
circuit.measure_all() # measurement!
# Circuits must obey the ISA of the backend.
# Convert to ISA circuits
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuit = pm.run(circuit)
```
</TabItem>
<TabItem value="PrimV1" label="backend.run">
``` python
from qiskit import QuantumCircuit
circuit = QuantumCircuit(4)
circuit.h(range(2))
circuit.cx(0,1)
circuit.measure_all() # measurement!
# Define provider and simulator backend
from qiskit_ibm_provider import IBMProvider
provider = IBMProvider(instance="ibm-q/open/main")
backend = provider.get_backend("ibmq_qasm_simulator")
# Define a statevector simulator
# from qiskit_aer import AerSimulator
# backend = AerSimulator(method="statevector", shots=100)
# Define a real backend
# backend = provider.get_backend("ibm_brisbane")
# Circuits must obey the ISA of a particular backend.
# Convert to ISA circuits
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuit = pm.run(circuit)
```
</TabItem>
</Tabs>
#### 2. Get counts from the result
<Tabs>
<TabItem value="runtime" label="Runtime Sampler">
``` python
sampler = Sampler(mode=backend)
job = sampler.run([isa_circuit])
result = job.result()
# Get results for the first (and only) PUB
pub_result = result[0]
# Get counts from the classical register "meas".
print(f" >> Meas output register counts: {pub_result.data.meas.get_counts()}")
>> Meas output register counts: {'0001': 210, '0010': 305, '0000': 282, '0011': 201, '0101': 2, '1010': 6, '0110': 5, '0100': 6, '1000': 3, '0111': 1, '1001': 2, '1011': 1}
```
</TabItem>
<TabItem value="legacy" label="backend.run()">
The required steps to reach our goal with `backend.run()` are:
1. Run circuits
2. Get counts from the result object
First, we run the circuit and output the result
object:
``` python
# Run
result = backend.run(isa_circuit, shots=1024).result()
```
``` python
>>> print("result: ", result)
result: Result(backend_name='ibmq_qasm_simulator', backend_version='0.11.0',
qobj_id='65bb8a73-cced-40c1-995a-8961cc2badc4', job_id='63fc95612751d57b6639f777',
success=True, results=[ExperimentResult(shots=1024, success=True, meas_level=2,
data=ExperimentResultData(counts={'0x0': 255, '0x1': 258, '0x2': 243, '0x3': 268}),
header=QobjExperimentHeader(clbit_labels=[['meas', 0], ['meas', 1], ['meas', 2], ['meas', 3]],
creg_sizes=[['meas', 4]], global_phase=0.0, memory_slots=4, metadata={}, n_qubits=4,
name='circuit-930', qreg_sizes=[['q', 4]], qubit_labels=[['q', 0], ['q', 1], ['q', 2], ['q', 3]]),
status=DONE, metadata={'active_input_qubits': [0, 1, 2, 3], 'batched_shots_optimization': False,
'device': 'CPU', 'fusion': {'enabled': False}, 'input_qubit_map': [[3, 3], [2, 2], [1, 1], [0, 0]],
'measure_sampling': True, 'method': 'stabilizer', 'noise': 'ideal', 'num_clbits': 4, 'num_qubits': 4,
'parallel_shots': 1, 'parallel_state_update': 16, 'remapped_qubits': False,
'sample_measure_time': 0.001001096}, seed_simulator=2191402198, time_taken=0.002996865)],
date=2023-02-27 12:35:00.203255+01:00, status=COMPLETED, header=QobjHeader(backend_name='ibmq_qasm_simulator',
backend_version='0.1.547'), metadata={'max_gpu_memory_mb': 0, 'max_memory_mb': 386782, 'mpi_rank': 0,
'num_mpi_processes': 1, 'num_processes_per_experiments': 1, 'omp_enabled': True, 'parallel_experiments': 1,
'time_taken': 0.003215252, 'time_taken_execute': 0.00303248, 'time_taken_load_qobj': 0.000169435},
time_taken=0.003215252, client_version={'qiskit': '0.39.5'})
```
Now we get the probability distribution from the output:
``` python
counts = result.get_counts(isa_circuit)
>>> print("counts: ", counts)
counts: {'0000': 255, '0001': 258, '0010': 243, '0011': 268}
```
</TabItem>
</Tabs>
## Next steps
The Runtime primitives offer a series of features and tuning
options, some of which do not have a legacy alternative to migrate from, but can
help improve your performance and results. For more information, refer
to the following:
<Admonition type="tip" title="Recommendations">
- [Common use cases](qiskit-runtime-use-case)
- [Migrate `backend.run` options to primitive options](qiskit-runtime-options)
- [Setting execution options](/guides/runtime-options-overview)
- [How to run a session](/guides/run-jobs-session)
- [Qiskit Runtime local testing mode](/guides/local-testing-mode)
</Admonition>