What additional options are available when using DDD?#

Most additional functionalities when using DDD with Mitiq are related to the choice of dynamical decoupling sequences. Since the idle windows of a quantum circuit can have different sizes, one cannot directly define a unique fixed DDD sequence. One can instead define a DDD rule, i.e., a Python function that generates a gate sequence from an input slack length.

We’ll discuss several ways of defining DDD rules:

  1. Selecting from the built in rules;

  2. Defining complex rules with the general_rule() and repeated_rule() functions;

  3. Defining custom rules from scratch;

  4. Nesting rules to fill long slack windows first, followed by shorter slack windows.

Built-in DDD rules#

Mitiq provides basic built in rules to approximate dynamical decoupling sequences that are most used and discussed in the literature (for more details see What is the theory behind DDD?). For example, the XX, YY, and XYXY rules generate the corresponding gate sequences spaced evenly over the input slack window. For each of these rules, the user may specify a different spacing between the gates in the sequence and pass the desired option as shown in the next code cell.

import numpy as np

from mitiq import ddd
from cirq import LineQubit, Circuit, rx, rz, CNOT, X, Y, H, Z, SWAP, DensityMatrixSimulator, amplitude_damp

a, b = LineQubit.range(2)
circuit_one = Circuit(
    rx(0.1).on(a),
    rx(0.1).on(a),
    rz(0.4).on(a),
    rx(-0.72).on(a),
    rz(0.2).on(a),
    rx(0.1).on(a),
    rx(0.1).on(a),
    rz(0.4).on(a),
    rx(-0.72).on(a),
    rz(0.2).on(a),
    rx(-0.8).on(b),
    CNOT.on(a, b),
)

def execute(circuit, noise_level=0.1):
    """Returns Tr[ρ |0⟩⟨0|] where ρ is the state prepared by the circuit
    executed with amplitude damping noise.
    """
    # Replace with code based on your frontend and backend.
    noisy_circuit = circuit.with_noise(amplitude_damp(gamma=noise_level))
    rho = DensityMatrixSimulator().simulate(noisy_circuit).final_density_matrix
    return rho[0, 0].real

  
rule = ddd.rules.xx
mitigated_result = ddd.execute_with_ddd(
    circuit=circuit_one, 
    executor=execute, 
    rule=rule,
    rule_args={"spacing": 0},
)

Note

The default value of the spacing option is -1, which generates sequences with the maximum spacing that can fit the size of a slack window.

More general sequences#

If the user wishes to experiment with creating other gate sequences, a general_rule() is provided, which takes as input a list of gates and their spacing. As an example, let’s define a rule function that will generate an XXYY sequence:

def xxyy(slack_length, spacing = -1):
    xxyy_sequence = ddd.rules.general_rule(
        slack_length=slack_length,
        spacing=spacing,
        gates=[X, X, Y, Y],
    )
    return xxyy_sequence

# Test
xxyy(slack_length=9)
0: ───I───X───I───X───I───Y───I───Y───I───

A rule defined in this manner can similarly be used with execute_with_ddd() to error-mitigate expectation values as shown at the top of this notebook.

To create a rule that generates repeated DDD sequences, we can use the repeated_rule() abstraction. The function repeated_rule() fills slack windows with as many repetitions as possible of some input elementary sequence.

def repeated_xxyy(slack_length):
    return ddd.rules.repeated_rule(slack_length=slack_length, gates=[X, X, Y, Y])

# Test
repeated_xxyy(8)
0: ───X───X───Y───Y───X───X───Y───Y───

Custom DD rules#

Since in Mitiq a DDD rule is just a Python function, the user can define a custom rule from scratch. For example, the following rule returns sequences of Hadamard gates only if slack_length is 2 or 4.

import numpy as np 

def custom_rule(slack_length: int) -> Circuit:
    q = LineQubit(0)
    if slack_length == 2:
        sequence = Circuit([H(q), H(q)])
    elif slack_length == 4:
        sequence = Circuit([H(q), H(q), H(q), H(q)])
    else:
        sequence = Circuit()
    return sequence

# Test
print(custom_rule(2))
print(custom_rule(4))
0: ───H───H───
0: ───H───H───H───H───

Nested rules#

Suppose a user wants to mix sequences where, for example, XYXY is applied first to long slack windows and then XX is applied to all the shorter windows that are left over.

As demonstrated in detail in the next user guide section, the function insert_ddd_sequences() is all one needs to apply DDD. So, to apply two nested rules, one only needs to call insert_ddd_sequences() twice as shown in the following example.

qreg = LineQubit.range(8)
x_layer = Circuit(X.on_each(qreg))
cnots_layer = Circuit(SWAP.on(q, q + 1) for q in qreg[:-1])
input_circuit = x_layer + cnots_layer + x_layer
input_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───
long_rule = ddd.rules.xyxy
short_rule = ddd.rules.xx
circuit_with_xyxy = ddd.insert_ddd_sequences(input_circuit, rule=long_rule)
circuit_with_xyxy_and_xx = ddd.insert_ddd_sequences(circuit_with_xyxy, rule=short_rule)
circuit_with_xyxy_and_xx
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───X───×───×───I───X───X───X───
                      │
4: ───X───I───X───X───×───×───X───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───

As visible from the printed circuits, XYXY sequences have been added in long windows, while XX sequences have been added in short windows.

The associated unmitigated and mitigated expectation values are:

# Unmitigated expectation value
execute(input_circuit)
0.003185894
# Expectation value mitigated with nested DDD sequences
execute(circuit_with_xyxy_and_xx)
0.020129737