PanQEC basics

In this tutorial, we will learn how the library is structured and what we can do with it.

Codes, noise models and decoders

PanQEC is made of three main ingredients: the codes, the noise models and the decoders. Let’s take a look at an example of how those components can interact.

[1]:
from panqec.codes import Toric2DCode
from panqec.error_models import PauliErrorModel
from panqec.decoders import MatchingDecoder

Let’s start by instantiating the following elements:

  1. A 2D toric code of lattice size \(L=4\)

  2. A Pauli error model where 20% of errors are Pauli \(X\) errors, 30% are Pauli \(Y\), and 50% Pauli \(Z\), and the total probability of error is \(p=0.1\), which is called the physical error rate.

  3. A minimum-weight perfect matching decoder based on the library PyMatching

[2]:
code = Toric2DCode(4)

error_model = PauliErrorModel(0.2, 0.3, 0.5)
p = 0.1

decoder = MatchingDecoder(code, error_model, p)

We can then use the error model to generate some errors, from which we can extract a syndrome

[3]:
errors = error_model.generate(code, p)
syndrome = code.measure_syndrome(errors)

print("Errors: ", errors)
print("Syndrome: ", syndrome)
Errors:  [0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0]
Syndrome:  [0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 1 1 0 0 0 0 1 0 0 0 1 0 0 0 0 0]

The error vector is represented in the so-called binary symplectic format. It is an array of size \(2n\) (with \(n\) the number of physical qubits), such that the first half of the array describes the presence or absence of Pauli \(X\) operators, and the second half of Pauli \(Z\) operators.

And finally we can decode the syndrome using our matching decoder, and check if the decoding succeeded

[4]:
# Obtain the correction by decoding the syndrome
correction = decoder.decode(syndrome)
print("Correction: ", correction)

# Get the remaining error once the correction has been applied
residual_error = (correction + errors) % 2
print("Residual error: ", residual_error)

# Check whether the residual error is in the codespace
# (i.e. whether there is any remaining excitation)
in_codespace = code.in_codespace(residual_error)
print("Is in codespace: ", in_codespace)

# Get the logical errors
logical_errors = code.logical_errors(residual_error)
print("Logical errors: ", logical_errors)

# Check whether the decoding succeeded
success = not code.is_logical_error(residual_error) and in_codespace
print("Success: ", success)
Correction:  [0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0]
Residual error:  [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
Is in codespace:  True
Logical errors:  [0 0 0 0]
Success:  True

Code properties

\([[n, k, d]]\)

The first properties we might want to extract from a code are the number of physical qubits \(n\), the number of logical qubits \(k\), and the distance \(d\) of the code.

[5]:
code = code = Toric2DCode(4)

print(f"[[n,k,d]]=[[{code.n},{code.k},{code.d}]]")
[[n,k,d]]=[[32,2,4]]

Parity-check matrix

From a computational perspective, a code is entirely described by its parity-check matrix. We can extract it using code.stabilizer_matrix:

[6]:
code.stabilizer_matrix
[6]:
<32x64 sparse matrix of type '<class 'numpy.uint8'>'
        with 128 stored elements in Compressed Sparse Row format>

As you can see from the output, the parity-check matrix is represented in a sparse format (as a scipy.sparse.csr_matrix), meaning that only the 1s are stored in memory. If you need it as a (dense) numpy array, you can use the method .toarray() to do the conversion:

[7]:
code.stabilizer_matrix.toarray()
[7]:
array([[0, 0, 0, ..., 0, 0, 0],
       [0, 0, 0, ..., 0, 0, 0],
       [0, 0, 0, ..., 0, 0, 0],
       ...,
       [0, 0, 0, ..., 0, 0, 0],
       [0, 0, 0, ..., 0, 0, 0],
       [0, 0, 0, ..., 0, 0, 0]], dtype=uint8)

Just like the error vectors, parity-check matrices are stored in binary symplectic format

For CSS codes, the stabilizer matrix can be separated into \(H_X\) and \(H_Z\) submatrices, made respectively of \(X\) and \(Z\) pauli operators. In the case of the 2D toric code, \(H_X\) describes the vertex stabilizers and \(H_Z\) the face stabilizers. It is possible to extract those matrices using the properties code.Hx and code.Hz of CSS codes:

[8]:
# Check that the code is CSS
print("Is CSS: ", code.is_css)

# Extract Hx and Hz
print("Hx shape", code.Hx.shape)
print("Hz shape", code.Hz.shape)
Is CSS:  True
Hx shape (16, 32)
Hz shape (16, 32)

When dealing with CSS codes, it is sometimes useful to get the \(X\) part and the \(Z\) part of the syndrome separately. For instance, in the 2D toric code, you might want to decode vertex and face excitations in different steps. Here is how to decompose the syndrome in PanQEC:

[9]:
error_model = PauliErrorModel(0.2, 0.3, 0.5)
p = 0.1
errors = error_model.generate(code, p)
syndrome = code.measure_syndrome(errors)

vertex_syndrome = code.extract_x_syndrome(syndrome)
face_syndrome = code.extract_z_syndrome(syndrome)

print("Vertex syndrome", vertex_syndrome)
print("Face syndrome", face_syndrome)
Vertex syndrome [0 0 1 1 0 1 0 0 1 0 1 0 0 0 0 1]
Face syndrome [0 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0]

Logical operators

Codes are also provided with logical operators, that you can access using code.logicals_x and code.logicals_z. They will give you arrays with \(2n\) columns and one logical operator per row (in binary symplectic format).

[10]:
print("Logicals X", code.logicals_x)
print("Logicals Z", code.logicals_z)
Logicals X [[1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
  0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
  0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]]
Logicals Z [[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1
  0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
  0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0]]