511 lines
16 KiB
Plaintext
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>
|
|
|