595 lines
29 KiB
Plaintext
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}]
|
|
```
|