In the article Last time, I understood the theory of stabilizer code, so this time I will take up one concrete example of stabilizer code and use the quantum calculation simulator qlazy to check its operation. At the end of Last time, I introduced the generators of "Shor code", "Steane code" and "5 qubit code". Of these, "Shor code" and "Steane code" have already been checked for operation in another article [^ 1], so here we will implement "5 qubit code" and check the operation.
First, the four generators and the logical Z operator are listed again.
Source | operator |
---|---|
Although not shown in the table, the logical X operator is $ X \ otimes X \ otimes X \ otimes X \ otimes X $.
I would like to design a quantum circuit based on this, but it requires some preparation.
To create the logical ground state $ \ ket {0_L} $ from $ \ ket {00000} $, change the operator $ g_ {i} ^ {\ prime} $ corresponding to the generator $ g_i $ to $ g_ {i I had to choose to be anti-commutative with $ and all other $ g_j (j \ ne i) $. I thought that I should create a check matrix [^ 2] for the above generator and create a check matrix for $ g_ {i} ^ {\ prime} $, and as a result of trial and error, I found the following. Is done [^ 3].
[^ 2]: See Previous article. [^ 3]: Maybe there is a general-purpose and smart procedure, but I'm happy with it.
operator | |
---|---|
As a noise set, prepare everything that operates Pauli group operators $ X, Z, XZ $ for each qubit as shown below.
noise | operator |
---|---|
We also need a list of measurements $ \ {\ beta_ {l} ^ {(i)} \} $ when the generator is measured with the noise $ E_i $ added.
g_l E_i = \beta_{l}^{(i)} E_i g_l \tag{1}
It can be calculated with. That is, if $ g_l $ and $ E_i $ are commutative, then $ \ beta_ {l} ^ {(i)} = + 1 $, and if they are anti-commutative, then $ \ beta_ {l} ^ {(i)} =- It's 1 $. Below is a list of measurements for each noise. In the second column, the measured value $ + 1 $ is listed as $ + $ and the measured value $ -1 $ is listed as
noise | Measured index | |
---|---|---|
Preparations are now complete. Now, the quantum circuit is shown below.
First, we show a circuit that obtains the logical ground state $ \ ket {0_L} $ from the physical ground state $ \ ket {00000} $.
|0> --H---*---H---M
|0> --H---|-------|-------*---H---M
|0> --H---|-------|-------|-------|-------*---H---M
|0> --H---|-------|-------|-------|-------|-------|-------*---H---M
|0> --H---|-------|-------|-------|-------|-------|-------|-------|-------*---H---M
| | | | | | | | | |
|0> ----| |---| |---| |---| |---| |---| |---| |---| |---| |---| |---
|0> ----| |---| |---| |---| |---| |---| |---| |---| |---| |---| |---
|0> ----|g1 |---|g1'|---|g2 |---|g2'|---|g3 |---|g3'|---|g4 |---|g4'|---|g5 |---|g5'|--- |0L>
|0> ----| |---| |---| |---| |---| |---| |---| |---| |---| |---| |---
|0> ----| |---| |---| |---| |---| |---| |---| |---| |---| |---| |---
Next, the unknown 1 quantum state $ \ ket {\ psi} $ is encoded using quantum teleportation techniques.
|psi> ----------*---H-----------M
| |
|0> --H---*---X---------M |
| | |
------X-------------X-----Z---
------X-------------X-----Z---
|0L> ------X-------------X-----Z--- |psi_L>
------X-------------X-----Z---
------X-------------X-----Z---
Now that the coded state $ \ ket {\ psi_L} $ is obtained, add noise. Here, Ei represents some kind of noise.
---| |---
---| |---
|psi_L> ---|Ei|--- |psi_L'>
---| |---
---| |---
Finally, if you perform a syndrome measurement for error detection and perform an inverse calculation of noise for recovery, the state will be restored.
|0> --H---*--------------------------H-----M
|0> --H---|-------*------------------H-----M
|0> --H---|-------|-------*----------H-----M
|0> --H---|-------|-------|------*---H-----M
| | | | |
----| |---| |---| |--| |-----| |----
----| |---| |---| |--| |-----| |----
|psi_L'> ----|g1 |---|g2 |---|g3 |--|g4 |-----|E+ |---- |psi>
----| |---| |---| |--| |-----| |----
----| |---| |---| |--| |-----| |----
That is all for the quantum circuit.
Let's show an implementation example by qlazy.
import numpy as np
from qlazypy import QState
def logic_x(self, qid):
[self.x(q) for q in qid]
return self
def logic_z(self, qid):
[self.z(q) for q in qid]
return self
def ctr_logic_x(self, q, qid):
[self.cx(q, qtar) for qtar in qid]
return self
def ctr_logic_z(self, q, qid):
[self.cz(q, qtar) for qtar in qid]
return self
def ctr_g1(self, q, qid):
self.cx(q, qid[0]).cz(q, qid[1]).cz(q, qid[2]).cx(q, qid[3])
return self
def ctr_g2(self, q, qid):
self.cx(q, qid[1]).cz(q, qid[2]).cz(q, qid[3]).cx(q, qid[4])
return self
def ctr_g3(self, q, qid):
self.cx(q, qid[0]).cx(q, qid[2]).cz(q, qid[3]).cz(q, qid[4])
return self
def ctr_g4(self, q, qid):
self.cz(q, qid[0]).cx(q, qid[1]).cx(q, qid[3]).cz(q, qid[4])
return self
def encode(self, phase, qid_anc, qid_cod):
# make logical zero state: |0>_L
# g1
self.h(qid_anc[0]).ctr_g1(qid_anc[0], qid_cod).h(qid_anc[0])
self.m(qid=[qid_anc[0]])
mval = self.m_value(binary=True)
if mval == '1': self.z(qid_cod[0]).z(qid_cod[2])
self.reset(qid=[qid_anc[0]])
# g2
self.h(qid_anc[1]).ctr_g2(qid_anc[1], qid_cod).h(qid_anc[1])
self.m(qid=[qid_anc[1]])
mval = self.m_value(binary=True)
if mval == '1': self.z(qid_cod[0]).z(qid_cod[1]).z(qid_cod[2]).z(qid_cod[3])
self.reset(qid=[qid_anc[1]])
# g3
self.h(qid_anc[2]).ctr_g3(qid_anc[2], qid_cod).h(qid_anc[2])
self.m(qid=[qid_anc[2]])
mval = self.m_value(binary=True)
if mval == '1': self.z(qid_cod[0]).z(qid_cod[1]).z(qid_cod[3]).z(qid_cod[4])
self.reset(qid=[qid_anc[2]])
# g4
self.h(qid_anc[3]).ctr_g4(qid_anc[3], qid_cod).h(qid_anc[3])
self.m(qid=[qid_anc[3]])
mval = self.m_value(binary=True)
if mval == '1': self.z(qid_cod[0]).z(qid_cod[2]).z(qid_cod[3])
self.reset(qid=[qid_anc[3]])
# logical z
self.h(qid_anc[4]).ctr_logic_z(qid_anc[4], qid_cod).h(qid_anc[4])
self.m(qid=[qid_anc[4]])
mval = self.m_value(binary=True)
if mval == '1':
self.x(qid_cod[0]).x(qid_cod[1]).x(qid_cod[2]).x(qid_cod[3]).x(qid_cod[4])
self.reset(qid=[qid_anc[4]])
# make random quantum state and encode (with quantum teleportation)
self.reset(qid=qid_anc)
self.u3(qid_anc[0], alpha=phase[0], beta=phase[1], gamma=phase[2]) # input quantum state
print("* input quantum state")
self.show(qid=[qid_anc[0]])
self.h(qid_anc[1])
self.ctr_logic_x(qid_anc[1], qid_cod).cx(qid_anc[0], qid_anc[1])
self.h(qid_anc[0])
self.m(qid=qid_anc[0:2])
mval = self.m_value(binary=True)
if mval == '00': pass
elif mval == '01': self.logic_x(qid_cod)
elif mval == '10': self.logic_z(qid_cod)
elif mval == '11': self.logic_x(qid_cod).logic_z(qid_cod)
self.reset(qid=qid_anc)
return self
def add_noise(self, q, qid, kind):
if kind == 'X': self.x(qid[q])
elif kind == 'Z': self.z(qid[q])
elif kind == 'XZ': self.z(qid[q]).x(qid[q])
return self
def correct_err(self, qid_anc, qid_cod):
self.reset(qid=qid_anc)
# syndrome
self.h(qid_anc[0]).ctr_g1(qid_anc[0], qid_cod).h(qid_anc[0])
self.h(qid_anc[1]).ctr_g2(qid_anc[1], qid_cod).h(qid_anc[1])
self.h(qid_anc[2]).ctr_g3(qid_anc[2], qid_cod).h(qid_anc[2])
self.h(qid_anc[3]).ctr_g4(qid_anc[3], qid_cod).h(qid_anc[3])
self.m(qid=qid_anc[0:4])
mval = self.m_value(binary=True)
print("* syndrome =", mval)
# recovery
if mval == '0000': pass
elif mval == '0001': self.x(qid_cod[0])
elif mval == '1010': self.z(qid_cod[0])
elif mval == '1011': self.z(qid_cod[0]).x(qid_cod[0])
elif mval == '1000': self.x(qid_cod[1])
elif mval == '0101': self.z(qid_cod[1])
elif mval == '1101': self.z(qid_cod[1]).x(qid_cod[1])
elif mval == '1100': self.x(qid_cod[2])
elif mval == '0010': self.z(qid_cod[2])
elif mval == '1110': self.z(qid_cod[2]).x(qid_cod[2])
elif mval == '0110': self.x(qid_cod[3])
elif mval == '1001': self.z(qid_cod[3])
elif mval == '1111': self.z(qid_cod[3]).x(qid_cod[3])
elif mval == '0011': self.x(qid_cod[4])
elif mval == '0100': self.z(qid_cod[4])
elif mval == '0111': self.z(qid_cod[4]).x(qid_cod[4])
return self
if __name__ == '__main__':
QState.add_methods(logic_x, logic_z, ctr_logic_x, ctr_logic_z,
ctr_g1, ctr_g2, ctr_g3, ctr_g4,
encode, add_noise, correct_err)
# create registers
qid_anc = QState.create_register(5)
qid_cod = QState.create_register(5)
qnum = QState.init_register(qid_anc, qid_cod)
# parameters for input quantum state (U3 gate params)
phase = [np.random.rand(), np.random.rand(), np.random.rand()]
# encode quantum state
qs_ini = QState(qnum)
qs_ini.encode(phase, qid_anc, qid_cod)
qs_fin = qs_ini.clone()
# noise
q = np.random.randint(0, len(qid_cod))
kind = np.random.choice(['X','Z','XZ'])
print("* noise '{:}' to #{:} qubit".format(kind, q))
qs_fin.add_noise(q, qid_cod, kind)
# error correction
qs_fin.correct_err(qid_anc, qid_cod)
# result
fid = qs_ini.fidelity(qs_fin, qid=qid_cod)
print("* fidelity = {:.6f}".format(fid))
QState.free_all(qs_ini, qs_fin)
I will explain what you are doing in order. Look at the main processing section.
QState.add_methods(logic_x, logic_z, ctr_logic_x, ctr_logic_z,
ctr_g1, ctr_g2, ctr_g3, ctr_g4,
encode, add_noise, correct_err)
Now, register the custom method shown above as a method of the QState class that represents the quantum state.
# create registers
qid_anc = QState.create_register(5)
qid_cod = QState.create_register(5)
qnum = QState.init_register(qid_anc, qid_cod)
Now, generate the register to be used from now on. This sets variable values such as qid_anc = [0,1,2,3,4], qid_cod = [5,6,7,8,9], qnum = 10 [^ 4]. qid_anc is 5 auxiliary qubits. In the quantum circuit shown above, 5 auxiliary qubits are required to create the logical ground state, 2 to create the code state in quantum teleportation, and 4 to create error correction. Five auxiliary qubits are sufficient as the state can be reset just before each step. qid_cod is a qubit for describing the sign state. Since it is a 5 qubit code, it is sufficient to prepare 5 qubits.
[^ 4]: Of course, you can set the variable value directly without using such a class method. When dealing with complicated quantum circuits, I think this is easier to implement. The current example is not so complicated, so it doesn't matter which one.
# parameters for input quantum state (U3 gate params)
phase = [np.random.rand(), np.random.rand(), np.random.rand()]
So, 3 random parameter phases are generated to create the code state appropriately (3 parameters for U3 gate operation).
# encode quantum state
qs_ini = QState(qnum)
qs_ini.encode(phase, qid_anc, qid_cod)
qs_fin = qs_ini.clone()
So, first create the initial state including the auxiliary qubit, and then convert it to the coded state with the encode method. I'm copying qs_ini to qs_fin, which is ultimately to evaluate if the original state and the error-corrected state match.
Now let's take a look at the contents of the encode method.
# g1
self.h(qid_anc[0]).ctr_g1(qid_anc[0], qid_cod).h(qid_anc[0])
self.m(qid=[qid_anc[0]])
mval = self.m_value(binary=True)
if mval == '1': self.z(qid_cod[0]).z(qid_cod[2])
self.reset(qid=[qid_anc[0]])
...
Creates the logical ground state $ \ ket {0_L} $. Perform the operations shown in the quantum circuit above in order. The measured value (index) of the indirect measurement is in the mval variable. If this is "0", do nothing, if it is "1", calculate $ g_ {1} ^ {\ prime} = ZIZII $. When finished, reset the 0th auxiliary qubit used. Hereafter, the same thing is done for all other generators and the logical Z operator.
self.u3(qid_anc[0], alpha=phase[0], beta=phase[1], gamma=phase[2]) # input quantum state
print("* input quantum state")
self.show(qid=[qid_anc[0]])
Then, a random 1-qubit state is created by operating the U3 gate with phase as an argument to the 0th auxiliary qubit. Then, the status is displayed.
self.h(qid_anc[1])
self.ctr_logic_x(qid_anc[1], qid_cod).cx(qid_anc[0], qid_anc[1])
self.h(qid_anc[0])
self.m(qid=qid_anc[0:2])
mval = self.m_value(binary=True)
if mval == '00': pass
elif mval == '01': self.logic_x(qid_cod)
elif mval == '10': self.logic_z(qid_cod)
elif mval == '11': self.logic_x(qid_cod).logic_z(qid_cod)
self.reset(qid=qid_anc)
Now run the quantum teleportation circuit. If you execute the do nothing / logical X / logical Z / logical XZ operator according to the 4 patterns, mval contains the result of executing 2 measurements, and 5 from the 1 qubit state created earlier You can get the sign state of the qubit.
Return to the main processing section.
# noise
q = np.random.randint(0, len(qid_cod))
kind = np.random.choice(['X','Z','XZ'])
print("* noise '{:}' to #{:} qubit".format(kind, q))
qs_fin.add_noise(q, qid_cod, kind)
Randomly adds noise to the sign state of 5 qubits. Randomly select the qubit and randomly select the noise type from X / Z / XZ. The actual processing is done by the add_noise method. See the function definition for details.
# error correction
qs_fin.correct_err(qid_anc, qid_cod)
Then, perform error correction.
Let's take a look at the contents of the correct_err method.
self.reset(qid=qid_anc)
So, first reset all the auxiliary qubits.
# syndrome
self.h(qid_anc[0]).ctr_g1(qid_anc[0], qid_cod).h(qid_anc[0])
self.h(qid_anc[1]).ctr_g2(qid_anc[1], qid_cod).h(qid_anc[1])
self.h(qid_anc[2]).ctr_g3(qid_anc[2], qid_cod).h(qid_anc[2])
self.h(qid_anc[3]).ctr_g4(qid_anc[3], qid_cod).h(qid_anc[3])
self.m(qid=qid_anc[0:4])
mval = self.m_value(binary=True)
print("* syndrome =", mval)
Then, error detection measurement (syndrome measurement) is performed. The measured value (4-digit binary string) goes into the variable mval. Depending on this binary string,
# recovery
if mval == '0000': pass
elif mval == '0001': self.x(qid_cod[0])
elif mval == '1010': self.z(qid_cod[0])
elif mval == '1011': self.z(qid_cod[0]).x(qid_cod[0])
elif mval == '1000': self.x(qid_cod[1])
elif mval == '0101': self.z(qid_cod[1])
elif mval == '1101': self.z(qid_cod[1]).x(qid_cod[1])
elif mval == '1100': self.x(qid_cod[2])
elif mval == '0010': self.z(qid_cod[2])
elif mval == '1110': self.z(qid_cod[2]).x(qid_cod[2])
elif mval == '0110': self.x(qid_cod[3])
elif mval == '1001': self.z(qid_cod[3])
elif mval == '1111': self.z(qid_cod[3]).x(qid_cod[3])
elif mval == '0011': self.x(qid_cod[4])
elif mval == '0100': self.z(qid_cod[4])
elif mval == '0111': self.z(qid_cod[4]).x(qid_cod[4])
Uses to perform the inverse calculation of noise. The coded state should now be restored.
Return to the main processing section.
# result
fid = qs_ini.fidelity(qs_fin, qid=qid_cod)
print("* fidelity = {:.6f}".format(fid))
Then, calculate and display the fidelity to see the difference between the original quantum state and the quantum state after error correction [^ 5]. If the error correction is successful, the fid should be 1.0.
[^ 5]: The fidelity method argument qid calculates the fidelity for the subsystem represented by a particular qubit number list.
Now, let's run the above program.
* input quantum state
c[0] = +0.3382-0.0000*i : 0.1143 |++
c[1] = -0.6657+0.6652*i : 0.8857 |++++++++++
* noise 'Z' to #4 qubit
* syndrome = 0100
* fidelity = 1.000000
An appropriately set 1 quantum state was encoded, and noise'Z'was added to the 4th qubit, but the error pattern was '0100' ($ E_ {14} = IIIIZ $) by syndrome measurement. As a result of performing the inverse calculation, the fidelity became 1.000000. So the error correction was successful.
If you run it again,
* input quantum state
c[0] = +0.7701-0.0000*i : 0.5931 |+++++++
c[1] = -0.1945+0.6075*i : 0.4069 |+++++
* noise 'X' to #1 qubit
* syndrome = 1000
* fidelity = 1.000000
Then, a different pattern of noise was added to the state different from the previous one, but the error correction was successful in this case as well. I tried it many times and it didn't fail. So, congratulations, congratulations.
This completes the "stabilizer code" series that has been continued four times. There are some interesting unresolved issues, such as the standard form of stabilizer codes and the method of creating logical ground states only by unitary operations, but I would like to move on. Next time, we are planning "fault-tolerant quantum computation".
that's all
Recommended Posts