# What happens when I use DDD?#

The workflow of Digital Dynamical Decoupling (DDD) in Mitiq is represented in the figure below.

The user provides a

`QPROGRAM`

, (i.e. a quantum circuit defined via any of the supported frontends).Mitiq modifies the input circuit with the insertion of DDD gate sequences in idle windows.

The modified circuit is executed via a user-defined Executor.

The error mitigated expectation value is returned to the user.

With respect to the workflows of other error-mitigation techniques (e.g. ZNE or PEC),
DDD involves the generation and the execution of a *single* modified circuit.
For this reason, there is no need to combine the results of multiple circuits and the final inference step which is necessary for other
techniques is instead trivial for DDD.

Note

When setting the `num_trials`

option to a value larger than one, multiple circuits are actually generated by Mitiq and
the associated results are averaged to obtain the final expectation value. This more general case is not shown in the figure since
it can be considered as an average of independent single-circuit workflows.

As shown in How do I use DDD?, the function `execute_with_ddd()`

applies DDD behind the scenes
and directly returns the error-mitigated expectation value.
In the next sections instead, we show how one can apply DDD at a lower level, i.e., by:

Characterizing all the slack windows in a circuit;

Inserting DDD sequences in all slack windows;

Executing the modified circuit.

## Analysis of idle windows (optional step)#

In this section we show how one can determine all the idle windows (often called slack windows) in a circuit and how long they are, i.e. how many single-qubit gates can fit each window. This is an optional step, since it is actually not necessary to apply DDD with Mitiq. Nonetheless, it provides additional information on the gate structure of the circuit that can be useful, especially for research purposes.

### The circuit mask#

A quantum circuit can be visualized as a 2D grid where the horizontal axis represents discrete time steps (often called moments) and the vertical axis represents the qubits of the circuit. Each gate occupies one or more grid cells, depending on the number of qubits it acts on.

This 2D grid is essentially what we get each time we print a circuit out.

```
from cirq import Circuit, X, SWAP, LineQubit
qreg = LineQubit.range(8)
x_layer = Circuit(X.on_each(qreg))
cnots_layer = Circuit(SWAP.on(q, q + 1) for q in qreg[:-1])
circuit = x_layer + cnots_layer + x_layer
circuit
```

0: ───X───×───────────────────────────X─── │ 1: ───X───×───×───────────────────────X─── │ 2: ───X───────×───×───────────────────X─── │ 3: ───X───────────×───×───────────────X─── │ 4: ───X───────────────×───×───────────X─── │ 5: ───X───────────────────×───×───────X─── │ 6: ───X───────────────────────×───×───X─── │ 7: ───X───────────────────────────×───X───

The grid structure of a circuit can be expressed as a *mask matrix* with \(1\) entries in cells that
are occupied by gates and \(0\) entries in empty cells. So, for example, the mask matrix of the above circuit is the following:

```
from mitiq import ddd
mask_matrix = ddd.insertion._get_circuit_mask(circuit)
mask_matrix
```

```
array([[1, 1, 0, 0, 0, 0, 0, 0, 1],
[1, 1, 1, 0, 0, 0, 0, 0, 1],
[1, 0, 1, 1, 0, 0, 0, 0, 1],
[1, 0, 0, 1, 1, 0, 0, 0, 1],
[1, 0, 0, 0, 1, 1, 0, 0, 1],
[1, 0, 0, 0, 0, 1, 1, 0, 1],
[1, 0, 0, 0, 0, 0, 1, 1, 1],
[1, 0, 0, 0, 0, 0, 0, 1, 1]])
```

A slack window is an horizontal and contiguous sequence of zeros in the mask matrix, corresponding to a qubit which is idling for a finite amount of time.

### The slack matrix#

To analyze the structure of idle windows, it is more convenient to define a *slack matrix*, i.e.,
a matrix of positive integers that are placed at the beginning of each slack window and whose value represent the
time length of that window. For example, in our simple example, we get:

```
slack_matrix = ddd.get_slack_matrix_from_circuit_mask(mask_matrix)
slack_matrix
```

```
array([[0, 0, 6, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 5, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 4, 0, 0, 0, 0],
[0, 2, 0, 0, 0, 3, 0, 0, 0],
[0, 3, 0, 0, 0, 0, 2, 0, 0],
[0, 4, 0, 0, 0, 0, 0, 1, 0],
[0, 5, 0, 0, 0, 0, 0, 0, 0],
[0, 6, 0, 0, 0, 0, 0, 0, 0]])
```

This matrix contains the same amount of information as `mask_matrix`

, but it is more convenient for the analysis
of idle windows as shown in the next code cell.

```
import numpy as np
print(f"The circuit contains {np.count_nonzero(slack_matrix)} slack windows.")
print(f"The maximum slack length is {np.max(slack_matrix)}.")
lengths, counts = np.unique(slack_matrix, return_counts=True)
length_distribution = dict(zip(lengths[1:], counts[1:]))
print(f"The full distribution of slack lengths is {length_distribution}.")
print(f"On average, each qubit spends {np.mean(slack_matrix) :.0%} of time in idle mode.")
```

```
The circuit contains 12 slack windows.
The maximum slack length is 6.
The full distribution of slack lengths is {1: 2, 2: 2, 3: 2, 4: 2, 5: 2, 6: 2}.
On average, each qubit spends 58% of time in idle mode.
```

## Inserting DDD sequences#

The DDD error mitigation technique consists of filling the slack windows of a circuit with DDD gate sequences.
This can be directly achieved via the function `insert_ddd_sequences()`

function.

```
xyxy_rule = ddd.rules.xyxy
circuit_with_ddd = ddd.insert_ddd_sequences(circuit, rule=xyxy_rule)
circuit_with_ddd
```

0: ───X───×───I───X───Y───X───Y───I───X─── │ 1: ───X───×───×───I───X───Y───X───Y───X─── │ 2: ───X───────×───×───X───Y───X───Y───X─── │ 3: ───X───────────×───×───────────────X─── │ 4: ───X───────────────×───×───────────X─── │ 5: ───X───X───Y───X───Y───×───×───────X─── │ 6: ───X───I───X───Y───X───Y───×───×───X─── │ 7: ───X───I───X───Y───X───Y───I───×───X───

Note

In principle, the function `insert_ddd_sequences()`

is all one needs to apply DDD.
Indeed, since in DDD there is not a final post-processing step, one can simply insert DDD sequences before running the circuit on a noisy backend.
As shown in the next section, this approach is exactly equivalent to the application of the standard Mitiq function `execute_with_ddd()`

.

## Executing the modified circuit#

```
from cirq import DensityMatrixSimulator, amplitude_damp
def execute(circuit, noise_level=0.003):
"""Returns Tr[ρ |00..⟩⟨00..|] where ρ is the state prepared by the circuit
executed with depolarizing noise.
"""
noisy_circuit = circuit.with_noise(amplitude_damp(noise_level))
rho = DensityMatrixSimulator().simulate(noisy_circuit).final_density_matrix
return rho[0, 0].real
```

If executed on a noiseless backend, `circuit_with_ddd`

and `circuit`

are equivalent.
On a real backend, they have a different sensitivity to noise. The core idea of the DDD technique is that,
`circuit_with_ddd`

is (hopefully) less sensitive to noise thanks to the particular structure of DDD sequences.

```
# Ideal result
execute(circuit, noise_level=0)
```

```
1.0
```

```
# Unmitigated result
execute(circuit)
```

```
0.8255487
```

```
# Error-mitigated result (with DDD)
execute(circuit_with_ddd)
```

```
0.85605556
```

As a final remark, we stress that the low-level procedure that we have shown is exactly what `execute_with_ddd()`

does behind the scenes.
Let’s verify this fact:

```
np.isclose(
ddd.execute_with_ddd(circuit, execute, rule=xyxy_rule),
execute(circuit_with_ddd),
)
```

```
True
```