-
-
Notifications
You must be signed in to change notification settings - Fork 158
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adding layerwise folding as tutorial to mitiq. (#1894)
* Adding layerwise folding as tutorial to mitiq. * fixing typing issue for python3.10. * Update docs/source/examples/layerwise-folding.md Co-authored-by: Nathan Shammah <[email protected]> * Fixing doc warnings. Adding thumbnail. * Update docs/source/examples/layerwise-folding.md Co-authored-by: Andrea Mari <[email protected]> * Responding to diff comments. * Update docs/source/examples/layerwise-folding.md Co-authored-by: nate stemen <[email protected]> * Update docs/source/examples/layerwise-folding.md Co-authored-by: nate stemen <[email protected]> * Moving motivational material to top. * Removing duplicated content from tutorial. --------- Co-authored-by: vrusso <[email protected]> Co-authored-by: Nathan Shammah <[email protected]> Co-authored-by: Andrea Mari <[email protected]> Co-authored-by: nate stemen <[email protected]>
- Loading branch information
1 parent
bfbeb1c
commit fc92819
Showing
4 changed files
with
293 additions
and
0 deletions.
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,291 @@ | ||
--- | ||
jupytext: | ||
text_representation: | ||
extension: .md | ||
format_name: myst | ||
format_version: 0.13 | ||
jupytext_version: 1.11.1 | ||
kernelspec: | ||
display_name: Python 3 | ||
language: python | ||
name: python3 | ||
--- | ||
|
||
# ZNE with Qiskit: Layerwise folding | ||
|
||
|
||
This tutorial shows an example of how to mitigate noise on IBMQ backends using | ||
layerwise folding in contrast with global folding. | ||
|
||
One may ask why folding by layer is potentially beneficial to consider. One | ||
reason is that applying global folding will increase the length of the entire | ||
circuit while layerwise folding on a subset of only the noisiest layers will | ||
increase the circuit by a smaller factor. | ||
|
||
If running a circuit on hardware is bottle-necked by the cost of running a long | ||
circuit, this technique could potentially be used to arrive at a better result | ||
(although not as good as global folding) but with less monetary cost. | ||
|
||
More information on the layerwise folding technique can be found in | ||
*Calderon et al. Quantum (2023)* {cite}`Calderon_2023_Quantum`. | ||
|
||
|
||
- [ZNE with Qiskit: Layerwise folding](#zne-with-qiskit-layerwise-folding) | ||
- [Setup](#setup) | ||
- [Helper functions](#helper-functions) | ||
- [Define circuit to analyze](#define-circuit-to-analyze) | ||
- [Total variational distance metric](#total-variational-distance-metric) | ||
- [Impact of single vs. multiple folding](#impact-of-single-vs-multiple-folding) | ||
- [Executor](#executor) | ||
- [Global folding with linear extrapolation](#global-folding-with-linear-extrapolation) | ||
- [Layerwise folding with linear extrapolation](#layerwise-folding-with-linear-extrapolation) | ||
|
||
+++ | ||
|
||
## Setup | ||
|
||
```{code-cell} ipython3 | ||
from typing import Dict, List, Optional | ||
import numpy as np | ||
import os | ||
import cirq | ||
import qiskit | ||
import matplotlib.pyplot as plt | ||
from mitiq import zne | ||
from mitiq.zne.scaling.layer_scaling import layer_folding, get_layer_folding | ||
from mitiq.interface.mitiq_qiskit.qiskit_utils import initialized_depolarizing_noise | ||
from mitiq.interface.mitiq_qiskit.conversions import to_qiskit | ||
from mitiq.interface.mitiq_cirq.cirq_utils import sample_bitstrings | ||
from cirq.contrib.svg import SVGCircuit | ||
from qiskit_ibm_provider import IBMProvider | ||
# Default to a simulator. | ||
backend = qiskit.Aer.get_backend("qasm_simulator") | ||
noise_model = initialized_depolarizing_noise(noise_level=0.02) | ||
shots = 10_000 | ||
``` | ||
|
||
## Helper functions | ||
|
||
The following function will return a list of circuits where the ith element in | ||
the list is a circuit with layer "i" folded `num_folds` number of times. This | ||
will be useful when analyzing how much folding increases the noise on a given | ||
layer. | ||
|
||
```{code-cell} ipython3 | ||
def apply_num_folds_to_all_layers(circuit: cirq.Circuit, num_folds: int = 1) -> List[cirq.Circuit]: | ||
"""List of circuits where ith circuit is folded `num_folds` times.""" | ||
return [ | ||
layer_folding(circuit, [0] * i + [num_folds] + [0] * (len(circuit) - i)) | ||
for i in range(len(circuit)) | ||
] | ||
``` | ||
|
||
For instance, consider the following circuit. | ||
|
||
```{code-cell} ipython3 | ||
# Define a basic circuit for | ||
q0, q1 = cirq.LineQubit.range(2) | ||
circuit = cirq.Circuit( | ||
[cirq.ops.H(q0)], | ||
[cirq.ops.CNOT(q0, q1)], | ||
[cirq.measure(cirq.LineQubit(0))], | ||
) | ||
print(circuit) | ||
``` | ||
|
||
Let us invoke the `apply_num_folds_to_all_layers` function as follows. | ||
|
||
```{code-cell} ipython3 | ||
folded_circuits = apply_num_folds_to_all_layers(circuit, num_folds=2) | ||
``` | ||
|
||
Note that the first element of the list is the circuit with the first layer of | ||
the circuit folded twice. | ||
|
||
```{code-cell} ipython3 | ||
print(folded_circuits[0]) | ||
``` | ||
|
||
Similarly, the second element of the list is the circuit with the second layer | ||
folded. | ||
|
||
```{code-cell} ipython3 | ||
print(folded_circuits[1]) | ||
``` | ||
|
||
|
||
## Define circuit to analyze | ||
|
||
We will use the following circuit to analyze, but of course, you could use | ||
other circuits here as well. | ||
|
||
```{code-cell} ipython3 | ||
circuit = cirq.Circuit([cirq.X(cirq.LineQubit(0))] * 10, cirq.measure(cirq.LineQubit(0))) | ||
print(circuit) | ||
``` | ||
|
||
## Total variational distance metric | ||
|
||
An $i$-inversion can be viewed as a local perturbation of the circuit. We want | ||
to define some measure by which we can determine how much such a perturbation | ||
affects the outcome. | ||
|
||
Define the quantity: | ||
|
||
$$ | ||
p(k|C) = \langle \langle k | C | \rho_0 \rangle \rangle | ||
$$ | ||
|
||
as the probability distribution over measurement outcomes at the output of a | ||
circuit $C$ where $k \in B^n$ with $B^n$ being the set of all $n$-length bit | ||
strings where $\langle \langle k |$ is the vectorized POVM element that | ||
corresponds to measuring bit string $k$. | ||
|
||
The *impact* of applying an inversion is given by | ||
|
||
$$ | ||
d \left[p(\cdot|C), p(\cdot|C^{(i)})\right] | ||
$$ | ||
|
||
where $d$ is some distance measure. In | ||
*Calderon et al. Quantum (2023)* {cite}`Calderon_2023_Quantum` the authors used the total variational distance | ||
(TVD) measure where | ||
|
||
$$ | ||
\eta^{(i)} := \frac{1}{2} \sum_{k} |p(k|C) - p(k|C^{(i)})|. | ||
$$ | ||
|
||
```{code-cell} ipython3 | ||
def tvd(circuit: cirq.Circuit, num_folds: int = 1, shots: int = 10_000) -> List[float]: | ||
"""Compute the total variational distance (TVD) between ideal circuit and folded circuit(s).""" | ||
circuit_dist = sample_bitstrings(circuit=circuit, shots=shots).prob_distribution() | ||
folded_circuits = apply_num_folds_to_all_layers(circuit, num_folds) | ||
distances: Dict[int, float] = {} | ||
for i, folded_circuit in enumerate(folded_circuits): | ||
folded_circuit_dist = sample_bitstrings(circuit=folded_circuit, shots=shots).prob_distribution() | ||
res: float = 0.0 | ||
for bitstring in circuit_dist.keys(): | ||
res += np.abs(circuit_dist[bitstring] - folded_circuit_dist[bitstring]) | ||
distances[i] = res / 2 | ||
return distances | ||
``` | ||
|
||
## Impact of single vs. multiple folding | ||
|
||
We can plot the impact of applying layer inversions to the circuit. | ||
|
||
```{code-cell} ipython3 | ||
def plot_single_vs_multiple_folding(circuit: cirq.Circuit) -> None: | ||
"""Plot how single vs. multiple folding impact the error at a given layer.""" | ||
single_tvd = tvd(circuit, num_folds=1).values() | ||
multiple_tvd = tvd(circuit, num_folds=5).values() | ||
labels = [f"L{i}" for i in range(len(circuit))] | ||
x = np.arange(len(labels)) # the label locations | ||
width = 0.35 # the width of the bars | ||
fig, ax = plt.subplots() | ||
rects1 = ax.bar(x - width/2, single_tvd, width, label="single") | ||
rects2 = ax.bar(x + width/2, multiple_tvd, width, label="multiple") | ||
# Add some text for labels, title and custom x-axis tick labels, etc. | ||
ax.set_xlabel(r"$L_{G_i \theta_i}$") | ||
ax.set_ylabel(r"$\eta^{(i)}$") | ||
ax.set_title("Single vs. multiple folding") | ||
ax.set_xticks(x, labels, rotation=60) | ||
ax.legend() | ||
ax.bar_label(rects1, padding=3) | ||
ax.bar_label(rects2, padding=3) | ||
fig.tight_layout() | ||
plt.show() | ||
``` | ||
|
||
```{code-cell} ipython3 | ||
plot_single_vs_multiple_folding(circuit) | ||
``` | ||
|
||
As can be seen, the amount of noise on each layer is increased if the number of | ||
folds on that layer are increased. | ||
|
||
## Executor | ||
|
||
Next, we define an executor function that will allow us to run our experiment | ||
|
||
```{code-cell} ipython3 | ||
def executor(circuit: cirq.Circuit, shots: int = 10_000) -> float: | ||
"""Returns the expectation value to be mitigated. | ||
Args: | ||
circuit: Circuit to run. | ||
shots: Number of times to execute the circuit to compute the expectation value. | ||
""" | ||
qiskit_circuit = to_qiskit(circuit) | ||
job = qiskit.execute( | ||
experiments=qiskit_circuit, | ||
backend=backend, | ||
noise_model=noise_model, | ||
basis_gates=noise_model.basis_gates if noise_model is not None else None, | ||
optimization_level=0, # Important to preserve folded gates. | ||
shots=shots, | ||
) | ||
# Convert from raw measurement counts to the expectation value | ||
counts = job.result().get_counts() | ||
expectation_value = 0.0 if counts.get("0") is None else counts.get("0") / shots | ||
return expectation_value | ||
``` | ||
|
||
## Global folding with linear extrapolation | ||
|
||
First, for comparison, we apply ZNE with global folding on the entire circuit. | ||
We then compare the mitigated result of applying ZNE with linear extrapolation | ||
against the unmitigated value. | ||
|
||
|
||
```{code-cell} ipython3 | ||
unmitigated = executor(circuit) | ||
linear_factory = zne.inference.LinearFactory(scale_factors=[1.0, 1.5, 2.0, 2.5, 3.0]) | ||
mitigated = zne.execute_with_zne(circuit, executor, factory=linear_factory) | ||
print(f"Unmitigated result {unmitigated:.3f}") | ||
print(f"Mitigated result {mitigated:.3f}") | ||
``` | ||
|
||
## Layerwise folding with linear extrapolation | ||
|
||
For contrast, we apply layerwise folding on only the layer with the most noise | ||
and use linear extrapolation. As above, we compare the mitigated and | ||
unmitigated values. | ||
|
||
```{code-cell} ipython3 | ||
# Calculate the TVDs of each layer in the circuit (with `num_folds=3`): | ||
tvds = tvd(circuit, num_folds=3) | ||
# Fold noisiest layer only. | ||
layer_to_fold = max(tvds, key=tvds.get) | ||
fold_layer_func = zne.scaling.get_layer_folding(layer_to_fold) | ||
mitigated = zne.execute_with_zne(circuit, executor, scale_noise=fold_layer_func, factory=linear_factory) | ||
print(f"Mitigated (layerwise folding) result {mitigated:.3f}") | ||
print(f"Unmitigated result {unmitigated:.3f}") | ||
``` | ||
|
||
```{note} | ||
While doing layerwise folding on the noisiest layer will, on average, | ||
improve the mitigated value, it still will not eclipse the benefit of doing | ||
global folding. | ||
``` |