qiskit-documentation/docs/migration-guides/qiskit-quantum-instance.mdx

595 lines
29 KiB
Plaintext

---
title: QuantumInstance migration guide
description: Stop using the deprecated Qiskit `QuantumInstance` class
---
# QuantumInstance migration guide
<Admonition type="caution">
**Deprecation Notice**
This guide precedes the introduction of the V2 primitives interface. It was created to guide users from `QuantumInstance` to
the V1 primitives interface. Following the introduction of the V2 primitives, some providers have deprecated V1 primitive
implementations in favor of the V2 alternatives. If you are interested in following this guide, we recommend combining it with the
[Migrate to V2 primitives](./v2-primitives) guide to bring your code to the most updated state.
</Admonition>
The [`qiskit.utils.QuantumInstance`](/api/qiskit/0.46/qiskit.utils.QuantumInstance) is a utility class that allows the joint
configuration of the circuit transpilation and execution steps, and provides functions
at a higher level of abstraction for a more convenient integration with algorithms.
These include measurement error mitigation, splitting and combining execution to
conform to job limits,
and ensuring reliable circuit execution with additional job management tools.
The [`qiskit.utils.QuantumInstance`](/api/qiskit/0.46/qiskit.utils.QuantumInstance) class is being deprecated because
the functionality of [`qiskit.utils.QuantumInstance.execute`](/api/qiskit/0.46/qiskit.utils.QuantumInstance#execute) has been delegated to
the different implementations of the [`qiskit.primitives`](/api/qiskit/primitives) base classes.
The following table summarizes the migration alternatives for the [`qiskit.utils.QuantumInstance`](/api/qiskit/0.46/qiskit.utils.QuantumInstance) class:
| QuantumInstance method | Alternative |
| --- | --- |
| [`qiskit.utils.QuantumInstance.execute`](/api/qiskit/0.46/qiskit.utils.QuantumInstance#execute) | [`qiskit.primitives.Sampler.run`](/api/qiskit/qiskit.primitives.Sampler#run) or [`qiskit.primitives.Estimator.run`](/api/qiskit/qiskit.primitives.Estimator#run) |
| [`qiskit.utils.QuantumInstance.transpile`](/api/qiskit/0.46/qiskit.utils.QuantumInstance#transpile) | [`qiskit.compiler.transpile`](/api/qiskit/compiler#qiskit.compiler.transpile) or [`qiskit.transpiler.pass_manager.PassManager.run`](/api/qiskit/qiskit.transpiler.PassManager#run)|
| [`qiskit.utils.QuantumInstance.assemble`](/api/qiskit/0.46/qiskit.utils.QuantumInstance#assemble) | [`qiskit.compiler.assemble`](/api/qiskit/compiler#qiskit.compiler.assemble) [Deprecated: no longer part of the workflow]|
The remainder of this guide focused on the [`qiskit.utils.QuantumInstance.execute`](/api/qiskit/0.46/qiskit.utils.QuantumInstance#execute) to
[`qiskit.primitives`](/api/qiskit/primitives) migration path.
<Admonition type="caution">
**Background on the Qiskit primitives**
The Qiskit primitives are algorithmic abstractions that encapsulate system or simulator access for easy integration into algorithm workflows.
There are two types of primitives: Sampler and Estimator.
Qiskit provides reference implementations in [`qiskit.primitives.Sampler`](/api/qiskit/qiskit.primitives.Sampler) and [`qiskit.primitives.Estimator`](/api/qiskit/qiskit.primitives.Estimator). Additionally,
[`qiskit.primitives.BackendSampler`](/api/qiskit/qiskit.primitives.BackendSampler) and [`qiskit.primitives.BackendEstimator`](/api/qiskit/qiskit.primitives.BackendEstimator) are
wrappers for `backend.run()` that follow the primitives interface.
Providers can implement these primitives as subclasses of [`qiskit.primitives.BaseSampler`](/api/qiskit/0.46/qiskit.primitives.BaseSampler) and [`qiskit.primitives.BaseEstimator`](/api/qiskit/0.46/qiskit.primitives.BaseEstimator), respectively.
Qiskit Runtime ([`qiskit_ibm_runtime`](/api/qiskit-ibm-runtime/qiskit_ibm_runtime.QiskitRuntimeService)) and Aer ([`qiskit_aer.primitives`](https://qiskit.org/ecosystem/aer/apidocs/aer_primitives.html)) are examples of native implementations of primitives.
This guide uses the following naming convention:
- *Primitives* - Any Sampler or Estimator implementation using base classes [`qiskit.primitives.BackendSampler`](/api/qiskit/qiskit.primitives.BackendSampler) and a [`qiskit.primitives.BackendEstimator`](/api/qiskit/qiskit.primitives.BackendEstimator).
- *Reference primitives* - [`qiskit.primitives.Sampler`](/api/qiskit/qiskit.primitives.Sampler) and [`qiskit.primitives.Estimator`](/api/qiskit/qiskit.primitives.Estimator) are reference implementations that come with Qiskit.
- *Aer primitives* - The [Aer](https://qiskit.org/ecosystem/aer) primitive implementations [`qiskit_aer.primitives.Sampler`](https://qiskit.org/ecosystem/aer/stubs/qiskit_aer.primitives.Sampler.html) and [`qiskit_aer.primitives.Estimator`](https://qiskit.org/ecosystem/aer/stubs/qiskit_aer.primitives.Estimator.html).
- *Qiskit Runtime primitives* - The Qiskit Runtime primitive implementations [`qiskit_ibm_runtime.Sampler`](/api/qiskit-ibm-runtime/0.26/qiskit_ibm_runtime.Sampler) and [`qiskit_ibm_runtime.Estimator`](/api/qiskit-ibm-runtime/0.26/qiskit_ibm_runtime.Estimator).
- *`Backend` primitives* - Instances of [`qiskit.primitives.BackendSampler`](/api/qiskit/qiskit.primitives.BackendSampler) and [`qiskit.primitives.BackendEstimator`](/api/qiskit/qiskit.primitives.BackendEstimator). These allow any processor to implement primitive interfaces.
</Admonition>
## Choose the right primitive for your task
The [`qiskit.utils.QuantumInstance`](/api/qiskit/0.46/qiskit.utils.QuantumInstance) was designed to be an abstraction of transpile and run.
It took inspiration from [`qiskit.execute_function.execute`](/api/qiskit/0.46/execute#qiskit.execute_function.execute) but retained configuration information that could be set
at the algorithm level to save the user from defining the same parameters for every transpile or execute call.
The [`qiskit.primitives`](/api/qiskit/primitives) classes share some of these features, but unlike the [`qiskit.utils.QuantumInstance`](/api/qiskit/0.46/qiskit.utils.QuantumInstance),
there are multiple primitive classes, and each is optimized for a specific
purpose. Selecting the right primitive (Sampler or Estimator) requires some knowledge about
**what** it is expected to do and **where** or **how** it is expected to run.
<Admonition type="note">
Primitives are also **algorithmic** abstractions with defined tasks:
- The Estimator takes in circuits and observables and returns **expectation values**.
- The Sampler takes in circuits, measures them, and returns their **quasi-probability distributions**.
</Admonition>
To determine which primitive to use instead of [`qiskit.utils.QuantumInstance`](/api/qiskit/0.46/qiskit.utils.QuantumInstance), you should ask
yourself two questions:
1. What is the minimal unit of information used by your algorithm?
* If it uses an expectation value, you need an Estimator.
* If it uses a probability distribution (from sampling the device), you need a Sampler
2. How do you want to run your circuits?
This question is not new. In the legacy algorithm workflow, you would set up a
[`qiskit.utils.QuantumInstance`](/api/qiskit/0.46/qiskit.utils.QuantumInstance) with either a real system from a provider, or a simulator.
For this migration, this "system selection" process is translated to **where** do you import your primitives from:
* Using **local** statevector simulators for quick prototyping: **Reference primitives**
* Using **local** noisy simulations for finer algorithm tuning: **Aer primitives**
* Accessing **runtime-enabled systems** (or cloud simulators): **Qiskit Runtime primitives**
* Accessing **non runtime-enabled systems** : **`Backend` primitives**
Arguably, the Sampler is the closest primitive to [`qiskit.utils.QuantumInstance`](/api/qiskit/0.46/qiskit.utils.QuantumInstance), as they
both execute circuits and provide a result. However, with the [`qiskit.utils.QuantumInstance`](/api/qiskit/0.46/qiskit.utils.QuantumInstance),
the result data was system-dependent (it could be a counts `dict`, a `numpy.array` for
statevector simulations, and so on), while Sampler normalizes its `SamplerResult` to
return a [`qiskit.result.QuasiDistribution`](/api/qiskit/qiskit.result.QuasiDistribution) object with the resulting quasi-probability distribution.
The Estimator provides a specific abstraction for the expectation value calculation that can replace
[`qiskit.utils.QuantumInstance`](/api/qiskit/0.46/qiskit.utils.QuantumInstance) as well as the associated pre- and post-processing steps, usually performed
with an additional library such as [`qiskit.opflow`](/api/qiskit/0.46/opflow).
## Choose the right primitive for your settings
Certain [`qiskit.utils.QuantumInstance`](/api/qiskit/0.46/qiskit.utils.QuantumInstance) features are only available in certain primitive implementations.
The following table summarizes the most common [`qiskit.utils.QuantumInstance`](/api/qiskit/0.46/qiskit.utils.QuantumInstance) settings and which
primitives expose a similar setting through their interface:
<Admonition type="caution">
In some cases, a setting might not be exposed through the interface, but there might be an alternative path to make
it work. This is the case for custom transpiler passes, which cannot be set through the primitives interface,
but pre-transpiled circuits can be sent if you specify the option `skip_transpilation=True`. For more information,
refer to the API reference or source code of the desired primitive implementation.
</Admonition>
| QuantumInstance | Reference Primitives | Aer Primitives | Qiskit Runtime Primitives | `Backend` Primitives |
| --- | --- | --- | --- | --- |
| Select `backend` | No | No | Yes | Yes |
| Set `shots` | Yes | Yes | Yes | Yes |
| Simulator settings: `basis_gates`, `coupling_map`, `initial_layout`, `noise_model`, `backend_options` | No | Yes | Yes | No (inferred from internal `backend`) |
| Transpiler settings: `seed_transpiler`, `optimization_level` | No | No | Yes (via `options`) (*) | Yes (via `.set_transpile_options()`) |
| Set unbound `pass_manager` | No | No | No (but can `skip_transpilation`) | No (but can `skip_transpilation`) |
| Set `bound_pass_manager` | No | No | No | Yes |
| Set `backend_options`: common ones were `memory` and `meas_level` | No | No | No (only `qubit_layout`) | No |
| Measurement error mitigation: `measurement_error_mitigation_cls`, `cals_matrix_refresh_period`, | No | No | Sampler default > M3 (*) | No |
| Job management: `job_callback`, `max_job_retries`, `timeout`, `wait` | Does not apply | Does not apply | Sessions, callback (**) | No |
(\*) For more information on error mitigation and setting options on Qiskit Runtime Primitives, see
[Advanced Runtime Options](/guides/runtime-options-overview).
(\*\*) For more information on Runtime sessions, see [About Sessions](/guides/sessions).
## Code examples
### Example 1: Circuit sampling with local simulation
**QuantumInstance**
The only option for local simulations using the quantum instance was using an Aer simulator.
If no simulation method is specified, the Aer simulator defaults to an exact simulation
(statevector/stabilizer), if shots are specified, it adds shot noise.
Note that `QuantumInstance.execute()` returned the counts in hexadecimal format.
```python
from qiskit import QuantumCircuit
from qiskit_aer import AerSimulator
from qiskit.utils import QuantumInstance
circuit = QuantumCircuit(2)
circuit.x(0)
circuit.x(1)
circuit.measure_all()
simulator = AerSimulator()
qi = QuantumInstance(backend=simulator, shots=200)
result = qi.execute(circuit).results[0]
data = result.data
counts = data.counts
print("Counts: ", counts)
print("Data: ", data)
print("Result: ", result)
```
```text
Counts: {'0x3': 200}
Data: ExperimentResultData(counts={'0x3': 200})
Result: ExperimentResult(shots=200, success=True, meas_level=2, data=ExperimentResultData(counts={'0x3': 200}), header=QobjExperimentHeader(clbit_labels=[['meas', 0], ['meas', 1]], creg_sizes=[['meas', 2]], global_phase=0.0, memory_slots=2, metadata={}, n_qubits=2, name='circuit-99', qreg_sizes=[['q', 2]], qubit_labels=[['q', 0], ['q', 1]]), status=DONE, seed_simulator=2846213898, metadata={'parallel_state_update': 16, 'parallel_shots': 1, 'sample_measure_time': 0.00025145, 'noise': 'ideal', 'batched_shots_optimization': False, 'remapped_qubits': False, 'device': 'CPU', 'active_input_qubits': [0, 1], 'measure_sampling': True, 'num_clbits': 2, 'input_qubit_map': [[1, 1], [0, 0]], 'num_qubits': 2, 'method': 'stabilizer', 'fusion': {'enabled': False}}, time_taken=0.000672166)
```
**Primitives**
The primitives offer two alternatives for local simulation, one with the Reference primitives
and one with the Aer primitives. As mentioned above, the closest alternative to `QuantumInstance.execute()`
for sampling is the Sampler primitive.
**a. Reference primitives**
Basic simulation implemented using the [`qiskit.quantum_info`](/api/qiskit/quantum_info) module. If shots are
specified, the results include shot noise. Note that
the resulting quasi-probability distribution does not use bitstrings, but integers to identify the states.
```python
from qiskit import QuantumCircuit
from qiskit.primitives import Sampler
circuit = QuantumCircuit(2)
circuit.x(0)
circuit.x(1)
circuit.measure_all()
sampler = Sampler()
result = sampler.run(circuit, shots=200).result()
quasi_dists = result.quasi_dists
print("Quasi-dists: ", quasi_dists)
print("Result: ", result)
```
```text
Quasi-dists: [{3: 1.0}]
Result: SamplerResult(quasi_dists=[{3: 1.0}], metadata=[{'shots': 200}])
```
**b. Aer primitives**
This method uses Aer simulation following the statevector method. This is a closer replacement of the
[`qiskit.utils.QuantumInstance`](/api/qiskit/0.46/qiskit.utils.QuantumInstance)
example, as they are access the same simulator. Note that
the resulting quasi-probability distribution does not use bitstrings but integers to identify the states.
<Admonition type="note">
The [`qiskit.result.QuasiDistribution`](/api/qiskit/qiskit.result.QuasiDistribution) class that is returned as part of the [`qiskit.primitives.SamplerResult`](/api/qiskit/qiskit.primitives.SamplerResult)
exposes two methods to convert the result keys from integer to binary strings / hexadecimal:
- [`qiskit.result.QuasiDistribution.binary_probabilities`](/api/qiskit/qiskit.result.QuasiDistribution#binary_probabilities)
- [`qiskit.result.QuasiDistribution.hex_probabilities`](/api/qiskit/qiskit.result.QuasiDistribution#hex_probabilities)
</Admonition>
```python
from qiskit import QuantumCircuit
from qiskit_aer.primitives import Sampler
circuit = QuantumCircuit(2)
circuit.x(0)
circuit.x(1)
circuit.measure_all()
# If a noise model is provided, the Aer primitives
# perform an exact (statevector) simulation
sampler = Sampler()
result = sampler.run(circuit, shots=200).result()
quasi_dists = result.quasi_dists
# convert keys to binary bitstrings
binary_dist = quasi_dists[0].binary_probabilities()
print("Quasi-dists: ", quasi_dists)
print("Result: ", result)
print("Binary quasi-dist: ", binary_dist)
```
```text
Quasi-dists: [{3: 1.0}]
Result: SamplerResult(quasi_dists=[{3: 1.0}], metadata=[{'shots': 200, 'simulator_metadata': {'parallel_state_update': 16, 'parallel_shots': 1, 'sample_measure_time': 9.016e-05, 'noise': 'ideal', 'batched_shots_optimization': False, 'remapped_qubits': False, 'device': 'CPU', 'active_input_qubits': [0, 1], 'measure_sampling': True, 'num_clbits': 2, 'input_qubit_map': [[1, 1], [0, 0]], 'num_qubits': 2, 'method': 'statevector', 'fusion': {'applied': False, 'max_fused_qubits': 5, 'threshold': 14, 'enabled': True}}}])
Binary quasi-dist: {'11': 1.0}
```
### Example 2: Expectation value calculation with local noisy simulation
While this example does not include a direct call to `QuantumInstance.execute()`, it shows
how to migrate from a [`qiskit.utils.QuantumInstance`](/api/qiskit/0.46/qiskit.utils.QuantumInstance)-based to a [`qiskit.primitives`](/api/qiskit/primitives)-based
workflow.
**QuantumInstance**
The most common use case for computing expectation values with the Quantum Instance was as in combination with the
[`qiskit.opflow`](/api/qiskit/0.46/opflow) library. You can see more information in the [opflow migration guide](./qiskit-opflow-module).
```python
from qiskit import QuantumCircuit
from qiskit.opflow import StateFn, PauliSumOp, PauliExpectation, CircuitSampler
from qiskit.utils import QuantumInstance
from qiskit_aer import AerSimulator
from qiskit_aer.noise import NoiseModel
from qiskit_ibm_provider import IBMProvider
# Define problem using opflow
op = PauliSumOp.from_list([("XY",1)])
qc = QuantumCircuit(2)
qc.x(0)
qc.x(1)
state = StateFn(qc)
measurable_expression = StateFn(op, is_measurement=True).compose(state)
expectation = PauliExpectation().convert(measurable_expression)
# Define QuantumInstance with a noisy simulator
provider = IBMProvider()
device = provider.get_backend("ibmq_manila")
noise_model = NoiseModel.from_backend(device)
coupling_map = device.configuration().coupling_map
backend = AerSimulator()
qi = QuantumInstance(backend=backend, shots=1024,
seed_simulator=42, seed_transpiler=42,
coupling_map=coupling_map, noise_model=noise_model)
# Run
sampler = CircuitSampler(qi).convert(expectation)
expectation_value = sampler.eval().real
print(expectation_value)
```
```text
-0.04687500000000008
```
**Primitives**
The primitives allow the combination of the opflow and QuantumInstance functionality in a single Estimator.
In this case, for local noisy simulation, this will be the Aer estimator.
```python
from qiskit import QuantumCircuit
from qiskit.quantum_info import SparsePauliOp
from qiskit_aer.noise import NoiseModel
from qiskit_aer.primitives import Estimator
from qiskit_ibm_provider import IBMProvider
# Define problem
op = SparsePauliOp("XY")
qc = QuantumCircuit(2)
qc.x(0)
qc.x(1)
# Define Aer Estimator with noisy simulator
device = provider.get_backend("ibmq_manila")
noise_model = NoiseModel.from_backend(device)
coupling_map = device.configuration().coupling_map
# If a noise model is provided, the Aer primitives
# perform a "qasm" simulation
estimator = Estimator(
backend_options={ # method chosen automatically to match options
"coupling_map": coupling_map,
"noise_model": noise_model,
},
run_options={"seed": 42, "shots": 1024},
transpile_options={"seed_transpiler": 42},
)
# Run
expectation_value = estimator.run(qc, op).result().values
print(expectation_value)
```
```python
[-0.04101562]
```
### Example 3: Circuit sampling on IBM system with error mitigation
**QuantumInstance**
The `QuantumInstance` interface allowed configuring measurement error mitigation settings such as the method, the
matrix refresh period, or the mitigation pattern. This configuration is no longer available in the primitives
interface.
```python
from qiskit import QuantumCircuit
from qiskit.utils import QuantumInstance
from qiskit.utils.mitigation import CompleteMeasFitter
from qiskit_ibm_provider import IBMProvider
circuit = QuantumCircuit(2)
circuit.x(0)
circuit.x(1)
circuit.measure_all()
provider = IBMProvider()
backend = provider.get_backend("ibmq_montreal")
qi = QuantumInstance(
backend=backend,
shots=4000,
measurement_error_mitigation_cls=CompleteMeasFitter,
cals_matrix_refresh_period=0,
)
result = qi.execute(circuit).results[0].data
print(result)
```
```python
ExperimentResultData(counts={'11': 4000})
```
**Primitives**
The Qiskit Runtime primitives offer a suite of error mitigation methods that can be easily turned on with the
`resilience_level` option. These are, however, not configurable. The sampler's `resilience_level=1`
is the closest alternative to the QuantumInstance measurement error mitigation implementation, but this
is not a one-to-one replacement.
For more information about the error mitigation options in the Qiskit Runtime primitives, see [Configure Error Mitigation](/guides/configure-error-mitigation).
```python
from qiskit import QuantumCircuit
from qiskit_ibm_runtime import QiskitRuntimeService, Sampler, Options
circuit = QuantumCircuit(2)
circuit.x(0)
circuit.x(1)
circuit.measure_all()
service = QiskitRuntimeService(channel="ibm_quantum")
backend = service.backend("ibmq_montreal")
options = Options(resilience_level = 1) # 1 = measurement error mitigation
sampler = Sampler(session=backend, options=options)
# Run
result = sampler.run(circuit, shots=4000).result()
quasi_dists = result.quasi_dists
print("Quasi dists: ", quasi_dists)
```
```text
Quasi dists: [{2: 0.0008492371522941081, 3: 0.9968874384378738, 0: -0.0003921227905920063,
1: 0.002655447200424097}]
```
### Example 4: Circuit sampling with custom bound and unbound pass managers
Transpilation management is different between `QuantumInstance` and the primitives.
QuantumInstance allowed you to:
* Define bound and unbound pass managers that were called during `.execute()`.
* Explicitly call its `.transpile()` method with a specific pass manager.
QuantumInstance **did not** manage parameter bindings on parametrized quantum circuits. Therefore, if a `bound_pass_manager` was set, the circuit sent to `QuantumInstance.execute()` could
not have any free parameters.
When using the primitives:
* You cannot explicitly access their transpilation routine.
* The mechanism to apply custom transpilation passes to the Aer, Runtime, and `Backend` primitives is to pre-transpile
locally and set `skip_transpilation=True` in the corresponding primitive.
* The only primitives that accept a custom **bound** transpiler pass manager are instances of [`qiskit.primitives.BackendSampler`](/api/qiskit/qiskit.primitives.BackendSampler) or [`qiskit.primitives.BackendEstimator`](/api/qiskit/qiskit.primitives.BackendEstimator).
If a `bound_pass_manager` is defined, the `skip_transpilation=True` option does **not** skip this bound pass.
<Admonition type="caution">
Care is needed when setting `skip_transpilation=True` with the Estimator primitive.
Since operator and circuit size need to match for the Estimator, if the custom transpilation changes
the circuit size, the operator must be adapted before sending it
to the Estimator, as there is no mechanism to identify the active qubits it should consider.
</Admonition>
Note that the primitives do handle parameter bindings, so that even if a `bound_pass_manager` is defined in a
[`qiskit.primitives.BackendSampler`](/api/qiskit/qiskit.primitives.BackendSampler) or [`qiskit.primitives.BackendEstimator`](/api/qiskit/qiskit.primitives.BackendEstimator), you do not have to manually assign parameters as expected in the QuantumInstance workflow.
The two-stage transpilation was added to the `QuantumInstance` to allow running pulse-efficient transpilation passes with the [`qiskit.opflow.converters.CircuitSampler`](/api/qiskit/0.46/qiskit.opflow.converters.CircuitSampler) class. The following
example shows how to migrate this use case, where the `QuantumInstance.execute()` method is called by the [`qiskit.opflow.converters.CircuitSampler`](/api/qiskit/0.46/qiskit.opflow.converters.CircuitSampler) code.
**QuantumInstance**
```python
from qiskit.circuit.library.standard_gates.equivalence_library import StandardEquivalenceLibrary as std_eqlib
from qiskit.circuit.library import RealAmplitudes
from qiskit.opflow import CircuitSampler, StateFn
from qiskit.providers.fake_provider import FakeBelem
from qiskit.transpiler import PassManager, PassManagerConfig, CouplingMap
from qiskit.transpiler.preset_passmanagers import level_1_pass_manager
from qiskit.transpiler.passes import (
Collect2qBlocks, ConsolidateBlocks, Optimize1qGatesDecomposition,
RZXCalibrationBuilderNoEcho, UnrollCustomDefinitions, BasisTranslator
)
from qiskit.transpiler.passes.optimization.echo_rzx_weyl_decomposition import EchoRZXWeylDecomposition
from qiskit.utils import QuantumInstance
# Define backend
backend = FakeBelem()
# Build the pass manager for the parameterized circuit
rzx_basis = ['rzx', 'rz', 'x', 'sx']
coupling_map = CouplingMap(backend.configuration().coupling_map)
config = PassManagerConfig(basis_gates=rzx_basis, coupling_map=coupling_map)
pre = level_1_pass_manager(config)
inst_map = backend.defaults().instruction_schedule_map
# Build a pass manager for the CX decomposition (works only on bound circuits)
post = PassManager([
# Consolidate consecutive two-qubit operations.
Collect2qBlocks(),
ConsolidateBlocks(basis_gates=['rz', 'sx', 'x', 'rxx']),
# Rewrite circuit in terms of Weyl-decomposed echoed RZX gates.
EchoRZXWeylDecomposition(inst_map),
# Attach scaled CR pulse schedules to the RZX gates.
RZXCalibrationBuilderNoEcho(inst_map),
# Simplify single-qubit gates.
UnrollCustomDefinitions(std_eqlib, rzx_basis),
BasisTranslator(std_eqlib, rzx_basis),
Optimize1qGatesDecomposition(rzx_basis),
])
# Instantiate qi
quantum_instance = QuantumInstance(backend, pass_manager=pre, bound_pass_manager=post)
# Define parametrized circuit and parameter values
qc = RealAmplitudes(2)
print(qc.decompose())
param_dict = {p: 0.5 for p in qc.parameters}
# Instantiate CircuitSampler
sampler = CircuitSampler(quantum_instance)
# Run
quasi_dists = sampler.convert(StateFn(qc), params=param_dict).sample()
print("Quasi-dists: ", quasi_dists)
```
```text
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
q_0: ┤ Ry(θ[0]) ├──■──┤ Ry(θ[2]) ├──■──┤ Ry(θ[4]) ├──■──┤ Ry(θ[6]) ├
├──────────┤┌─┴─┐├──────────┤┌─┴─┐├──────────┤┌─┴─┐├──────────┤
q_1: ┤ Ry(θ[1]) ├┤ X ├┤ Ry(θ[3]) ├┤ X ├┤ Ry(θ[5]) ├┤ X ├┤ Ry(θ[7]) ├
└──────────┘└───┘└──────────┘└───┘└──────────┘└───┘└──────────┘
Quasi-dists: {'11': 0.443359375, '10': 0.21875, '01': 0.189453125, '00': 0.1484375}
```
**Primitives**
Let's see how the workflow changes with the `Backend` Sampler:
```python
from qiskit.circuit.library.standard_gates.equivalence_library import StandardEquivalenceLibrary as std_eqlib
from qiskit.circuit.library import RealAmplitudes
from qiskit.primitives import BackendSampler
from qiskit.providers.fake_provider import FakeBelem
from qiskit.transpiler import PassManager, PassManagerConfig, CouplingMap
from qiskit.transpiler.preset_passmanagers import level_1_pass_manager
from qiskit.transpiler.passes import (
Collect2qBlocks, ConsolidateBlocks, Optimize1qGatesDecomposition,
RZXCalibrationBuilderNoEcho, UnrollCustomDefinitions, BasisTranslator
)
from qiskit.transpiler.passes.optimization.echo_rzx_weyl_decomposition import EchoRZXWeylDecomposition
# Define backend
backend = FakeBelem()
# Build the pass manager for the parameterized circuit
rzx_basis = ['rzx', 'rz', 'x', 'sx']
coupling_map = CouplingMap(backend.configuration().coupling_map)
config = PassManagerConfig(basis_gates=rzx_basis, coupling_map=coupling_map)
pre = level_1_pass_manager(config)
# Build a pass manager for the CX decomposition (works only on bound circuits)
inst_map = backend.defaults().instruction_schedule_map
post = PassManager([
# Consolidate consecutive two-qubit operations.
Collect2qBlocks(),
ConsolidateBlocks(basis_gates=['rz', 'sx', 'x', 'rxx']),
# Rewrite circuit in terms of Weyl-decomposed echoed RZX gates.
EchoRZXWeylDecomposition(inst_map),
# Attach scaled CR pulse schedules to the RZX gates.
RZXCalibrationBuilderNoEcho(inst_map),
# Simplify single-qubit gates.
UnrollCustomDefinitions(std_eqlib, rzx_basis),
BasisTranslator(std_eqlib, rzx_basis),
Optimize1qGatesDecomposition(rzx_basis),
])
# Define parametrized circuit and parameter values
qc = RealAmplitudes(2)
qc.measure_all() # add measurements!
print(qc.decompose())
# Instantiate backend sampler with skip_transpilation
sampler = BackendSampler(backend=backend, skip_transpilation=True, bound_pass_manager=post)
# Run unbound transpiler pass
transpiled_circuit = pre.run(qc)
# Run sampler
quasi_dists = sampler.run(transpiled_circuit, [[0.5] * len(qc.parameters)]).result().quasi_dists
print("Quasi-dists: ", quasi_dists)
```
```text
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ░ ┌─┐
q_0: ┤ Ry(θ[0]) ├──■──┤ Ry(θ[2]) ├──■──┤ Ry(θ[4]) ├──■──┤ Ry(θ[6]) ├─░─┤M├───
├──────────┤┌─┴─┐├──────────┤┌─┴─┐├──────────┤┌─┴─┐├──────────┤ ░ └╥┘┌─┐
q_1: ┤ Ry(θ[1]) ├┤ X ├┤ Ry(θ[3]) ├┤ X ├┤ Ry(θ[5]) ├┤ X ├┤ Ry(θ[7]) ├─░──╫─┤M├
└──────────┘└───┘└──────────┘└───┘└──────────┘└───┘└──────────┘ ░ ║ └╥┘
meas: 2/═══════════════════════════════════════════════════════════════════╩══╩═
0 1
Quasi-dists: [{1: 0.18359375, 2: 0.2333984375, 0: 0.1748046875, 3: 0.408203125}]
```