End-to-end examples
Follow these examples to design a Qiskit Runtime algorithm.
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.
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)
from qiskit_aer import AerSimulator # former import: from qiskit import Aer
# Get local simulator backend
backend = AerSimulator()
# Run
result = backend.run(isa_circuits)
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
.
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()
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()
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:
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)
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.
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)
2. Calculate expectation values
Estimator simplifies the user-side syntax compared to the legacy approaches, making it a more
convenient tool for algorithm design.
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
>>> Expectation value: [-0.8879899326312926]
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.
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
>>> print("expectation: ", expectation_value)
expectation: -1.065734058826613
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.
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)
from qiskit_aer import AerSimulator # former import: from qiskit import Aer
# Get local simulator backend
backend = AerSimulator()
# Run
result = backend.run(isa_circuits)
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.
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()
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()
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:
When using the Sampler primitive, the circuit must contain measurements.
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)
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)
2. Get counts from the result
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}
The required steps to reach our goal with backend.run()
are:
- Run circuits
- Get counts from the result object
First, we run the circuit and output the result object:
# Run
result = backend.run(isa_circuit, shots=1024).result()
>>> 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:
counts = result.get_counts(isa_circuit)
>>> print("counts: ", counts)
counts: {'0000': 255, '0001': 258, '0010': 243, '0011': 268}
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: