# Randomized Benchmarking: Interleaved RB¶

In this notebook we explore the subset of methods in `randomized_benchmarking.py`

that are related specifically to interleaved randomized benchmarking (IRB). I suggest reviewing the notebook `examples/randomized_benchmarking.ipynb`

first, since we will assume familiarity with its contents and treat IRB as a modification to the ‘standard’ RB protocol.

In standard RB we are interested in characterizing the average impact of noise on our gates, and we estimate an average error rate per Clifford. Interleaved RB is designed to characterize a particular gate, which is ‘interleaved’ throughout the standard RB sequence. The protocol is a simple extension of standard RB which involves running sequences of random Cliffords with the gate of interest G interleaved after every Clifford. Specifically, we transform a standard RB sequence

into an ‘interleaved’ sequence

where G is the gate we wish to characterize. Note that we still need to invert the sequence, so G must be an element of the Clifford group, and the final inverting gate must be updated to invert the entire sequence of gates including the G.

The IRB protocol can be summarized as follows:

- we want to characterize a gate G which acts on the set of qubits Q
- run standard RB on the qubits Q and estimate an rb decay.
- generate another set of sequences with the gate G interleaved in each sequence. Run these sequences exactly as you would for standard RB and estimate an rb decay, which we will call the irb decay. We expect this parameter to be smaller than the standard rb decay due to the effect of the extra applications of G.
- use the two decay parameters to calculate the error on gate G. We can also get lower and upper bounds on the fidelity of G.
- (Optional) run a unitarity experiment on the qubits Q and use this to improve the fidelity bounds from the last step.

The following is a starting point for more information

*Efficient measurement of quantum gate error by interleaved randomized benchmarking*.

## The code¶

The difference between an RB experiment and an IRB experiment is one flag in `generate_rb_experiments`

, so we’ll go through this quickly. Refer to `examples/randomized_benchmarking.ipynb`

for a more careful breakdown.

```
[1]:
```

```
# Needs in terminal:
# $ quilc -S
# $ qvm -S
import numpy as np
from pyquil.api import get_benchmarker, get_qc
from forest.benchmarking.randomized_benchmarking import *
from forest.benchmarking.plotting import plot_figure_for_fit
from pyquil.gates import *
from pyquil import Program
%matplotlib inline
```

Get a quantum computer and benchmarker object per usual

```
[2]:
```

```
bm = get_benchmarker()
qc = get_qc("9q-square-noisy-qvm", noisy=True)
```

### Generate an RB and IRB experiment¶

We’ll start be selecting our gate and hyperparameters. Then we generate two sets of experiments. The first set is the standard RB sequences and the second set has our gate interleaved in the sequences

```
[3]:
```

```
# Choose your gate
qubits = (0, 1)
gate = Program(CNOT(*qubits))
# Choose your parameters
qubit_groups = [qubits]
depths = [2, 15, 25, 30, 60]
num_sequences = 25
depths = [d for d in depths for _ in range(num_sequences)]
rb_expts = generate_rb_experiments(bm, qubit_groups, depths)
# provide the extra arg for the interleaved gate
inter_expts = generate_rb_experiments(bm, qubit_groups, depths, interleaved_gate=gate)
```

### Run the standard RB experiment¶

This takes around a minute. We extract the estimated decay parameter and plot the results.

```
[4]:
```

```
# NBVAL_SKIP
# tag this cell to be skipped during testing
# Run the RB Sequences on a QuantumComputer
num_shots=100
rb_results = acquire_rb_data(qc, rb_expts, num_shots, show_progress_bar=True)
# Calculate a fit to a decay curve
stats = get_stats_by_qubit_group(qubit_groups, rb_results)[qubit_groups[0]]
fit = fit_rb_results(depths, stats['expectation'], stats['std_err'], num_shots)
# Extract rb decay parameter
rb_decay = fit.params['decay'].value
rb_decay_error = fit.params['decay'].stderr
# Plot
fig, axs = plot_figure_for_fit(fit, xlabel="Sequence Length [Cliffords]", ylabel="Survival Probability")
```

```
100%|██████████| 125/125 [01:29<00:00, 1.40it/s]
```

### Now run the interleaved RB experiment¶

This takes a bit longer than the last cell.

```
[5]:
```

```
# NBVAL_SKIP
# tag this cell to be skipped during testing
# Run the RB Sequences on a QuantumComputer
num_shots=100
inter_results = acquire_rb_data(qc, inter_expts, num_shots, show_progress_bar=True)
# Calculate a fit to a decay curve
stats = get_stats_by_qubit_group(qubit_groups, inter_results)[qubit_groups[0]]
fit = fit_rb_results(depths, stats['expectation'], stats['std_err'], num_shots)
# Extract irb decay parameter
irb_decay = fit.params['decay'].value
irb_decay_error = fit.params['decay'].stderr
# Plot
fig, axs = plot_figure_for_fit(fit, xlabel="Sequence Length [Cliffords]", ylabel="Survival Probability")
```

```
100%|██████████| 125/125 [01:41<00:00, 1.23it/s]
```

We expect the irb_decay to be somewhat smaller due to the effect of the extra instances of the interleaved gate adding noise.

```
[6]:
```

```
print(rb_decay)
print(irb_decay)
```

```
0.97288649829259
0.950257506322514
```

### Extracting gate error and bounds¶

Using these two decay values we can estimate the error on our chosen gate.

```
[7]:
```

```
dim = 2**len(qubits)
print(rb_decay_to_gate_error(rb_decay, dim))
gate_error = irb_decay_to_gate_error(irb_decay, rb_decay, dim)
print(gate_error)
```

```
0.020335126280557503
0.017444731741413116
```

We can also get bounds on our estimate using the two decay values.

```
[8]:
```

```
bounds = interleaved_gate_fidelity_bounds(irb_decay, rb_decay, dim)
print(bounds)
```

```
[0.959329747438885, 1.0057807890782888]
```

```
[9]:
```

```
assert(bounds[0] < 1-gate_error and 1-gate_error < bounds[1])
```

### Improving the bounds with unitarity¶

To get improved bounds we can run an additional unitarity experiment, following the method described in [U+IRB].

See `examples/randomized_benchmarking_unitarity.ipynb`

if unitarity RB is unfamiliar. This is particularly SLOW.

*Efficiently characterizing the total error in quantum circuits*.

```
[10]:
```

```
# NBVAL_SKIP
# tag this cell to be skipped during testing. It is particularly SLOW, around 6 minutes
num_shots = 50
expts = generate_unitarity_experiments(bm, qubit_groups, depths, num_sequences)
results = acquire_rb_data(qc, expts, num_shots, show_progress_bar=True)
stats = get_stats_by_qubit_group(qubit_groups, results)[qubit_groups[0]]
fit = fit_unitarity_results(depths, stats['expectation'], stats['std_err'])
# plot the raw data, point estimate error bars, and fit
fig, axs = plot_figure_for_fit(fit, xlabel="Sequence Length [Cliffords]", ylabel="Shifted Purity")
unitarity = fit.params['decay'].value
```

```
100%|██████████| 125/125 [07:32<00:00, 3.62s/it]
```

Hopefully the `unitarity`

can be incorporated to improve our bounds. However, the bounds might be `NaN`

depending on the outcome of the unitarity and difference between rb and irb decays. Getting better estimates of each parameter helps prevent this.

```
[11]:
```

```
better_bounds = interleaved_gate_fidelity_bounds(irb_decay, rb_decay, dim, unitarity)
print(better_bounds)
```

```
[0.9597518532587754, 0.9925052728337453]
```

```
[ ]:
```

```
```