Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
NVIDIA
GitHub Repository: NVIDIA/cuda-q-academic
Path: blob/main/qec101/02_QEC_Stabilizers.ipynb
1128 views
Kernel: Python 3 (ipykernel)
# SPDX-License-Identifier: Apache-2.0 AND CC-BY-NC-4.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License.

QEC 101

Lab 2 - Stabilizers, the Shor code, and the Steane code

\renewcommand{\ket}[1]{|{#1}\rangle} \renewcommand{\bra}[1]{\langle{#1}|} This lab introduces the stabilizer formalism, a powerful tool for working with more sophisticated quantum error correction (QEC) codes. After a brief introduction to the theory, the lab will walk through the Shor and Steane codes with interactive coding exercises.

This lab was motivated by content from "Quantum Error Correction: an Introductory Guide" and "Quantum Error Correction for Dummies", both excellent resources we refer readers to for additional detail. For a more technical introduction, see chapter 10 of "Quantum Computation and Quantum Information" or the PhD thesis where the concept of stabilizer codes was introduced.

This is the second lab in the QEC series. If you are not familiar with the basics of classical or quantum error correction (EC), please complete the first lab in this series.

The list below outlines what you'll be doing in each section of this lab:

  • 2.1 Define stabilizers and why they are important

  • 2.2 Interactively Learn and Code the Steane Code in CUDA-Q.

  • 2.3 Perform Steane Code Capacity Analysis with CUDA-QX

  • 2.4 Interactively Learn and Code the Shor Code in CUDA-Q.

Lab 2 Learning Objectives:

  • Understand what a stabilizer is, how it works, and why it is important

  • Understand the approach of the Shor and Steane codes

  • Understand logical operators

  • Code the Shor and Steane codes in CUDA-Q

Execute the cells below to load all the necessary packages for this lab.

## Instructions for Google Colab. You can ignore this cell if you have cuda-q set up and have # all the dependent files on your system # Uncomment the lines below and execute the cell to install cuda-q #!pip install cudaq #!pip install cudaq_qec #!wget -q https://github.com/nvidia/cuda-q-academic/archive/refs/heads/main.zip #!unzip -q main.zip #!mv cuda-q-academic-main/qec101/Images ./Images
import cudaq from cudaq import spin from cudaq.qis import * import numpy as np import matplotlib.pyplot as plt from typing import List

2.1 Stabilizers and Logical Operators

An important subclass of QEC codes, known as stabilizer codes, use special operations called stabilizers to clean up errors in encoded quantum information, and thus "stabilize" the state.

An operation ss acting on a state ψ\ket{\psi} is said to be a stabilizer of the state if the state is a +1 eigenstate of the operation sψ=+1ψs \ket{\psi} = +1 \ket{\psi}. The high-level intuiton here is that if small errors have accumulated in a logically encoded state, the action of applying this stabilizer is to project the state back to a perfectly error-free state, and we measure +1+1. Sometimes larger errors occur, and we do not measure +1+1, which informs us something has gone wrong.

In lab 1, the codespace was defined by the set of basis codewords, such as 000\ket{000} and 111\ket{111} for the 3-qubit quantum repetition code. In that lab the codewords were provided to you for each code, but in a stabilizer code, we can equivalently define the codespace by providing the stabilizers which stabilize each basis codeword. In practice, this process of defining a code by the stabilizers is much more efficient and scalable as the codes grow larger.

The codespace CC can be defined as formed by all ψ\ket{\psi} such that siψ=+1ψs_i\ket{\psi} = +1 \ket{\psi} for each siSs_i\in S, where these sis_i are stabilizers which form a group SS (note: in some texts this group SS is called the stabilizer, not the elements). That is, the codespace is the joint +1 eigenspace fixed by the stabilizers.

Again in lab 1, we were given the codespace and error space for the 3-qubit quantum repetition code up front. However, let's think about working backwards from the computational basis for the 3-qubit Hilbert space. These can be sorted based on the eigenvalues returned when operated on by all elements of the stabilizer group S={Z1Z2,Z2Z3}S = \{Z_1Z_2, Z_2Z_3\}:

| Basis state | Eigenvalue for Z1Z2Z_1Z_2 | Eigenvalue for Z2Z3Z_2Z_3 | | ----------- | ----------- | ---------- | | 000\ket{000} | 1 | 1 | | 001\ket{001} | 1 | -1 | | 010\ket{010} | -1 | -1 | | 100\ket{100} | -1 | 1 | | 011\ket{011} | -1 | 1 | | 101\ket{101} | -1 | -1 | | 110\ket{110} | 1 | -1 | | 111\ket{111} | 1 | 1 |

The basis states that have an eigenvalue of 1 for both Z1Z2Z_1Z_2 and Z2Z3Z_2Z_3 make up the codespace 000\ket{000} and 111\ket{111}. There are other valid stabilizers in this code, such as Z1Z3Z_1 Z_3, but any stabilizer group can be boiled down into a minimal set which can be multiplied together to generate all of the others.

This is a really powerful approach, because it eliminates the need to derive and document the basis of the codespace in advance. Instead, one can simply define an appropriate set of stabilizers to establish the codespace.

Stabilizer codes are usually characterized as [[n,k,d]][[n,k,d]] (double brackets for quantum codes) where nn is the number of physical qubits encoding kk logical qubits with distance dd. It is always the case that these codes require nkn-k stabilizers. The reason for this is that each stabilizer splits the original 2n2^n Hilbert space in two (the +1 and -1 eigenspace), with 21=22^1 = 2 degrees of freedom remaining to define the logical qubit.

The trick then becomes finding good sets of stabilizers that correspond to QEC codes with favorable properties.

Stabilizer Properties

Three key properties for [[n,k,d]][[n,k,d]] stabilizers:

  1. Here we consider only to Pauli product stabilizers, that is, sis_i needs to be a Pauli-group element. The n-qubit Pauli group GnG_n is a special group constructed from the Pauli matrices:

I=(1001),X=(0110),Y=(0ii0),Z=(1001)I = \begin{pmatrix} 1 & 0 \\ 0 & 1 \end{pmatrix}, \quad X = \begin{pmatrix} 0 & 1 \\ 1 & 0 \end{pmatrix}, \quad Y = \begin{pmatrix} 0 & -i \\ i & 0 \end{pmatrix}, \quad Z = \begin{pmatrix} 1 & 0 \\ 0 & -1 \end{pmatrix}

The group GnG_n consists of 44n4*4^n elements and is built by forming a group that begins with all possible length nn Pauli words. For example G3G_3 has terms like IZZIZZ, ZXZZXZ, YYYYYY, etc. The group is then closed by including the possible scalar cofficients which arise from multiplying these terms {1,1,i,i}\{ 1, -1, i, -i\}. So G1G_1 would be

G1{±I,±iI,±Z,±iZ,±X,±iX,±Y,±iY}G_1 \equiv \{\pm I, \pm iI,\pm Z, \pm iZ,\pm X, \pm iX,\pm Y, \pm iY\}
  1. Each sis_i must be able to operate on every logical state ψL\ket{\psi_L}. Furthermore, this action should leave each ψL\ket{\psi_L} fixed (i.e., the eigenvalue of ψL\ket{\psi_L} should be +1 for each logical state).

  2. Stabilizers need to be measurable in any order. This means each stabilizer needs to commute with every other stabilizer (that is, sisj=sjsis_is_j = s_js_i, or equivalently [si,sj]sisjsjsi=0[s_i, s_j] \equiv s_is_j - s_js_i=0).

Logical Operators

In addition to stabilizers, each [[n,k,d]][[n,k,d]] code has 2k2k logical operators (Xˉi\bar{X}_i and Zˉi\bar{Z}_i) which perform XX and ZZ operations on the logical states. For example:

Zˉ2Xˉ111L=Zˉ201L=01L\bar{Z}_2\bar{X}_1\ket{11}_L = \bar{Z}_2\ket{01}_L = -\ket{01}_L

These logical operators must satisfy two properties.

  1. Xˉi\bar{X}_i and Zˉi\bar{Z}_i commute with all stabilizers.

  2. Xˉi\bar{X}_i and Zˉi\bar{Z}_i must anticommute with one another if acting on the same logical qubit (i.e., {Xˉi,Zˉj}XˉiZˉj+ZˉjXˉi=2δijI\{\bar{X}_i,\bar{Z}_j\}\equiv \bar{X}_i\bar{Z}_j + \bar{Z}_j\bar{X}_i= 2\delta_{ij}I for all i,ji,j).

The next few sections will make use of stabilizers to solidify these concepts and enable coding of QEC codes far more interesting than the quantum repetition code.

2.2 The Steane Code

The Steane code is a famous QEC code that is the quantum version of the [7,4,3] Hamming code introduced in the first QEC lab. One immediate difference is that the Steane code encodes a single logical qubit making it a [[7,1,3]] code.

Remember, that the Hamming code adds additional parity bits that help "triangulate" where an error occurred. In the lab 1 exercises you constructed the generator matrix GG and used it to produce the logical codewords in the classical Hamming code. For example, b=0110b=0110 was encoded as

c=bG=[0110][1000110010010100100110001111]=[0110110]c = bG= \begin{bmatrix} 0 & 1 & 1 & 0 \end{bmatrix} \cdot \begin{bmatrix} 1 & 0 & 0 & 0 & 1 & 1 & 0 \\ 0 & 1 & 0 & 0 & 1 & 0 & 1 \\ 0 & 0 & 1 & 0 & 0 & 1 & 1 \\ 0 & 0 & 0 & 1 & 1 & 1 & 1 \end{bmatrix} = \begin{bmatrix} 0 & 1& 1 & 0 & 1& 1& 0 \end{bmatrix}

Any logically encoded state, cc, could then be multiplied by the parity check matrix (HH) to determine if any syndromes were triggered or not.

HcT=[110110010110100111001][0110110]=[000].Hc^T = \begin{bmatrix} 1 & 1 & 0 & 1 & 1 & 0 & 0 \\ 1 & 0 & 1 & 1 & 0 & 1 & 0 \\ 0 & 1 & 1 & 1 & 0 & 0 & 1 \end{bmatrix} \cdot \begin{bmatrix} 0 \\ 1 \\ 1 \\ 0 \\ 1 \\ 1 \\ 0 \end{bmatrix} = \begin{bmatrix} 0 \\ 0 \\ 0 \end{bmatrix}.

What was not discussed in the Hamming code section was the fact that the parity check matrix (HH) can be used to define the codespace. A valid logical codeword is any cc that satisfies the relationship HcT=[000]Hc^T=\begin{bmatrix} 0 \\ 0 \\ 0 \end{bmatrix}. As there are 7 data bits, that means there are 27=1282^7=128 possible encoded states between the codespace and error space. It turns out, 16 of these fall within the codespace and 112 are in the error space. Of the 16 in the codespace, 8 have even parity (even number of 1's) while the other half has odd parity.

| Even Bitstrings in Codespace | Odd Bitstrings in Codespace | | ----------- | ----------- | | 0000000 | 1111111 | | 0001111 | 1110000 | | 0110110 | 1001001 | | 0111001 | 1000110 | | 1010101 | 0101010 | | 1011010 | 0100101 | | 1100011 | 0011100 | | 1101100 | 0010011 |

This provides us a way to define the logical code words: 0L\ket{0}_L and 1L\ket{1}_L. The logical codewords, 0L\ket{0}_L and 1L\ket{1}_L, for the Steane code are superpositions over the states corresponding to the classic even and odd codewords, respectively.

0L=18(0000000+0001111+0110110+0111001+1010101+1011010+1100011+1101100)\ket{0}_L = \frac{1}{\sqrt{8}}(\ket{0000000} +\ket{0001111} +\ket{0110110} + \ket{ 0111001} + \ket{1010101} + \ket{1011010}+ \ket{1100011} + \ket{1101100})1L=18(1111111+1110000+1001001+1000110+0101010+0100101+0011100+0010011)\ket{1}_L = \frac{1}{\sqrt{8}}(\ket{1111111} +\ket{1110000} +\ket{1001001} + \ket{1000110} + \ket{0101010} + \ket{0100101}+ \ket{0011100} + \ket{0010011})

You might notice by inspection, that 0L=Xˉ1L=X1X2X3X4X5X6X71L\ket{0}_L = \bar{X}\ket{1}_L = X_1X_2X_3X_4X_5X_6X_7\ket{1}_L. That is to say, flipping all the bits swaps logical states. Similarly, Zˉ=Z1Z2Z3Z4Z5Z6Z7\bar{Z} = Z_1Z_2Z_3Z_4Z_5Z_6Z_7 will flip the phase, transforming 12(0L+1L)\frac{1}{\sqrt{2}}(\ket{0}_L+\ket{1}_L) to 12(0L1L)\frac{1}{\sqrt{2}}(\ket{0}_L-\ket{1}_L)

The encoding circuit to produce the logical codewords is shown below, and is based off the constraints imposed by the parity check matrix.

Drawing

Exercise 1 - The Steane Code:

In the cell below, build a CUDA-Q kernel to encode the logical 0 state using the Steane code. Sample the circuit to prove that you indeed created the appropriate superposition. In the cells following, complete the entire Steane code by adding stabilizer checks and code to measure the logical state. Complete the numbered tasks as well to confirm your code works as expected.

@cudaq.kernel def steane_code(): """Prepares a kernel for the Steane Code Returns ------- cudaq.kernel Kernel for running the Steane code """ #Initialize Registers #TODO # Create a superposition over all possible combinations of parity check bits #TODO #Entangle states to enforce constraints of parity check matrix (circuit above) #TODO results = cudaq.sample(steane_code, shots_count=10000) print(results)

The Steane code is a member of an important family of stabilizer codes known as Calderbank-Shor-Steane (CSS) codes. A CSS code is characterized by the property that Z and X errors can be detected and corrected independently. A benefit of this, is that fewer ancilla qubits are required to produce the syndromes, and they can be reset and reused for each error. However, this procedure is also slower.

The stabilizers for ZZ-type errors are SZ={X1X2X5X4,X1X3X4X6,X2X3X4X7},S_Z = \{X_1X_2X_5X_4, X_1X_3X_4X_6, X_2X_3X_4X_7\}, while the stabilizers for XX-type errors are of similar form: SX={Z1Z2Z5Z4,Z1Z3Z4Z6,Z2Z3Z4Z7}S_X = \{Z_1Z_2Z_5Z_4, Z_1Z_3Z_4Z_6, Z_2Z_3Z_4Z_7\}.

Just as with the classical Hamming code, there is a nice way to visualize the syndrome results. The diagram below places each data qubit on each vertex. They are arranged such that each of the three sections or plaquettes corresponds to one of the stabilizers.

The syndromes can be visually interpreted by putting a colored X on the syndromes that are flagged. Each coloring of this graph uniquely corresponds to an error on a specific qubit which is why the Steane code is often referred to as a color code.

Drawing

You are now ready to code the rest of the Steane code. After encoding, introduce an XX error and ZZ error on the qubits of your choice. Try performing the XX and ZZ syndrome measurements using the same three ancilla qubits and resetting them in between. Make your code such that you can measure the data qubits and confirm the state of the logical qubit.

import cudaq @cudaq.kernel def steane_code(): """Prepares a kernel for the Steane Code Returns ------- cudaq.kernel Kernel for running the Steane code """ #Initialize Registers #TODO # Create a superposition over all possible combinations of parity check bits #TODO #Entangle states to enforce constraints of parity check matrix (circuit above) #TODO #Add Errors (Optional) #TODO # Perform Stabilizer checks for Z errors #TODO # Perform Stabilizer checks for X errors #TODO # Correct X errors #TODO # Correct Z errors #TODO results = cudaq.sample(steane_code, shots_count=1000) print(results) #Post-process Results #TODO

Now, test your code! Just measure in the ZZ basis as the same procedure could be performed with the XX basis.

  1. Try adding single XX errors, guess which stabilizers should flag and confrm they do.

  2. Add two errors. Confirm the code cannot correct the errors and a logical bitflip occurs.

  3. It turns out that like the Shor code, there are alternate choices for Xˉ\bar{X}. Modify your counting code above and test if X0X1X4X_0X_1X_4 or X0X4X5X_0X_4X_5 are valid choices for Xˉ\bar{X}.

2.3 Steane Code Capacity Analysis with CUDA-Q QEC

CUDA-QX is set of libraries that enable easy acceleration of quantum application development. One of the libraries, CUDA-Q QEC, is focused on error correction and can help expedite much of the work done above. This final section will demonstrate how to run a code capacity memory experiment with the Steane code.

A memory experiment is a procedure to test how well a protocol can preserve quantum information. Such an experiment can help assess the quality of a QEC code but is often limited by assumptions that deviate from a realistic noise model. One such example is a code capacity experiment. A code capacity procedure determines the logical error rate of a QEC code under strict assumptions such as perfect gates or measurement. Code capacity experiments can help put an upper bound on a procedure's threshold and is therefore a good starting place to compare new codes.

The process is outlined in the diagram below. Assume the 0000000 bitstring is the baseline (no error). Bitflips are then randomly introduced and produce errors in the data vector to produce results like 0100010. If this were a real test on a physical quantum device, the data vector would not be known and a user could only proceed through the bottom path in the figure - performing syndrome extraction and then decoding the result to see if a logical flip occurred. In a code capacity experiment, the data vector with errors is known, so it can be used to directly compute if a logical state flip occurred or not. Dividing the number of times the actual (top path) and predicted (bottom path) results agree by the total number of rounds provides an estimate of the logical error rate for the code being tested.

Drawing

Exercise 2 - CUDA-Q QEC Code Capacity Experiment:

CUDA-Q QEC allows researchers to streamline experiments like this with just a few lines of code. Try running the cells below to compute the logical error rate of the Steane code under code capacity assumptions given probability of error pp.

import numpy as np import cudaq_qec as qec

Next, load the Steane code, which is already implemented in CUDA-Q QEC.

steane = qec.get_code("steane")

The parity check matrices and observables can also be extracted from the Steane code.

Hz = steane.get_parity_z() Hx = steane.get_parity_x() H = steane.get_parity() observable = steane.get_observables_z()

A decoder can then be specified which takes the parity check matrix as an input.

decoder = qec.get_decoder("single_error_lut", Hz)

Then, sample_code_capacity can be called and provided with p, the probability of any bit flipping, and the number of shots for the analysis.

p = 0.1 # set a probability of a bit flip error occuring nShots = 100 # specify the number of shots syndromes, data = qec.sample_code_capacity(Hz, nShots, p) for x in range(nShots): print("Data Qubits", data[x], "Syndromes", syndromes[x])

Notice how the Steane code is already defined within CUDA-Q QEC, along with a selection of decoders, and the sample_code_capacity API to automatically run the procedure. Otherwise, you would need to code the entire process from scratch like you did in section 2.2 for each QEC code you want to test!

If the experiment is repeated many times with different pp values, a plot can be generated like the one shown below. The purple line is the y=xy=x and corresponds to the case that the logical error rate is identical to the physical error rate. Anywhere the green line is below the purple line indicates that the Steane code was able to produce a logical error rate that is less than the physical error rate of the data qubits. When the green line is above the purple, the Steane code produced a worse logical error rate indicating that it would have been better to just use the data qubits and avoid the QEC procedure. The crossover point is an estimate for the code's threshold. Refining this estimate would require more sophisticated circuit level noise models that more accurately represent the performance of the Steane code under realistic conditions.

Drawing

Though code capacity has much room to improve, it is a great example of the utility of CUDA-Q QEC and how simple procedures can be streamlined so users can focus on testing codes rather than coding up the details of each test.

2.4 The Shor Code

The first QEC code was proposed by Peter Shor in 1995, known as the Shor code. The Shor code is a [[9,1,3]] code which uses 9 qubits to encode a single qubit, but can correct single XX or ZZ-type errors.

The motivation for the code, is that the 3-qubit repetition code can correct bit flip errors but not phase flip errors. We can consider why this is by examining the encoded +L\ket{+}_L state, which looks like the following:

+L=12(0L 3bit+1L 3bit)=12(000+111)\ket{+}_L = \frac{1}{\sqrt{2}}(\ket{0}_{L~3\mathrm{bit}}+\ket{1}_{L~\mathrm{3bit}}) = \frac{1}{\sqrt{2}}(\ket{000}+\ket{111})

If a Z1Z_1, Z2Z_2, or a Z3Z_3 error occurs, the +L \ket{+}_L state is transformed to L \ket{-}_L, another valid codeword. This means there is no way to tell if a phase flip error occurred or not. One could produce the repetition code in the +L \ket{+}_L and L \ket{-}_L basis to correct ZZ errors, but then the same problem would persist for XX errors.

The ingenuity behind the Shor code is to concatenate two 3-bit repetition codes into a 9-qubit code that can detect both types of errors. The encoding process begins with the 3-bit encoding of the +\ket{+} state.

+3bit=12(000+111)\ket{+}_{\mathrm{3 bit}} = \frac{1}{\sqrt{2}}(\ket{000}+\ket{111})

Then, 0L\ket{0}_L is encoded by taking a tensor product of three +3bit\ket{+}_{\mathrm{3 bit}} states.

0L=+3bit+3bit+3bit\ket{0}_L = \ket{+}_{\mathrm{3 bit}} \otimes \ket{+}_{\mathrm{3 bit}} \otimes \ket{+}_{\mathrm{3 bit}}0L=18(000+111)(000+111)(000+111)\ket{0}_L = \frac{1}{\sqrt{8}}(\ket{000} + \ket{111})(\ket{000} + \ket{111})(\ket{000} + \ket{111})

The same process is completed for the 1L\ket{1}_L state, this time using 3bit\ket{-}_{\mathrm{3 bit}} as the starting point. 1L=18(000111)(000111)(000111) \ket{1}_L = \frac{1}{\sqrt{8}}(\ket{000} - \ket{111})(\ket{000} - \ket{111})(\ket{000} - \ket{111})

This encoding of ψ=α0+β1\psi = \alpha \ket{0} + \beta \ket{1} can be implemented with the following quantum circuit:

Drawing

The next consideration is to define the logical operators so that they behave as we expect, namely: Xˉ0L=1L\bar{X}\ket{0}_L = \ket{1}_L Xˉ1L=0L\bar{X}\ket{1}_L = \ket{0}_L and Zˉ0L=0L\bar{Z}\ket{0}_L = \ket{0}_L Zˉ1L=1L.\bar{Z}\ket{1}_L = -\ket{1}_L. In other words, we need the following equations to hold:

Xˉ0L=Xˉ18(000+111)(000+111)(000+111)=18(000111)(000111)(000111)=1L\bar{X}\ket{0}_L = \bar{X}\frac{1}{\sqrt{8}}(\ket{000} + \ket{111})(\ket{000} + \ket{111})(\ket{000} + \ket{111}) = \frac{1}{\sqrt{8}}(\ket{000} - \ket{111})(\ket{000} - \ket{111})(\ket{000} - \ket{111}) = \ket{1}_LZˉ1L=Zˉ18(000111)(000111)(000111)=18(111000)(111000)(111000)=1L\bar{Z}\ket{1}_L = \bar{Z}\frac{1}{\sqrt{8}}(\ket{000} - \ket{111})(\ket{000} - \ket{111})(\ket{000} - \ket{111}) = \frac{1}{\sqrt{8}}(\ket{111} - \ket{000})(\ket{111} - \ket{000})(\ket{111} - \ket{000}) = -\ket{1}_L

Can you see what the logical operators need to be?

For a logical bit flip to occur (Xˉ\bar{X}) the phase of each block needs to change. This is accomplished by performing a ZZ operation on one of the qubits in each block, thus Xˉ=Z1Z4Z7\bar{X} = Z_1Z_4Z_7 is a valid choice, though not the only choice as others like Xˉ=Z2Z5Z8\bar{X} = Z_2Z_5Z_8 or even Xˉ=Z1Z2Z3Z4Z5Z6Z7Z8Z9\bar{X} = Z_1Z_2Z_3Z_4Z_5Z_6Z_7Z_8Z_9 also work. Similarly, for Zˉ\bar{Z} to take 1L\ket{1}_L to 1L-\ket{1}_L (and 0L\ket{0}_L to itself) all of the bits need to flip, thus Zˉ=X1X2X3X4X5X6X7X8X9\bar{Z} = X_1X_2X_3X_4X_5X_6X_7X_8X_9. The curious reader can confirm that the anticommutativity holds between these logical operators and that they commute with each stabilizer discussed below.

Now, consider what happens when XX and ZZ-type errors corrupt a state encoded with the Shor code. If a bitflip error occurs on qubit 8 of the 0L\ket{0}_L state.

X80L=18(000+111)(000+111)(010+101)X_8\ket{0}_L = \frac{1}{\sqrt{8}}(\ket{000} + \ket{111})(\ket{000} + \ket{111})(\ket{010} + \ket{101})

The error only corrupts the third block of the code which houses the 8th qubit. So, this means stabilizers that perform parity checks on that block only (Z7Z8Z_7Z_8 and Z8Z9Z_8Z_9) are sufficient to determine which position experienced an error. Extending this, all bit flip errors can be corrected with the following six stabilizers, two for each block. Because each block is an independent repetition code, the Shor code can handle three bit flip errors, as long as they occur in distinct blocks.

Sbitflips={Z1Z2,Z2Z3,Z4Z5,Z5Z6,Z7Z8,Z8Z9}S_{\mathrm{bit flips}} = \{Z_1Z_2, Z_2Z_3, Z_4Z_5, Z_5Z_6, Z_7Z_8, Z_8Z_9\}

Now consider the impact of a a phase flip error that acts on the 6th qubit, for example.

Z60L=18(000+111)(000111)(000+111)Z_6\ket{0}_L = \frac{1}{\sqrt{8}}(\ket{000} + \ket{111})(\ket{000} - \ket{111})(\ket{000} + \ket{111})

The phase of the second block is changed which means the entire state can be rewritten as +3bit3bit+3bit\ket{+}_{\mathrm{3 bit}} \otimes \ket{-}_{\mathrm{3 bit}} \otimes \ket{+}_{\mathrm{3 bit}}. This "zoomed out" view makes it clear how the repetition code is leveraged again. This time a stabilizer is needed which can test the parity of block 1 with block 2 and block 2 with block 3.

The stabilizer X1X2X3X4X5X6X_1X_2X_3X_4X_5X_6 can be used for this which will return 1 if the first two blocks have the same phase and -1 if they differ. Work this out by hand to convince yourself this works if it is not obvious why this is the case. Similarly, X4X5X6X7X8X9X_4X_5X_6X_7X_8X_9 can test the parity of the second two blocks completing the stabilizers necessary to detect phase flip errors.

Sphaseflips={X1X2X3X4X5X6,X4X5X6X7X8X9}S_{\mathrm{phase flips}} = \{X_1X_2X_3X_4X_5X_6,X_4X_5X_6X_7X_8X_9 \}

All 8 stabilizers can correct any single-qubit ZZ or XX error as summarized in the table below. Note that the Shor code is a redundant code, meaning that certain syndromes correspond to multiple errors. At first this may seem problematic, but each error is fixed by the same correction, so knowing the specific source of the error is not always necessary.

| Error Type | Syndrome (Stabilizer Measurements) | | ----------- | ----------- | | No Error | 0 0 0 0 0 0 0 0 | | X1X_1 | 1 0 0 0 0 0 0 0 | | X2X_2 | 1 1 0 0 0 0 0 0 | | X3X_3 | 0 1 0 0 0 0 0 0 | | X4X_4 | 0 0 1 0 0 0 0 0 | | X5X_5 | 0 0 1 1 0 0 0 0 | | X6X_6 | 0 0 0 1 0 0 0 0 | | X7X_7 | 0 0 0 0 1 0 0 0 | | X8X_8 | 0 0 0 0 1 1 0 0 | | X9X_9 | 0 0 0 0 0 1 0 0 | | Z1Z_1 | 0 0 0 0 0 0 1 0 | | Z2Z_2 | 0 0 0 0 0 0 1 0 | | Z3Z_3 | 0 0 0 0 0 0 1 0 | | Z4Z_4 | 0 0 0 0 0 0 1 1 | | Z5Z_5 | 0 0 0 0 0 0 1 1 | | Z6Z_6 | 0 0 0 0 0 0 1 1 | | Z7Z_7 | 0 0 0 0 0 0 0 1 | | Z8Z_8 | 0 0 0 0 0 0 0 1 | | Z9Z_9 | 0 0 0 0 0 0 0 1 |

Exercise 3 - The Shor Code:

Now you have all of the backgound necessary to code the Shor code in CUDA-Q. Fill in the sections below to build up a kernel that performs Shor code encoding and syndrome checks. The kernel should be constructed such that you can apply errors and select mesurement in the ZZ or XX basis. Complete the tasks listed below to ensure your code works.

import cudaq @cudaq.kernel def shor_code(error_qubit: list[int], error_location: list[int], measure: int): """Prepares a kernel for the Shor Code Parameters ----------- error_qubit: list[int] a list where each element is an applied error designated as 1 =x or 2 =z error_location: list[int] each element corresponds to the index of the qubit which the error occurs on measure: int Option to measure in the z basis (0) or the x basis (1) Returns ------- cudaq.kernel Kernel for running the Shor code """ #Encode the data qubits with Shor encoding circuit. Hint: It might be helpful to create separate registers for the data and ancilla qubits #TODO # Initial Psi (25/75) distribution in Z and X basis ry(1.04772,data_qubits[0]) rz(1.521, data_qubits[0]) # Apply optional single qubit errors #TODO # Apply Hadamard gate to ancilla qubits #TODO # Apply the Bit Flip syndromes #TODO # Apply the phase flip syndromes #TODO # Apply Hadamard gate to ancilla qubits #TODO # Perform mid-circuit measurements to determine syndromes #TODO # Apply the appropriate corrections based on the results from the syndrome measurements #TODO #Perform Hadamard on data qubits to rotate out of X basis (because of concatonated code) #TODO #Measure in X or Z basis depending on kernel input h(data_qubits) # put a Hadamard before the measurement to transform back into the Z basis # An X basis measurement can be obtained by applying a second Hadamard before a Z basis measurement #TODO

The final Hadamard is necessary because the code is concatenated and the second layer is in the XX basis. Specifying the measurement basis allows you to confirm that the errors were or were not fixed.

You will also need to post process the results. In the case of ZZ basis measurement (where you see the impact of logical XX errors), you need to compute the parity of the logical XX operator Z1Z2Z3Z4Z5Z6Z7Z8Z9Z_1Z_2Z_3Z_4Z_5Z_6Z_7Z_8Z_9, by measuring all the qubits in the ZZ basis and computing the parity (Sum them and then mod 2) of the results.

The same can be done for an XX basis measurement (where you see the impact of logical ZZ errors). In this case you need to compute the parity of the logical ZZ operator X1X2X3X4X5X6X7X8X9X_1X_2X_3X_4X_5X_6X_7X_8X_9 by measuring all of the qubits in the XX basis and computing thier parity.

Write a postprocessing function below which takes results, computes the parity of each measurment, prints the number of 1's and 0's, and prints the results.

def post_process(results): """takes results from a CUDA-Q sample and prints the results and the number of 0's and 1's by computing the parity of the bitstrings. Parameters ----------- results: cudaq.SampleResult A dictionary of the results from sampling the quantum state """

Now, run your code through the following tests and confirm it is working well.

  1. Prepare ψ\ket{\psi} in the 0\ket{0} state. Sample your kernel with no errors in the ZZ and XX basis. Do you see a 100/0 and 50/50 distribution for each respectively? What do you notice about the bitstrings when you measure in each basis?

  2. Now, comment out the part of your code that fixes errors. Add a single ZZ error and measure in the ZZ and XX basis. Do you observe a bitflip in the ZZ basis results? Note, because the Shor code has an extra layer of Hadamard gates, a ZZ error impacts the ZZ observable which is not the usual case.

  3. Now prepare ψ\ket{\psi} in the +\ket{+} state. Comment out the part of your code that fixes errors, add a single XX error, and measure in the ZZ and XX basis. Do you observe a bitflip in the XX basis results now?

  4. Uncomment the part of your code that fixes the errors and run the same samples in 2 and 3. Did the correct syndrome flag and was the impact of the error ameliorated?

  5. Prepare ψ\ket{\psi} in the 0\ket{0} state again. With your full code, add multiple ZZ errors and note that the stabilizer checks cannot properly fix them as the code is only distance 3.

# TODO # Run tests on your Shor Code