Skip to content

Commit

Permalink
Complete LRE user guide (#2542)
Browse files Browse the repository at this point in the history
* make private function public

* check build and import path for part 3

* lre test change glossary

* part 3 cleanup

* glossary and readme

* workflow diagram

* Vincent: Apply suggestions from code review

Co-authored-by: Vincent Russo <[email protected]>

* vincent's suggestions: part 2

* part 4 draft

* underscore unit test

* remove cirq warning

* Apply suggestions from code review

Co-authored-by: nate stemen <[email protected]>

* build error

* workflow diagram and other edits

* chunking folding methods

* nate's suggested changes

Co-authored-by: nate stemen <[email protected]>

* workflow_diagram + remove low level

* suggestions round 2

* toctree

* duplicated text landing page

* add formula for the scale factor vector

* formatting

* fix docstring formatting

* minor tweaking

---------

Co-authored-by: Vincent Russo <[email protected]>
Co-authored-by: nate stemen <[email protected]>
  • Loading branch information
3 people authored Nov 7, 2024
1 parent 56f5e49 commit 53807c0
Show file tree
Hide file tree
Showing 11 changed files with 307 additions and 24 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ mitiq.qem_methods()
| Readout-error mitigation | [REM](https://mitiq.readthedocs.io/en/latest/guide/rem.html) | [`mitiq.rem`](https://github.com/unitaryfund/mitiq/tree/main/mitiq/rem) | [1907.08518](https://arxiv.org/abs/1907.08518) <br>[2006.14044](https://arxiv.org/abs/2006.14044)
| Quantum Subspace Expansion | [QSE](https://mitiq.readthedocs.io/en/stable/guide/qse.html) | [`mitiq.qse`](https://github.com/unitaryfund/mitiq/tree/main/mitiq/qse) | [1903.05786](https://arxiv.org/abs/1903.05786)|
| Robust Shadow Estimation 🚧 | [RSE](https://mitiq.readthedocs.io/en/stable/guide/shadows.html)| [`mitiq.qse`](https://github.com/unitaryfund/mitiq/tree/main/mitiq/shadows) | [2011.09636](https://arxiv.org/abs/2011.09636) <br> [2002.08953](https://arxiv.org/abs/2002.08953)|
| Layerwise Richardson Extrapolation 🚧 | Coming soon | [`mitiq.lre`](https://github.com/unitaryfund/mitiq/tree/main/mitiq/lre) | [2402.04000](https://arxiv.org/abs/2402.04000) |
| Layerwise Richardson Extrapolation | [LRE](https://mitiq.readthedocs.io/en/stable/guide/lre.html) | [`mitiq.lre`](https://github.com/unitaryfund/mitiq/tree/main/mitiq/lre) | [2402.04000](https://arxiv.org/abs/2402.04000) |


In addition, we also have a noise tailoring technique currently available with limited functionality:
Expand Down
4 changes: 4 additions & 0 deletions docs/source/guide/glossary.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ trained with quantum circuits that resemble the circuit of interest, but which a
: Sequences of gates are applied to slack windows (single-qubit idle windows) in a quantum circuit to reduce the coupling
between the qubits and the environment, mitigating the effects of noise.

[Layerwise Richardson Extrapolation (LRE)](lre.md)
: Expectation values from multiple layerwise noise-scaled circuits are used to compute the error-mitigated expectation value
through multivariate Richardson extrapolation.

[Probabilistic Error Cancellation (PEC)](pec.md)
: Ideal operations are represented as quasi-probability distributions over noisy implementable operations, and unbiased estimates of expectation values are obtained by averaging over circuits sampled according to this representation.

Expand Down
5 changes: 0 additions & 5 deletions docs/source/guide/lre-1-intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,6 @@ LRE works in two main stages: generate noise-scaled circuits via layerwise scali
This workflow can be executed by a single call to {func}`.execute_with_lre`.
If more control is needed over the protocol, Mitiq provides {func}`.multivariate_layer_scaling` and {func}`.multivariate_richardson_coefficients` to handle the first and second steps respectively.

```{danger}
LRE is currently compatible with quantum programs written using `cirq`.
Work on making this technique compatible with other frontends is ongoing. 🚧
```

## Problem Setup

To demonstrate the use of LRE, we'll first define a quantum circuit, and a method of executing circuits for demonstration purposes.
Expand Down
243 changes: 243 additions & 0 deletions docs/source/guide/lre-3-options.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
---
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
---

# What additional options are available when using LRE?

In [](lre-1-intro.md), {func}`.execute_with_lre` was used to calculated the error mitigated expectation values.
In this section, we will outline the optional arguments that can be used and adjusted with this technique.

```python
from mitiq import lre

lre_value = lre.execute_with_lre(
circuit,
executor,
degree,
fold_multiplier,
folding_method=<"noise scaling method imported from zne.scaling.folding">,
num_chunks=<"number of chunks to group a large circuit into">,
)
```

The hyperparameters that can be controlled are:

- `degree`: to modify the extrapolating polynomial
- `fold_multiplier`: to control how the circuit is scaled
- `folding_method`: to choose the unitary folding method
- `num_chunks`: to alter the sampling cost

## Controlling how the noise-scaled circuits are created

### Extrapolating polynomial

The chosen degree of the extrapolating polynomial affects the way in which circuits get scaled, as we'll see below.
For this example, we'll define a circuit consisting of 4 layers.

```{code-cell} ipython3
from cirq import LineQubit, Circuit, CZ, CNOT, H
q0, q1, q2, q3 = LineQubit.range(4)
circuit = Circuit(
H(q0),
CNOT.on(q0, q1),
CZ.on(q1, q2),
CNOT.on(q2, q3),
)
print(circuit)
```

For `degree = 2`, the scale factor pattern is generated through the terms in the monomial basis for the multivariate polynomial. For more information, see [](lre-5-theory.md).

Here, $\lambda_i$ refers to the folding factor for the $i$-th layer. The example monomial basis is given by:

$$\{1, λ_1, λ_2, λ_3, λ_4, λ_1^2, λ_1 λ_2, λ_1 λ_3, λ_1 λ_4, λ_2^2, λ_2 λ_3, λ_2 λ_4, λ_3^2, λ_3 λ_4, λ_4^2\}$$

Each vector of scale factor vectors is given by $\boldsymbol{\lambda}_i = \boldsymbol{1} + 2 \boldsymbol{m}_i$ where $\boldsymbol{1} = (1, 1, \ldots, 1)$ and $\boldsymbol{m}_i$ is a vector of non-negative integers representing the number of times a layer is to be folded as dictated by the fold multiplier.

```{code-cell} ipython3
from mitiq.lre.multivariate_scaling import get_scale_factor_vectors
scale_factors = get_scale_factor_vectors(circuit, degree=2, fold_multiplier=2)
print(scale_factors)
```

In the noise scaled circuits created using the above scale factor vectors:

- The term $1$ in the monomial terms basis corresponds to the `degree = 0` term in the polynomial which is equivalent to
the $\lambda_1^0\lambda_2^0\lambda_3^0\lambda_4^0$ term. Due to this term, the first noise-scaled circuit is unchanged.

- due to the $λ_1$ term in the monomial basis, the second noise-scaled circuit only scales the first layer in the circuit.

- due to the $λ_2$ term in the monomial basis, the next noise-scaled circuit only scales the second layer in the circuit

- and so on.

The total number of noise-scaled circuits is given by $\binom{d + l - 1}{d}$ where $l$ is the number of layers in the circuit and $d$ is the chosen degree of the multivariate polynomial as discussed in [](lre-5-theory.md).

```{code-cell} ipython3
print(f"Total number of noise scaled circuits created: {len(scale_factors)}")
```

As the `fold_multiplier` is changed, the number of scaled circuits remains the same but how the layers are scaled
is altered.

```{code-cell} ipython3
scale_factors_diff_fold_multiplier = get_scale_factor_vectors(
circuit,
degree=2,
fold_multiplier=3,
)
print(f"Total number of noise-scaled circuits created with different"
f" fold_multiplier: {len(scale_factors_diff_fold_multiplier)}")
print(f"Scale factor for some noise scaled circuit with degree=2 "
f"and fold_multiplier=2: \n {scale_factors[-2]}")
print(f"Scale factor for some noise scaled circuit with degree= 2 "
f"but fold_multiplier=3: \n {scale_factors_diff_fold_multiplier[-2]}")
```

Both the number of noise scaled circuits and scale factor vectors are changed when a different value for `degree` is used while keeping everything else the same.

```{code-cell} ipython3
scale_factors_diff_degree = get_scale_factor_vectors(
circuit,
degree=3,
fold_multiplier=2,
)
print(f"Total number of noise scaled circuits created: "
f"{len(scale_factors_diff_degree)}")
```

Thus, even though `degree` and `fold_multiplier` are required to use {func}`.execute_with_lre`, they function as a tunable
hyperparameter affecting the performance of the technique.

## Chunking a circuit into fewer layers

When you have a large circuit, the size of the sample matrix increases as the number of monomial terms scale polynomially. The size of the sample matrix influences sampling cost. In such a case, a circuit of 100 layers could be grouped into 4 chunks where each chunk consists of 25 collated layers. The noise scaling function {func}`.multivariate_layer_scaling`
treats each chunk as a layer to be scaled when the parameter `num_chunks` is used. Thus, for the 100 layer circuit grouped into 4 chunks with `degree = 2` and `fold_multiplier = 2`, only 15 noise-scaled circuits are created i.e. sample matrix is reduced to dimension $15 \times 15$.

```{caution}
Reducing the sampling cost by chunking the circuit can affect the performance of the technique.
```

Suppose we want to chunk our example circuit into 2 chunks while using `degree = 2` and
`fold_multiplier = 2`. The sample matrix defined by the monomial terms is reduced in size as chunking the circuit
will create a new monomial basis for the extrapolating polynomial.

$$\{1, λ_1, λ_2, λ_1^2, λ_1 λ_2, λ_2^2\}$$

The scale factor vectors change as shown below:

```{code-cell} ipython3
scale_factors_with_chunking = get_scale_factor_vectors(
circuit,
degree=2,
fold_multiplier=2,
num_chunks=2,
)
print(scale_factors_with_chunking)
```

Thus, the total number of noise-scaled circuits is reduced by chunking the circuit into fewer layers to be folded.

```{code-cell} ipython3
print(f"Total number of noise scaled circuits with chunking: "
f"{len(scale_factors_with_chunking)}")
print(f"Total number of noise scaled circuits without chunking: "
f"{len(scale_factors)}")
```

How the noise-scaled circuits are chunked differs greatly as each chunk in the circuit is now equivalent to a layer to be folded via unitary folding. In the example below, we compare the second noise-scaled circuit in a chunked and a non-chunked
circuit which corresponds to the $λ_1$ term in the monomial basis.

```{code-cell} ipython3
from mitiq.lre.multivariate_scaling import multivariate_layer_scaling
# apply chunking
chunked_circ = multivariate_layer_scaling(
circuit, degree=2, fold_multiplier=2, num_chunks=2
)[1]
# skip chunking
non_chunked_circ = multivariate_layer_scaling(
circuit, degree=2, fold_multiplier=2
)[1]
print("original circuit: ", circuit, sep="\n")
print("Noise scaled circuit created with chunking: ", chunked_circ, sep="\n")
print(
"Noise scaled circuit created without chunking: ",
non_chunked_circ,
sep="\n",
)
```

### Noise scaling method

The default choice for unitary folding in {func}`.execute_with_lre` and {func}`.multivariate_layer_scaling` is
{func}`.fold_gates_at_random()`.

However, there are two other choices as well: {func}`.fold_all()` and {func}`.fold_global()` which can be used for the
`folding_method` parameter in {func}`.execute_with_lre`.

```{tip}
The choice of folding method matters only when chunking is employed.
Otherwise the noise scaled circuits created using either of the folding methods will look identical as they are created by scaling each layer as required.
```

```{code-cell} ipython3
from mitiq.zne.scaling import fold_all, fold_global
# apply local folding
local_fold_circ = multivariate_layer_scaling(
circuit, degree=2, fold_multiplier=2, folding_method=fold_all
)[-2]
# apply global folding
global_fold_circ = multivariate_layer_scaling(
circuit,
degree=2,
fold_multiplier=2,
num_chunks=2,
folding_method=fold_global,
)[-2]
print("original circuit: ", circuit, sep="\n")
print(
"Noise scaled circuit created using local unitary folding: ",
local_fold_circ,
sep="\n",
)
print(
"Noise scaled circuit created using global unitary folding and chunking: ",
global_fold_circ,
sep="\n",
)
```

This section showed in detail how to vary the default and non-default parameters required by the technique.
An in-depth discussion on these is provided in [](lre-4-low-level.md)
37 changes: 37 additions & 0 deletions docs/source/guide/lre-4-low-level.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
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
---

# What happens when I use LRE?

As shown in the figure below, LRE works in two steps, layerwise noise scaling and extrapolation.

The noise-scaled circuits are created through the functions in {mod}`mitiq.lre.multivariate_scaling.layerwise_folding` while the error-mitigated expectation value is estimated by using the functions in {mod}`mitiq.lre.inference.multivariate_richardson`.

```{figure} ../img/lre_workflow_steps.png
---
width: 700px
name: lre-overview2
---
The diagram shows the workflow of the layerwise Richardson extrapolation (LRE) in Mitiq.
```

**The first step** involves generating and executing layerwise noise-scaled quantum circuits.

- The user provides a `QPROGRAM` i.e. a frontend supported quantum circuit.
- Mitiq generates a set of layerwise noise-scaled circuits by applying unitary folding based on a set of pre-determined scale factor vectors.

The noise-scaled circuits are then executed on a backend, obtaining a set of noisy expectation values.

**The second step** involves inferring the error mitigated expectation value from the measured results through multivariate richardson extrapolation.

The function {func}`.execute_with_lre` accomplishes both steps behind the scenes to estimate the error mitigate expectation value. Additional information is available in [](lre-1-intro.md). The linked page also has a section demonstrating how to apply each step individually.
16 changes: 9 additions & 7 deletions docs/source/guide/lre.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
```{warning}
The user guide for LRE in Mitiq is currently under construction.
```

# Layerwise Richardson Extrapolation

Layerwise Richardson Extrapolation (LRE), an error mitigation technique, introduced in
{cite}`Russo_2024_LRE` extends the ideas found in ZNE by allowing users to create multiple noise-scaled variations of the input
circuit such that the noiseless expectation value is extrapolated from the execution of each
noisy circuit.
```{figure} ../img/lre_workflow_steps.png
---
width: 700px
name: lre-overview
---
The diagram shows the workflow of the layerwise Richardson extrapolation (LRE) in Mitiq.
```

Layerwise Richardson Extrapolation (LRE), an error mitigation technique, introduced in
{cite}`Russo_2024_LRE` works by creating multiple noise-scaled variations of the input
Expand All @@ -24,5 +24,7 @@ maxdepth: 1
---
lre-1-intro.md
lre-2-use-case.md
lre-3-options.md
lre-4-low-level.md
lre-5-theory.md
```
Binary file added docs/source/img/lre_workflow_steps.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 6 additions & 5 deletions mitiq/lre/inference/multivariate_richardson.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

from mitiq.interface import accept_any_qprogram_as_input
from mitiq.lre.multivariate_scaling.layerwise_folding import (
_get_scale_factor_vectors,
get_scale_factor_vectors,
)


Expand Down Expand Up @@ -94,7 +94,7 @@ def sample_matrix(
if fold_multiplier < 1:
raise ValueError("Fold multiplier must be greater than or equal to 1.")

scale_factor_vectors = _get_scale_factor_vectors(
scale_factor_vectors = get_scale_factor_vectors(
input_circuit, degree, fold_multiplier, num_chunks
)
num_layers = len(scale_factor_vectors[0])
Expand Down Expand Up @@ -134,8 +134,9 @@ def multivariate_richardson_coefficients(
:cite:`Russo_2024_LRE`).
We use the sample matrix to find the constants of linear combination
$c = (c_1, c_2, c_3, …, c_M)$ associated with a known vector of noisy
expectation values $z = (<O(λ_1)>, <O(λ_2)>, <O(λ_3)>, ..., <O(λ_M)>)^T$.
$c = (c_1, c_2, …, c_M)$ associated with a known vector of noisy
expectation values :math:`z = (\langle O(λ_1)\rangle,
\langle O(λ_2)\rangle, ..., \langle O(λ_M)\rangle)^T`.
The coefficients are found through the ratio of the determinants of $M_i$
and the sample matrix. The new matrix $M_i$ is defined by replacing the ith
Expand All @@ -158,7 +159,7 @@ def multivariate_richardson_coefficients(
input_circuit, degree, fold_multiplier, num_chunks
)
num_layers = len(
_get_scale_factor_vectors(
get_scale_factor_vectors(
input_circuit, degree, fold_multiplier, num_chunks
)
)
Expand Down
1 change: 1 addition & 0 deletions mitiq/lre/multivariate_scaling/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@

from mitiq.lre.multivariate_scaling.layerwise_folding import (
multivariate_layer_scaling,
get_scale_factor_vectors,
)
Loading

0 comments on commit 53807c0

Please sign in to comment.