4.2. Block
4.2.1. Definition
A Block is a dataclass that describes a QEC code that is compatible with Loom modules and more particularly with Eka.
Note that at no point a Block will represent a full quantum state: they describe a subspace in which one can encode logical information using operations.
It is a general representation of a stabilizer code.
4.2.2. Example
Let’s create a Block object that represents a simple repetition code.
We start by defining the Lattice that sets the geometry of the system. Since the repetition code can be represented on a line, we choose the linear lattice:
from loom.eka import Lattice
linear_lattice = Lattice.linear(lattice_size=(3,))
While it is not necessary to create a Block object, it is useful to know what the lattice is like for indexing purpose.
Once the lattice is defined, we can create the code stabilizers. To create a bit-flip repetition code, we need two \(ZZ\) checks.
from loom.eka import Stabilizer
repetition_stabs = [
Stabilizer(
pauli="ZZ",
data_qubits=((i, 0), (i+1, 0)),
ancilla_qubits=((i, 1),)
)
for i in range(2)
]
We need to make sure that the qubit indices are compatible with the geometry we defined previously.
Then come the logical operators. The bit-flip operators are defined by a single \(Z\) operator and a \(XXX\) operator (spanning the whole set of data qubits):
from loom.eka import PauliOperator
x_op = PauliOperator(pauli="Z", data_qubits=((0, 0),))
z_op = PauliOperator(pauli="XXX", data_qubits=((0, 0), (1, 0), (2, 0)))
Finally, we need to specify how to measure the stabilizers we created using SyndromeCircuit objects and mapping them with the stabilizers. Since we are measuring the stabilizers of a repetition code, we only need to define a single circuit blueprint that will be used by both stabilizers:
from loom.eka import SyndromeCircuit
zz_circuit = SyndromeCircuit(
pauli="ZZ",
name="zz",
circuit=None, # This creates a default circuit to measure the given pauli string
)
stab_to_circuit = {
stab.uuid: zz_circuit.uuid for stab in repetition_stabs
}
Note that this step may be ignored. In that case, a default set of SyndromeCircuit is provided but there is no guarantee when it comes to fault tolerance properties.
Once we have defined all the different components, we can assemble them together and create the Block object:
from loom.eka import Block
my_rep_code = Block(
code_type="custom",
unique_label="qubit_1",
stabilizers=repetition_stabs,
logical_x_operators=[x_op],
logical_z_operators=[z_op],
syndrome_circuits=[zz_circuit],
stabilizer_to_syndrome_circuit=stab_to_circuit,
)
This process can be automated and we provide multiple code factories for well-known codes.
4.2.3. Validations
There is a comprehensive set of validations applied at the construction of Block to ensure that some properties are satisfied. These checks ensure that a user defining a custom code does not result in an erroneous definition of a QEC code. We use the notation \([\![n, k, d]\!]\) for a code that encodes \(k\) logical qubits in \(n\) physical qubits and whose distance is \(d\).
The list of validators includes:
Number of logical operators: the number of logical \(X\) and \(Z\) operators is the same. They define the number of logical qubits encoded in the
Block.\(|\{\mathcal{L}_{X}\}| = |\{\mathcal{L}_{Z}\}|\)
All physical qubits contained in logical operators are also contained in stabilizers.
All stabilizers and logical operators are distinct: no object is repeated in the specification of the code.
Commutation relations of stabilizers: all stabilizers should commute one-to-one.
\([s_i, s_j] = 0, \forall s_i, s_j \in \{\mathcal{S}\}\)
Commutation relations of logical operators: all logical \(X\) operators should commute one-to-one, all logical \(Z\) operators should commute one-to-one.
\([x_i, x_j] = 0, \forall x_i, x_j \in \{\mathcal{L}_X\}\),
\([z_i, z_j] = 0, \forall z_i, z_j \in \{\mathcal{L}_Z\}\)
Commutation of logical operators with stabilizers: all logical operators commute with all stabilizers.
\([l_i, s_j] = 0, \forall l_i \in \{\mathcal{L}_{X,Z}\}, \forall s_j \in \{\mathcal{S}\}\)
Anti-commutation relations of logical operators: all logical \(X\) operators anti-commute with exactly one logical \(Z\) operator (and vice-versa). The non-commuting logical operators share the same index by convention.
\([x_i, z_j] = 0, \forall x_i, z_j \in \{\mathcal{L}_{X,Z}\}, i\neq j\)
\(\{x_i, z_i\} = 0, \forall x_i \in \{\mathcal{L}_X\} \text{ and } z_i \in \{\mathcal{L}_Z\}\)
The number of physical qubits, stabilizers, and logical operators is consistent with the code: for a given code \([\![n, k, d]\!]\), we have \(n - k\) independent stabilizers acting (non-trivially) on a total of \(n\) qubits, \(k\) logical \(X\) operators, and \(k\) logical \(Z\) operators. Since only the number of independent stabilizer matters, we can use an overdefined set.
\(\text{support}(\{\mathcal{S}\}) = n\)
\(|\{\mathcal{S}\}| = n-k\)
\(|\{\mathcal{L}_X\}| = |\{\mathcal{L}_Z\}| = k\)
Note: for better performance when it comes to object creation, the validation can be turned off by using skip_validation=True when instantiating a Block.
It should only be set to false if the block arguments are known to produce a valid object. We recommend using this keyword only for the pre-defined code factories that we provide.
4.2.4. Assumptions
A block assumes that there exists a stabilizer representation of the QEC code and that Circuit objects are mapped to these stabilizers. Non-stabilizer codes are not natively supported.