Skip to content

Memory Experiment

In this tutorial, we’ll learn how to build one of the simplest Quantum Error Correction (QEC) experiments — the memory experiment.
We’ll go step by step through the process: from building the lattice, to defining stabilizers and logical operators, and finally setting up the experiment sequence.


Step 1: Create the Lattice

A lattice is a geometric arrangement of qubits that defines how they interact and how information flows through the system.

In this example, we’ll use a square lattice, which is one of the most common configurations in surface codes.

Each qubit in the lattice can be addressed by its coordinates (x, y, b), where:

  • x and y represent spatial position in two dimensions,
  • b typically indicates the qubit type (e.g., data or ancilla).

We can create a square lattice using the following code:

from loom.eka import Lattice

dx = 4
dy = 4
lattice = Lattice.square_2d((dx, dy))

This creates a pattern where the qubits (0, 0, 0) and (0, 0, 1) repeat in a grid:

  • 4 times along the x-axis (to the right),
  • 4 times along the y-axis (downward).

Step 2: Define the QEC Code

A QEC code defines how quantum information is encoded on the physical qubits. In Loom, a Block object represents a logical qubit. Each block is defined by:

  • Stabilizers
  • Logical operators

Stabilizers

A stabilizer checks the parity of a group of data qubits. Each stabilizer acts on a specific set of qubits with an operator composed of "Z", "X" or "Y"s.

For example, the stabilizer below checks "ZZZZ" parity of four data qubits:

(1, 0, 0), (0, 0, 0), (1, 1, 0), and (0, 1, 0).

With qubit (1, 1, 1) as ancilla.

from loom.eka import Stabilizer

zz = Stabilizer(
            "ZZZZ",
            ((1, 0, 0), (0, 0, 0), (1, 1, 0), (0, 1, 0)),
            ancilla_qubits=((1, 1, 1),),
        )

Logical Operators

Logical operators define how the logical qubit is manipulated. They describe the operations that correspond to the logical X and Z gates of the encoded qubit.

We can define them using the PauliOperator class. Each operator is represented by a string (e.g., "XXX", "ZZZ") and the qubits it acts upon.

from loom.eka import PauliOperator

logical_x_operators = PauliOperator("XXX", ((0, 0, 0), (1, 0, 0), (2, 0, 0)))
logical_z_operators = PauliOperator("ZZZ", ((0, 0, 0), (0, 1, 0), (0, 2, 0)))

Here:

  • The logical X operator acts along the x-direction of the lattice
  • The logical Z operator acts along the y-direction

Step 3: Build the Logical Qubit Block

Now that we have stabilizers and logical operators, we can put them together into a single Block. This block represents our logical qubit and fully describes the structure of the rotated surface code used in this experiment.

from loom.eka import Block 

def build_3x3_rotated_surface_code() -> Block:
    rsc_stabilizers = (
        Stabilizer(
            "ZZZZ",
            ((1, 0, 0), (0, 0, 0), (1, 1, 0), (0, 1, 0)),
            ancilla_qubits=((1, 1, 1),),
        ),
        Stabilizer(
            "ZZZZ",
            ((2, 1, 0), (1, 1, 0), (2, 2, 0), (1, 2, 0)),
            ancilla_qubits=((2, 2, 1),),
        ),
        Stabilizer(
            "XXXX",
            ((1, 1, 0), (1, 2, 0), (0, 1, 0), (0, 2, 0)),
            ancilla_qubits=((1, 2, 1),),
        ),
        Stabilizer(
            "XXXX",
            ((2, 0, 0), (2, 1, 0), (1, 0, 0), (1, 1, 0)),
            ancilla_qubits=((2, 1, 1),),
        ),
        Stabilizer(
            "XX",
            ((0, 0, 0), (0, 1, 0)),
            ancilla_qubits=((0, 1, 1),),
        ),
        Stabilizer(
            "XX",
            ((2, 1, 0), (2, 2, 0)),
            ancilla_qubits=((3, 2, 1),),
        ),
        Stabilizer(
            "ZZ",
            ((2, 0, 0), (1, 0, 0)),
            ancilla_qubits=((2, 0, 1),),
        ),
        Stabilizer(
            "ZZ",
            ((1, 2, 0), (0, 2, 0)),
            ancilla_qubits=((1, 3, 1),),
        ),
    )
    rot_surf_code = Block(
        unique_label="q1",
        stabilizers=rsc_stabilizers,
        logical_x_operators=(PauliOperator("XXX", ((0, 0, 0), (1, 0, 0), (2, 0, 0))),),
        logical_z_operators=(PauliOperator("ZZZ", ((0, 0, 0), (0, 1, 0), (0, 2, 0))),),
    )
    return rot_surf_code

rot_surf_code = build_3x3_rotated_surface_code()

Now we have the logical qubit for our experiment.

Step 4: Construct the Memory Experiment

Now that we have a logical qubit defined, we can build the memory experiment itself.

The process follows three main steps:

  • Reset all data qubits to a known state (e.g., |0⟩).
  • Perform syndrome extraction, where stabilizers are measured to detect possible errors.
  • Measure the logical qubit, to determine whether the stored information has changed.

In Loom, we can define a logical circuit with an 'Eka' object. This object represents the logical circuit and contains all necessary information about the lattice, logical blocks, and the sequence of operations.

Each entry in the operations list corresponds to a moment in time, where all listed operations happen simultaneously. It’s crucial that each qubit participates in only one operation at a time, to avoid conflicts.

from loom.eka import Eka 
from loom.eka.operations import MeasureBlockSyndromes, MeasureLogical, ResetAllDataQubits

operations = [
    [ResetAllDataQubits("q1", state="0")],
    [MeasureBlockSyndromes("q1", n_cycles=1)],
    [MeasureLogicalZ("q1")],
]

memory_experiment = Eka(
    lattice=lattice,
    blocks=[
        rot_surf_code,
    ],
    operations=operations,
)

After running this code, the memory_experiment object will contain:

  • The lattice configuration
  • The logical qubit definition
  • The full timeline of operations

Next Steps

Congratulations! 🎉 — You’ve just created your first logical qubit for a memory experiment!

In the next tutorial, we’ll explore how to interpret the Eka representation of the memory experiment and run it on a backend.