Skip to content

Commit

Permalink
QSE docs cleanup (unitaryfund#2490)
Browse files Browse the repository at this point in the history
  • Loading branch information
natestemen committed Sep 4, 2024
1 parent 685e7d0 commit 1a3705c
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 44 deletions.
40 changes: 13 additions & 27 deletions docs/source/guide/qse-1-intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,16 @@ Here we show how to use QSE by means of a simple example.
To use QSE, we call `qse.execute_with_qse()` with five “ingredients”:

- A quantum circuit to prepare a state
- A quantum computer or noisy simulator to return a QuantumResult from
- A quantum computer or noisy simulator which returns a {class}`.QuantumResult`
- An observable we wish to compute the error mitigated expectation value
- A list of Check Operators which can be stabilizers, symmetries or anything else.
- A Code Hamiltonian which defines the state with the least amount of errors
- A list of "check operators" which can be stabilizers, symmetries, or anything else.
- A "code Hamiltonian" which defines the state with the least amount of errors

## 1. Define a quantum circuit
The quantum circuit can be specified as any quantum circuit supported by Mitiq.

In the next cell we define (as an example) a quantum circuit that prepares the logical 0 state in the [[5,1,3]] code. For simplicity, we use Gram Schmidt orthogonalization to form a unitary matrix that we use as a gate to prepare our state.
In the next cell we define (as an example) a quantum circuit that prepares the logical 0 state in the [[5,1,3]] code.
For simplicity, we use Gram Schmidt orthogonalization to form a unitary matrix that we use as a gate to prepare our state.

```{code-cell} ipython3
def prepare_logical_0_state_for_5_1_3_code():
Expand Down Expand Up @@ -104,13 +105,11 @@ def prepare_logical_0_state_for_5_1_3_code():
circuit.append(g(*qubits))
return circuit
def demo_qse():
circuit = prepare_logical_0_state_for_5_1_3_code()
```

## 2. Define an executor
We define an [executor](executors.md) function which inputs a circuit and returns a `QuantumResult`. Here for sake of example we use a simulator that adds single-qubit depolarizing noise after each moment and returns the final density matrix.
We define an [executor](executors.md) function which inputs a circuit and returns a `QuantumResult`.
Here for sake of example we use a simulator that adds single-qubit depolarizing noise after each moment and returns the final density matrix.

```{code-cell} ipython3
from typing import List
Expand All @@ -127,14 +126,11 @@ def execute_with_depolarized_noise(circuit: QPROGRAM) -> np.ndarray:
noise_model_function=cirq.depolarize,
noise_level=(0.01,),
)
def demo_qse():
circuit = prepare_logical_0_state_for_5_1_3_code()
executor = execute_with_depolarized_noise
```

## 3. Observable
We define an observable in the code subspace: $O = ZZZZZ$. As an example, assume that we wish to compute the expectation value $Tr[\rho O]$ of the observable O.
We define an observable in the code subspace: $O = ZZZZZ$.
As an example, assume that we wish to compute the expectation value $\mathrm{tr}[\rho O]$ of the observable $O$.

```{code-cell} ipython3
def get_observable_in_code_space(observable: List[cirq.PauliString]):
Expand All @@ -152,14 +148,12 @@ def get_observable_in_code_space(observable: List[cirq.PauliString]):
observable_in_code_space *= 0.5 * Observable(FIVE_I, g)
return observable_in_code_space
def demo_qse():
circuit = prepare_logical_0_state_for_5_1_3_code()
executor = execute_with_depolarized_noise
observable = get_observable_in_code_space(PauliString("ZZZZZ"))
```

## 4. Check Operators and Code Hamiltonian
We then assign the check operators as a list of pauli strings, and the code Hamiltonian as an Observable for the [[5,1,3]] code. The check operators of the [[5,1,3]] code are simply the expansion of the code’s 4 generators: $[XZZXI, IXZZX, XIXZZ, ZXIXZ]$

We then assign the check operators as a list of pauli strings, and the code Hamiltonian as an Observable for the [[5,1,3]] code.
The check operators of the [[5,1,3]] code are simply the expansion of the code’s 4 generators: $[XZZXI, IXZZX, XIXZZ, ZXIXZ]$

```{code-cell} ipython3
def get_5_1_3_code_check_operators_and_code_hamiltonian() -> tuple:
Expand Down Expand Up @@ -195,19 +189,11 @@ def get_5_1_3_code_check_operators_and_code_hamiltonian() -> tuple:
]
Hc = Observable(*negative_Ms_as_pauliStrings)
return Ms_as_pauliStrings, Hc
def demo_qse():
circuit = prepare_logical_0_state_for_5_1_3_code()
executor = execute_with_depolarized_noise
observable = get_observable_in_code_space(PauliString("ZZZZZ"))
check_operators, code_hamiltonian = get_5_1_3_code_check_operators_and_code_hamiltonian()
```


## Run QSE
Now we can run QSE. We first compute the noiseless result then the noisy result to compare to the mitigated result from QSE.

With everything defined, we can now run QSE in full.

```{code-cell} ipython3
def demo_qse():
Expand Down
18 changes: 10 additions & 8 deletions docs/source/guide/qse-3-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,22 @@ kernelspec:
name: python3
---


# What additional options are available in QSE?
In addition to the necessary ingredients already discussed in [How do I use QSE?](qse-1-intro.md), there are a few additional options included in the implementation.
In addition to the necessary ingredients already discussed in [How do I use QSE?](qse-1-intro.md), there are a few additional options included in the implementation.

## Caching Pauli Strings to Expectation Values
```{warning}
The cache object is modified in place.
```

Specifically, in order to save runtime, the QSE implementation supports the use of a cache that maps pauli strings to their expectation values. This is taken as an additional parameter in the [`execute_with_qse`](https://mitiq.readthedocs.io/en/stable/apidoc.html#mitiq.qse.qse.execute_with_qse) function.

The inclusion of the cache significantly speeds up the runtime and avoids the need for re-computation of already computed values.
Furthermore, it is important to note that the cache gets modified in place, so the user can pass the same cache object to `execute_with_qse` and avoid regenerating the cache unnecessarily , e.g. if the noise model is the same from one execution to another.
```{warning}
The cache object is modified in place when passing it to `execute_with_qse`.
```

The inclusion of the cache significantly speeds up the runtime and avoids the need for re-computation of already computed values.
Furthermore, since the cache is modified in place, it can be reused as long as the noise model remains the same.

## Requirements for Check Operators

It is also important to note that when specifying the check (or excitation) operators for the execution, it is not necessary to specify the full exponential number of operators. As many or as few operators can be specified. The tradeoff is the fidelity of the projected state.
When specifying the check operators, it is **not** necessary to specify the full exponential number of operators.
As many or as few operators can be specified.
The tradeoff is the fidelity of the projected state.
24 changes: 15 additions & 9 deletions docs/source/guide/qse-5-theory.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,24 @@ kernelspec:

# What is the theory behind QSE?

We implement the subspace expansion method introduced by McClean et al {cite}`McClean_2020_NatComm`
We implement the subspace expansion method introduced by McClean et al {cite}`McClean_2020_NatComm`.

Subspace expansion as an error mitigation scheme uses a set of operators, called the check operators, to expand around the output state, then search this subspace for the state with the lowest error. When used in conjunction with a quantum code, a subset of the stabilizer group can be used to expand around the output state. If the full stabilizer group is used, subspace expansion is equivalent to projecting the final state of the computation onto the codeword subspace.
Subspace expansion as an error mitigation scheme uses a set of operators, called the check operators, to define a subspace surrounding the output state.
The protocol then involves searching this subspace for the state with the lowest error.

The check operators can also include other operators outside the stabilizers group. For instance, if the final state of the computation is known to have some symmetry, including the symmetry operator as a check operator is useful in correcting errors that don’t respect the symmetry. In our code implementation, we allow the user to input the set of check operators, which can be stabilizers, symmetries, or anything else.
```{tip}
When used in conjunction with a quantum code, a subset of the stabilizer group can be used to expand around the output state.
If the full stabilizer group is used, subspace expansion is equivalent to projecting the final state of the computation onto the codeword subspace.
```

Let $| \Psi \rangle$ be the state we wish to prepare on the quantum computer and $M_i$ be the set of check operators such that $ M_i | \Psi \rangle = | \Psi \rangle$. Furthermore, let $\rho$ represent the density matrix of the actual final state prepared on the device. Due to errors, $\rho \neq | \Psi \rangle \langle \Psi |$. However, we would like to use our knowledge about the state $| \Psi \rangle$ encoded in the check operators to mitigate the errors in $\rho$.
The check operators can also include other operators outside the stabilizers group. For instance, if the final state of the computation is known to have some symmetry, including the symmetry operator as a check operator is useful in correcting errors that don’t respect the symmetry. In our code implementation, we allow the user to input the set of check operators, which can be stabilizers, symmetries, or anything else.

As mentioned, the main idea is to expand around $\rho$ using the check operators, and then search this subspace for the state with least amount of error. The state with the least amount of energy is defined as the state in the subspace that minimize $\mathrm H = - \sum_i M_i$. Thus the full procedure can be formulated as follows,
$\min_{{c_i}} \text{Tr}[\bar P_c \rho \bar P_c^\dagger \mathrm H ]$ subject to the constrain $\text{Tr}[\bar P_c \rho \bar P_c^\dagger] = 1$ with $\bar P_c = \sum c_i M_i$.
Let $| \Psi \rangle$ be the state we wish to prepare on the quantum computer and $M_i$ be the set of check operators such that $ M_i | \Psi \rangle = | \Psi \rangle$. Furthermore, let $\rho$ represent the density matrix of the actual final state prepared on the device. Due to errors, $\rho \neq | \Psi \rangle \langle \Psi |$. However, we would like to use our knowledge about the state $| \Psi \rangle$ encoded in the check operators to mitigate the errors in $\rho$.

As is turned out, this minimization problem can be mapped to the following generalized eigenvalue problem:
$H \boldsymbol c = \lambda_0 S \boldsymbol c$ where $\boldsymbol c_i = c_i$, $H_{ij} = \text{Tr}[M_i^\dagger H M_i \rho]$, and $S_{ij} = \text{Tr}[M_i^\dagger M_i \rho]$. The eigenvector $\boldsymbol c$ corresponding to the lowest $\lambda$ is the solution to the minimization problem.
As mentioned, the main idea is to expand around $\rho$ using the check operators, and then search this subspace for the state with least amount of error. The state with the least amount of energy is defined as the state in the subspace that minimizes $H = - \sum_i M_i$. Thus the full procedure can be formulated as follows,
$\min_{{c_i}} \mathrm{tr}[\bar P_c \rho \bar P_c^\dagger H ]$ subject to the constrain $\mathrm{tr}[\bar P_c \rho \bar P_c^\dagger] = 1$ with $\bar P_c = \sum c_i M_i$.

In practice, $H_{ij}$ and $S_{ij}$ can be measured on the quantum computer and the eigenvalue problem can be solved classically. Once $\boldsymbol c$ is obtained, we can construct $\bar P_c$ and use it to calculate any observable $A$ of interest using $\text{Tr}[ \bar P_c \rho \bar P_c^\dagger A ]$.
This minimization problem can be mapped to the following generalized eigenvalue problem:
$H \boldsymbol c = \lambda_0 S \boldsymbol c$ where $\boldsymbol c_i = c_i$, $H_{ij} = \mathrm{tr}[M_i^\dagger H M_i \rho]$, and $S_{ij} = \mathrm{tr}[M_i^\dagger M_i \rho]$. The eigenvector $\boldsymbol c$ corresponding to the lowest $\lambda$ is the solution to the minimization problem.

In practice, $H_{ij}$ and $S_{ij}$ can be measured on the quantum computer and the eigenvalue problem can be solved classically. Once $\boldsymbol c$ is obtained, we can construct $\bar P_c$ and use it to calculate any observable $A$ of interest using $\mathrm{tr}[ \bar P_c \rho \bar P_c^\dagger A ]$.

0 comments on commit 1a3705c

Please sign in to comment.