Simulating a Quantum Error Correcting Code

This diagram illustrates a single qubit error correcting circuit. The input consists of a direct product of an encoded qubit |ψ⟩ ⊗|ψ⟩ ⊗|ψ⟩ , with a pair of ancillary qubits in state |0 ⟩⊗|0⟩.  The error box contains a Pauli-X gate which induces a flip error in one of the input qubits. The gates within the dotted line represent the syndrome measurements. The remaining CNOT and Toffoli gates represent error recovery of the input.

ClearAll["Global`*"]
(*(Subscript[ we define the bitamp function which maps a 5-qubit ket |k⟩, 5]) into its binary form \[VerticalSeparator](Subscript[k, 5])(Subscript[k, 4])(Subscript[k, 3])(Subscript[k, 2])(Subscript[k, 1])⟩ *)
bitmap[i_]:=IntegerDigits[i,2,5];
(* for example these commands result in *)
bitmap[0]
bitmap[31]

We also define the various CNOT (or conditional Pauli-X gates) (Subscript[X, i j]). Here subscript i represent the control qubit and j

(* Pauli-X gate mappings *)
X[0]=1;
X[1]=0;
(* Conditional Pauli-X (CNOT) gates *)
X35gate[in_]:=Which[in[[3]]==0,in,in[[3]]==1,{X[in[[1]]],in[[2]],in[[3]],in[[4]],in[[5]]}]
X25gate[in_]:=Which[in[[4]]==0,in,in[[4]]==1,{X[in[[1]]],in[[2]],in[[3]],in[[4]],in[[5]]}]
X24gate[in_]:=Which[in[[4]]==0,in,in[[4]]==1,{in[[1]],X[in[[2]]],in[[3]],in[[4]],in[[5]]}]
X14gate[in_]:=Which[in[[5]]==0,in,in[[5]]==1,{in[[1]],X[in[[2]]],in[[3]],in[[4]],in[[5]]}]

(* For example, let's check the truth Table for X14 gate. The first column are the inputs, the second column represents the output  *)
truthX13=Grid[Table[{bitmap[i],X14gate[bitmap[i]]},{i,0,31}] ]

Syndrome

Using the above gates we construct the syndrome portion of the circuit. For a given 5-qubit input the syndrome gives us the following output

syndrome[input_]:=X14gate[X24gate[X25gate[X35gate[input]]]]

Some examples of the syndrome outputs are

syndrome[{0,0,0,0,0}]
syndrome[{0,0,0,0,1}]
syndrome[{0,0,1,0,0}]

Note that the first three qubit values are unchanged following a syndrome evaluations

Error Recovery

To construct the error recovery circuit we need to define two additional CNOT gates and  a Toffoli gate.

(* 3 bit toffli toff[in_]:=If[in[[1]] in[[2]]==1,{in[[1]],in[[2]],X[in[[3]]]},in] *)

toff54to321[in_]:=If[in[[1]]==1 && in[[2]]==1,{in[[1]],in[[2]],X[in[[3]]],X[in[[4]]],X[in[[5]]]},in]
(*toff54to2[in_]:=If[in[[1]]==0 && in[[2]]==1,{in[[1]],in[[2]],in[[3]],X[in[[4]]],in[[5]]},in]*)
(*toff54to3[in_]:=If[in[[1]]==1 && in[[2]]==0,{in[[1]],in[[2]],X[in[[3]]],in[[4]],in[[5]]},in]*)
X53[in_]:=If[in[[1]]==1 ,{in[[1]],in[[2]],X[in[[3]]],in[[4]],in[[5]]},in]
X41[in_]:=If[in[[2]]==1 ,{in[[1]],in[[2]],in[[3]],in[[4]],X[in[[5]]]},in]

Finally we construct the series of gates, shown above, given by the QEC command defined below,

QECC[input_]:=toff54to321[X53[X41[syndrome[input]]]]

Suppose the input consists of the codeword  |000⟩, representing a logical state|0⟩,  as well as the three, bit-flip, corrupted states |001⟩, |010⟩, |100⟩.   
Likewise, the no-error state  |111⟩, representing a logical state |1⟩,  and the corresponding  bit-flip states |110⟩, |101⟩, |110⟩.   
These states are generated by an error box, that contains any of the four gates listed below

flip[a_]:=Which[a==0,1,a==1,0]
errorX0[{b1_,b2_,b3_}]:={0,0,b1,b2,b3}
errorX1[{b1_,b2_,b3_}]:={0,0,b1,b2,flip[b3]}
errorX2[{b1_,b2_,b3_}]:={0,0,b1,flip[b2],b3}
errorX3[{b1_,b2_,b3_}]:={0,0,flip[b1],b2,b3}

So errorX0, represents no-error, errorX1 incurs a bit-flip in the first qubit, and so on. L

(* Let's insert the 000 codeword into the error box, we get three types of errors, which we correct by with the QECC function *)
state={0,0,0};
errorX0[state]
QECC[%]
Print[" ---------"]
errorX1[state]
QECC[%]
Print[" ---------"]
errorX2[state]
QECC[%]
Print[" ---------"]
errorX3[state]
QECC[%]
Print[" ---------"]

Note that for each of the possible errors, QECC returns the original code-word 000 (located,in the first three qubits). 
Similarly, the code word 111 subjected to the error box, returns the original codeword after being processed by QECC

state={1,1,1};
errorX0[state]
QECC[%]
Print[" ---------"]
errorX1[state]
QECC[%]
Print[" ---------"]
errorX2[state]
QECC[%]
Print[" ---------"]
errorX3[state]
QECC[%]
Print[" ---------"]

QECC Simulation

We now consider a QECC simulation of a single-qubit bit flip error. Define a noisy channel, with probability p=0.1 that a bit value flips

noise[i_]:=Module[{p=0.1},prob=p;Which[i==1,RandomChoice[{p,1-p}->{0,1}],i==0,RandomChoice[{p,1-p}->{1,0}]]]

(*encode s ingle bit into its logical codeword *)
encode[i_]:=Which[i==0,{0,0,0},i==1,{1,1,1}]

(* define the error box, including the anciallry qubit assignments *)
noisebox[i_]:=Flatten[{0,0,Map[noise,encode[i]]}]

Simulation without error correction

With this choice for p, we instantiate 100 instances for this error box. Let’s assume the input state to the error box is |000⟩

Table[noisebox[0],{i,0,100}]

For a given set of simulations we count the number of successful transmissions, i.e. number of outputs containing qubit assignments 0,0,0 for the first three qubits

success=Count[Table[Take[noisebox[0],-3],{i,1,100}],{0,0,0}]

Thus the error rate is

errorate=N[1-success/100]

Including Quantum Error Correction

We now subject the QECC function, defined above, on the output of the error box, and count the number of successful transmissions of  state |000⟩

Qsuccess=Count[Table[Take[QECC[noisebox[0]],-3],{i,1,100}],{0,0,0}]

Whose error rate is now reduced to

Qerrorate=N[1-Qsuccess/100]

The reduced error should be on the order of (p^2)

prob^2