[PYTHON] Basics of Quantum Information Theory: Logical Operation by Toric Code (Brading)

\def\bra#1{\mathinner{\left\langle{#1}\right|}} \def\ket#1{\mathinner{\left|{#1}\right\rangle}} \def\braket#1#2{\mathinner{\left\langle{#1}\middle|#2\right\rangle}}

Introduction

Previous article constructs a high-performance error correction code (surface code) using qubits deployed on a grid mapped to a torus. I've seen that you can. However, it is very difficult to actually physically create such a torus, and the number of logical qubits that can be expressed is proportional to the number of holes in the torus, so a quantum computer with a large number of logical bits in this direction. Seems to be nearly impossible to create. Therefore, a toric code method has been proposed that allows logical operations to be performed by creating multiple defects of some sort on a mere plane that is not a torus, moving them, and wrapping them around (braiding) them like a braid. This time I will take up that.

Let me first say what I am trying to explain in the sections that follow. First, in the "theoretical explanation", what kind of defect can be created to express the logical bit, how the logical $ X $ operator and the logical $ Z $ operator can be defined, and by moving the defect (Braid). It shows that a logical 2-quantum bit operation (logical CNOT operation) can be realized. In "Operation check", use the quantum calculation simulator qlazy to confirm that the CNOT operation can be actually executed by braiding the defect created on the plane. ..

The following documents were used as references.

  1. Koshiba, Morimae, Fujii "Quantum Calculation Based on Observation" Corona Publishing Co., Ltd. (2017)
  2. K.Fujii,"Quantum Computation with Topological Codes - from qubit to topological fault-tolerance",arXiv:1504.01444v1 [quant-ph] 7 Apr 2015

Theoretical explanation

Face operator and vertex operator

Consider a grid as shown in the figure below. This time, we do not assume periodic boundary conditions like previous, so think of it as a grid defined on a plane.

fig1.png

On top of this, define the face operator and the vertex operator. The face operator is a tensor product of the $ Z $ operators on the four sides surrounding each face, and they are neatly arranged in the same shape. However, the vertex operator is a little different. The number of $ X $ operators for the vertex operators at the edge of the plane is not four, because each vertex at the edge of the plane connects to only three or two sides. Consider a group of stabilizers whose origin is the surface operator and vertex operator defined in this way. If the number of grids is $ M \ times N $, the number of face operators is $ MN $, and the number of vertex operators is $ (M + 1) (N + 1) = MN + M + N + 1 $. , The total number of generators is $ 2MN + M + N + 1 $. However, not all are independent. As you can see from the above figure, the product of all vertex operators is $ I $ (identity operator) [^ 1]. In other words, if you choose one vertex operator arbitrarily, it can be expressed as the product of all other vertex operators, so the number of independent generators is reduced by 1 from $ MN + M + N + 1 $. It becomes $ MN + M + N $. On the other hand, the number of qubits is $ (M + 1) N + M (N + 1) = 2MN + M + N $, which is the same as the number of independent generators. Therefore, this group of stabilizers uniquely determines the quantum state. This state is called "vacuum" here. In the following sections, we will explain in order that quantum operations can be performed by creating and moving defects using this vacuum as a starting point.

[^ 1]: If you put all the vertex operators side by side, you can see that all the products are identity operators because the $ X $ operator corresponding to the same qubit always appears twice. By the way, the product of all face operators is the tensor product of the $ Z $ operators in a loop surrounding the plane.

Creating a vacuum

First, how to create a vacuum state. Let us consider what kind of quantum operation should be performed on a group of qubits arranged in a grid to create such a stabilizer state (vacuum state). Generally stabilizer group

S = <g_1, g_2, g_3, \cdots , g_n>  \tag{1}

The stabilizer state specified in is obtained by measuring $ g_1, g_2, \ cdots, g_n $ in order under the initial state $ \ ket {00 \ cdots 0} $. As I explained in Previous article, let's review it briefly. Measuring $ g_1 $ under the state $ \ ket {00 \ cdots 0} $ is equivalent to having the projection operator $ (I \ pm g_1) / 2 $ act on the state. Here the code $ \ pm $ corresponds to whether the measured value was $ + 1 $ or $ -1 $. The state after measurement is

g_1 \frac{I \pm g_1}{2} \ket{00 \cdots 0} = \pm \frac{I \pm g_1}{2} \ket{00 \cdots 0}  \tag{2}

Therefore, it becomes the state on the eigenspace corresponding to the eigenvalue $ + 1 $ or $ -1 $ of $ g_1 $ (projected). Next, when $ g_2 $ is measured for this measured state, it is projected to the eigenspace corresponding to the eigenvalue $ + 1 $ or $ -1 $ of $ g_2 $ depending on the measured value. Furthermore, when measurements are made in the order of $ g_3, g_4, \ cdots, g_n $, the final projection is on the state (simultaneous eigenstate) on the product space of the eigenspaces of $ g_1, g_2, \ cdots, g_n $. Will be done. However, it is completely probabilistic which eigenspace corresponds to each $ g_i $. In Previous article, we added an operation to invert the eigenvalue if it is projected on the eigenspace of $ -1 $. .. Then, the stabilizer state corresponding to the stabilizer group of the equation (1) can be obtained neatly. However, if the procedure is broken, for example,

S = <-g_1, g_2, -g_3, \cdots , -g_n>  \tag{3}

You can get the stabilizer state corresponding to the stabilizer group like (this happens when the measured value of the 1st, 3rd, and nth generators is -1). In general

S = <\pm g_1, \pm g_2, \pm g_3, \cdots , \pm g_n>  \tag{4}

In, the stabilizer state corresponding to the stabilizer group in which the sign of $ g_i $ is $ + $ or - is obtained according to the measured value of each generator. This is not a problem at all, as it is only necessary to retain the syndrome measurement criteria for each generator in advance when making error corrections. In other words, if the stabilizer group of the measurement result is obtained as shown in Eq. (3), if the result of measuring the first generator $ g_1 $ is $ -1 $, it is a normal value and there is no error. You can judge, and conversely, if it is $ + 1 $, you can judge that there was an error, and so on.

When actually performing such a measurement to create a vacuum state, in addition to the qubits (data qubits) deployed on the sides of the lattice, auxiliary qubits for indirect measurement (symdrome measurement) are additionally deployed. It is good to do. The specific way to do this is simply to add auxiliary qubits to the center and vertices of each face of the grid as shown in the figure below. The auxiliary qubits deployed in the center of each surface are for indirect measurement (symdrome measurement) of the surface operator, and the auxiliary qubits deployed at each vertex are for indirect measurement (symdrome measurement) of the vertex operator. It is.

fig2.png

Before measuring the face and vertex operators, first initialize all data qubits and auxiliary qubits to $ \ ket {0} $. Then, indirectly measure the surface operator corresponding to each surface. The auxiliary qubit number corresponding to the surface to be measured is $ f_0 $, and the data qubit number on the side surrounding the surface is $ f_1, f_2, f_3, f_4 $.

f0 --H--*--*--*--*--H-- M
        |  |  |  |
f1 -----Z--|--|--|-----
f2 --------Z--|--|-----
f3 -----------Z--|-----
f4 --------------Z-----

Measure the surface operator with a quantum circuit such as. Do this on all fronts. Then measure the vertex operator for each vertex. Let the auxiliary qubit number corresponding to the vertex to be measured be $ v_0 $ and the data qubit number on the edge connected to that vertex be $ v_1, v_2, v_3, v_4 $.

v0 --H--*--*--*--*--H-- M
        |  |  |  |
v1 -----X--|--|--|-----
v2 --------X--|--|-----
v3 -----------X--|-----
v4 --------------X-----

Measure the vertex operator with a quantum circuit like. Do this for all vertices. The vacuum state is now created.

The vacuum state is one state. Not a code space. To make it a code space, it is necessary to remove at least one generator and consider a group of stabilizers with $ n-1 $ independent generators. Last time logicalizes the $ X $ or $ Z $ operators lined up on the loop that wraps around the hole by considering the periodic boundary condition = torus. Could be an operator. This time, we will define the logical operator by artificially creating a defect (hole) on the plane.

Defect vs. qubit generation

First, define a "defect". Defects are areas where no face or vertex operators are defined. The vacuum state explained earlier was a stabilizer state in which surface operators and vertex operators were densely spread over the entire grid. If you can create a region on this plane that does not have a face operator (or vertex operator), that is a defect. So how do you make this flaw? In fact, it can be easily created by measuring any of the qubits on the plane that constitutes the vacuum state.

Notice the two adjacent faces that make up the grid, as shown in the figure below. Consider measuring the $ X $ operator corresponding to the qubit above the two faces, which have one edge in common. In other words, the 4th qubit in the figure is measured on the $ X $ basis (or it is the same if you think that the 4th qubit is Hadamard gated and then measured on the $ Z $ basis).

fig3.png

Let's describe this measurement in stabilizer-style terms [^ 2]. There are only two generators that are counter-commutative with the operator $ X_4 $ that you want to measure: the surface operator $ Z_1 Z_3 Z_4 Z_6 \ equiv A_1 $ and the surface operator $ Z_2 Z_4 Z_5 Z_7 \ equiv A_2 $. All other face operators and vertex operators are commutative. In this case, one anti-commutative operator can be applied to the other anti-commutative operators to make one anti-commutative operator while keeping the stabilizers immutable. In other words, the stabilizer group

[^ 2]: Stabilizer type measurements will be mentioned many times in the discussions that follow. Please refer to the previous article "Basics of Quantum Information Theory: Quantum Error Correction (Stabilizer Code: 2)".

S = <A_1, A_2, \cdots>  \tag{5}

To

S = <A_1 A_2, A_2, \cdots>  \tag{6}

The only anti-commutative generator can be $ A_2 $, as in. After that, we measure $ X_4 $, which is the measurement target. If the measured value is $ + 1 $, replace $ A_2 $ with $ X_4 $, and if it is $ -1 $, replace $ A_2 $ with $-. Stabilizer group replaced with X_4 $

S = <A_1 A_2, \pm X_4, \cdots>  \tag{7}

The stabilizer state corresponding to is the state after measurement. You can see that the surface operators $ A_1 $ and $ A_2 $ that were in the original vacuum are gone and replaced by $ A_1 A_2 $ and $ \ pm X_4 $. In other words, it can be said that the measurement of $ X_4 $ generated two pairs of defects in the parts corresponding to $ A_1 $ and $ A_2 $. Also, this state is unique to $ X_4 $ because the generator contains $ \ pm X_4 $.

The measurement pair-produced defects, but equation (7) represents a unique state and is not codespace. For example, stabilizers excluding $ \ pm X_4 $,

S^{\prime} = <A_1 A_2, \cdots>  \tag{8}

Is the code space for describing one logical qubit.

So how can we define the logical $ X $ operator on this codespace? Bring $ X_4 $ again. This $ X_4 $ is commutative with all generators of $ S ^ {\ prime} $ and is not an element of $ S ^ {\ prime} $, so it acts as a logical operator. And

<A_1 A_2, X_4, \cdots>  \tag{9}

Corresponds to the eigenvalue $ + 1 $ of $ X_4 $

<A_1 A_2, -X_4, \cdots>  \tag{10}

Is the state corresponding to the eigenvalue $ -1 $ of $ X_4 $, so if $ X_4 $ is defined as a logical $ X $ operator, equation (9) is $ \ ket {+ ^ {L}} $, equation (10). ) Will represent $ \ ket {-^ {L}} $ ($ L $ with superscript shall represent a logical qubit; and so on).

On the other hand, for the logical $ Z $ operator, you can choose an operator that is commutative with the logical $ X $ operator $ X_4 $ and is commutative with all the generators of $ S ^ {\ prime} $. For example, $ A_1 $ meets that requirement and can be defined as the logical $ Z $ operator [^ 3]. Alternatively, $ A_2 $ can be the logical $ Z $ operator.

[^ 3]: Please note that $ A_1 $ or $ A_2 $ is no longer the generator of this stabilizer group.

The above is the simplest method of creating defect pairs (= method of creating code space). This type of defect is called a p-type defect because the defect is a lack of surface operators on the regular lattice. There is also another type of flaw. It is the lack of surface operators on dual lattices (that is, vertex operators in normal lattices). The method of creating it is explained below.

Pay attention to the two adjacent faces that make up the dual lattice as shown in the figure below (forget the qubit number earlier, and reassign the qubit number). In the original normal grid, they are adjacent cross characters. Consider measuring the $ Z $ operator corresponding to the qubit above these two crosses, which have one edge to share. That is, the fourth qubit in the figure is measured at the $ Z $ basis.

fig4.png

Let's describe this measurement in stabilizer-style terms. There are only two generators that are anti-convertible to the operator $ Z_4 $ that you want to measure: the vertex operator $ X_1 X_3 X_4 X_6 \ equiv B_1 $ and the vertex operator $ X_2 X_4 X_5 X_7 \ equiv B_2 $. All other vertex operators and all face operators are commutative. As in the previous discussion, the state after measurement is

S = <B_1 B_2, \pm Z_4, \cdots>  \tag{11}

And this is the unique state of $ \ pm Z_4 $. Exclude $ \ pm Z_4 $ here

S^{\prime} = <B_1 B_2, \cdots>  \tag{12}

Considering, this is a group of stabilizers that define the code space. So if you bring $ Z_4 $ again, it is commutative with all the generators of $ S ^ {\ prime} $, and it is not an element of $ S ^ {\ prime} $, so the role of the logical operator Play. And

<B_1 B_2, Z_4, \cdots>  \tag{13}

Corresponds to the eigenvalue $ + 1 $ of $ Z_4 $

<B_1 B_2, -Z_4, \cdots>  \tag{14}

Is the state corresponding to the eigenvalue $ -1 $ of $ Z_4 $, so if $ Z_4 $ is defined as the logical $ Z $ operator, equation (13) is $ \ ket {0 ^ {L}} $, equation (14). ) Will represent $ \ ket {1 ^ {L}} $.

On the other hand, for the logical $ X $ operator, you can choose an operator that is commutative with $ Z_4 $ and commutative with all generators of $ S $. For example, $ B_1 $ meets that requirement, so you can define it as a logical $ X $ operator. Alternatively, $ B_2 $ can be the logical $ X $ operator.

The above is the simplest method for creating defect pairs on a dual grid. This type of defect is called a d-type defect because the defect is a lack of surface operators on the dual lattice.

Defect vs. qubit movement

You can also expand the area of the defect by repeating the measurement. By measuring the qubits around the p-type defect at the $ X $ basis as shown in the figure below, the defect region expands (light blue region). The qubits indicated by black circles are the measured qubits.

fig5.png

You can also eliminate defects. First, enlarge the p-type defect in the horizontal direction as shown on the left in the figure below. On top of that, let's eliminate the middle of the three defects that are lined up. Since it is equivalent to reviving the surface operator corresponding to that surface, we can measure that surface operator. Actually, indirect measurement is performed using the auxiliary qubit corresponding to the surface (not shown in the figure below). Then, it will be as shown on the right in the figure below.

fig6.png

Is it true even if it is said that it will be? I'm going to hear that, so let's check this process in stabilizer format as before. The state on the left in the above figure can be obtained by extending the discussion when Eq. (7) was obtained.

S = <A_1 A_2 A_3, \pm X_5, \pm X_6, \cdots> \tag{15}

You can see that Here, the sign of $ \ pm $ is determined according to the measurement result of $ X_5 $ and the measurement result of $ X_6 $. Under this condition, measure $ A_2 $. $ A_2 $ is anti-commutative with $ X_5 $ and $ X_6 $, so first multiply either one to make one anti-commutative. For example

S = <A_1 A_2 A_3, \pm X_5 X_6, \pm X_6, \cdots> \tag{16}

To do. Then, the only generator that is anti-commutative with $ A_2 $ is $ \ pm X_6 $. On top of this, measure $ A_2 $. Depending on the measurement result

S = <A_1 A_2 A_3, \pm X_5 X_6, \pm A_2, \cdots> \tag{17}

The state changes like this. The stabilizer state does not change even if any generator is transferred to another generator. For example, try multiplying the third generator over the first generator. Then

S = <\pm A_1 A_3, \pm X_5 X_6, \pm A_2, \cdots> \tag{18}

It will be. This completes the state on the right in the above figure. In other words, you can move defects by combining expansion and disappearance.

Here, considering the code space defined by the stabilizer group excluding $ \ pm X_5 X_6 $, the chain of $ X_5 X_6 $ is used as the logical $ X $ operator as in the previous discussion, and the defect is $ A_1 $. We can see that we can define the loop around the as a logical $ Z $ operator.

By the way, what if you feel uncomfortable with the sign being $ + $ or - depending on the measured value? I will add a little. As a result of measuring $ X_6 $, the sign of $ X_6 $ in equation (15) becomes -. In that case, bring the logical $ Z $ operator $ A_3 $ (or $ A_1 A_2 $), which is an operator that is anti-commutative with $ X_6 $ and commutative with all other generators, and perform the entire operation. It's fine. On the other hand, if the sign of $ A_2 $ in Eq. (17) becomes - as a result of measuring $ A_2 $, the logical $ X $ operator before moving $ X_5 $ should be calculated as a whole. It's good [^ 4]. result,

[^ 4]: When checking the operation later, perform the defect pair movement simulation in this way.

S = <A_1 A_3, X_5 X_6, A_2, \cdots> \tag{19}

You can get a clean condition like this. This is the unique state corresponding to the eigenvalue $ + 1 $ of $ X_5 X_6 $. This process can run indefinitely and move defect pairs anywhere. However, no matter what the arrangement, the state determined by this is the unique state of the tensor product of the $ X $ operator on the chain connecting the two defects [^ 5]. And the tensor product of the $ X $ operators on this chain acts as the logical $ X $ operator, and the $ Z $ operator on the loop surrounding either of the two defects is the logical $ Z $ operator. It plays a role [^ 6].

[^ 5]: The same is true for any chain that connects defects. Previous article is a story similar to saying that the product of operators on a loop that can be continuously transformed represents the same effect.

[^ 6]: Considering the code space determined by the stabilizer group excluding $ X_5 X_6 $ from equation (19), the logical $ X $ and logical $ Z $ operators are as described in the text. Equation (19) represents the $ \ ket {+ ^ {L}} $ state because it is the eigenstate for the logical $ X $ eigenvalue $ + 1 $. When the logical $ Z $ is calculated in this state, the logical phase is inverted and changed to the $ \ ket {-^ {L}} $ state.

The above is the same for d-type defects, and the defects can be moved by repeating expansion and disappearance, and the tensor product of the $ Z $ operator on the chain connecting the defects becomes the eigenstate. And the tensor product of the $ Z $ operators on that chain acts as the logical $ Z $ operator, and the $ X $ operator on the loop (in the dual lattice) that surrounds either of the two defects is the logical $ X. Acts as a $ operator.

So far, for the sake of simplicity, we've mainly considered the flaws due to the lack of one face operator (or vertex operator), but as explained earlier, it can be expanded arbitrarily. For example, a large defect pair as shown in the figure below can be easily created by repeating the measurement.

fig7.png

If you make such a big defect, the number of Pauli operators that make up the logical operator will increase accordingly. In other words, the sign distance increases [^ 7]. The larger the sign distance, the more qubits can be corrected. Specifically, a code with a distance of $ 2t + 1 $ makes it possible to correct errors in the $ t $ qubit. By the way, in the case of the above figure, the sign distance is 16 (as you can see by counting), so the number of qubits that can be corrected is 7.

[^ 7]: The sign distance in the quantum error correction code is defined as the minimum number of Pauli operators that make up a logical operator. The implication is the minimum number of qubits that changed when performing some logical operation from any sign and moving it to another sign. The sign distance is defined this way because the number of changing qubits is equal to the number of Pauli operators included in the logical operators. By the way, the code distance in the classical linear code is the minimum value of the Hamming distance between logically different codes. In other words, in both quantum and classical terms, the sign distance can be thought of as an index that indicates how many bits should be changed in order to change from one sign to another.

2 qubit operations with Braiding

I will summarize the contents up to the previous section. If you create a p-type defect pair by measurement from a vacuum state, that state is the eigenstate of the tensor product of the Pauli $ X $ operator on the chain connecting the defects. Considering the generator excluding the chain, that is, the code space defined by the two (p-type) defects created in a vacuum, the tensor product of the Pauli $ X $ operator on the chain connecting the defects is this code. You can think of it as a logical $ X $ operator in space. Also, if so, the tensor product of the Pauli $ Z $ operator on the loop surrounding either defect becomes the logical $ Z $ operator in this codespace. On the other hand, if you create a d-type defect pair, its state is the tensor product of the Pauli $ Z $ operator on the chain connecting the defects. Considering the generator excluding the chain, that is, the code space defined by the two (d-type) defects created in a vacuum, the tensor product of the Pauli $ Z $ operator on the chain connecting the defects is this code. You can think of it as a logical $ Z $ operator in space. And if so, the tensor product of the Pauli $ X $ operator on the loop surrounding either defect is the logical $ X $ operator in this codespace. It was that.

Now, suppose that a pair of p-type defects and a pair of d-type defects exist on a plane apart as shown in the figure below. In the case of the p-type defect pair, instead of the two face operators disappearing, the product of the two missing face operators is newly added to the generator, so one logical qubit is now available. You can express it. Also, in the d-type defect pair, instead of the two vertex operators disappearing, the product of the two missing vertex operators is newly added to the generator, so this is one logical qubit. It means that the bits can be expressed. Therefore, two logical qubits can be represented by the defect shown in the figure below.

fig8.png

Here, the p-type defect pair is the 0th logical qubit, and the d-type defect pair is the 1st logical qubit. Suppose you want the 0th logical qubit to be the eigenstate of the logical $ X $ operator. That is, suppose you want to initialize the 0th qubit to $ \ ket {+ ^ {L}} $ or $ \ ket {-^ {L}} $. What should i do? If you remember the basics of the stabilizer format, you will understand it immediately. All you have to do is add the logical $ X $ operator, that is, the tensor product of the $ X $ operator placed on the chain connecting the defects, to the generator (see the figure below). This will create the state $ \ ket {+ ^ L} $. As explained in the previous section, if you pair-produce p-type defects and move either defect, either the state $ \ ket {+ ^ L} $ or $ \ ket {-^ L} $ will be natural. You can get it. If you get $ \ ket {-^ L} $ (or $ \ ket {+ ^ L} $), then the logical $ Z $ operator, that is, Pauli $ Z $ on the loop surrounding either defect. It can be changed to $ \ ket {+ ^ L} $ (or $ \ ket {-^ L} $) by letting the operator's tensor product act on this state.

If you want to initialize the first qubit to $ \ ket {0 ^ L} $ or $ \ ket {1 ^ L} $, you want to use the logical $ Z $ operator, that is, $ placed on the chain of defects. All you have to do is add the tensor product of the Z $ operator to the generator (see below). This will create the state $ \ ket {0 ^ L} $. As explained in the previous section, if you pair-produce d-type defects and move one of the defects, it will naturally be in this state $ \ ket {0 ^ L} $ or $ \ ket {1 ^ L} $. It can be obtained. If we get $ \ ket {1 ^ L} $ (or $ \ ket {0 ^ L} $), we get the logical $ X $ operator, that is, Pauli $ X $ on the loop surrounding either defect. It can be changed to $ \ ket {0 ^ L} $ (or $ \ ket {1 ^ L} $) by acting the operator's tensor product on this state.

fig9.png

Let us show that a logical CNOT operation can be performed using these two logical qubits. In stabilizer-style terms, what should be achieved is

CNOT: <XI,IZ> \rightarrow <XX,ZZ>   \tag{20}

It is a conversion. If expressed in a quantum circuit involving two logical qubits,

X --*-- X    I --*-- Z
    |            |
I --X-- X    Z --X-- Z

It would be good if could be realized.

Let me conclude first [^ 8]. Move one of the p-type defect pairs so that it wraps around one of the d-type defects. For example, the 0th logical qubit is set to the eigenstate (eigenvalue is 1) of the logical $ X $ operator as shown in the figure (1) below, and one of the p-type defects (the right side) as shown in the figure (2) below. ) Is moved around one of the d-type defects (to the right). Then the chain of $ X $ operators wraps around the d-type defect. Since the whole state does not change even if the $ X $ operator on the trivial loop is applied, the loop of the $ X $ operator (indicated by the orange line) as shown in (3) below is applied. After all, as shown in (4) below, the chain of $ X $ operators is divided into two parts. One is a chain that connects the original p-type defect pair, and the other is a loop that wraps around the d-type defect. Since this loop does not change its state even if it contracts continuously, it contracts to the boundary surrounding the d-type defect in (4) below. The $ X $ operator on this loop was the logical $ X $ operator of the first logical qubit, so the first logical qubit is in the eigenstate of the logical $ X $ operator (eigenvalue is 1). I am. This means that the logical initial state $ XI $ has changed to the logical final state $ XX $.

[^ 8]: I don't really understand the origin of the idea why it was thought that CNOT could be realized with this, so I sweat.

fig10.png fig11.png

The following shows that a similar move changes the logical initial state $ IZ $ to the logical final state $ ZZ $. As shown in the figure (1) below, set the first logical qubit to the eigenstate (eigenvalue is 1) of the logical $ Z $ operator, and make the p-type defect go around the d-type defect as before. Move to. When the p-type defect reaches the chain of the $ Z $ operator that connects the d-type defects and moves further, it moves while pushing the chain of the $ Z $ operator as shown in (2) below. It will be in shape [^ 9]. The state does not change even if the $ Z $ operator on the trivial loop is applied, so if you apply the loop of the $ Z $ operator (indicated by the orange line) as shown in (3) below, After all, the chain of $ Z $ operators is divided into two parts, as shown in (4) below. One is a chain that connects the original d-type defect pair, and the other is a loop that wraps around the p-type defect. Since the $ Z $ operator on this loop was the logical $ Z $ operator of the 0th logical qubit, the 0th logical qubit is in the eigenstate (eigenvalue is 1) of the logical $ Z $ operator. I am. Now we know that the logical initial state $ IZ $ has changed to the logical final state $ ZZ $.

[^ 9]: I won't explain in detail why this happens. You can see it by checking the measurements in the stabilizer format while manually calculating each one.

fig12.png fig13.png

In this way, Braiding allowed a CNOT operation on two logical qubits. The initial state is $ \ ket {+ ^ {L} 0 ^ {L}} $ and the CNOT operation is performed on it, so the final state is the logical Bell state $ (\ ket {0 ^ {L} 0 ^ {L }} + \ ket {1 ^ {L} 1 ^ {L}}) / \ sqrt {2} $. It is that.

Operation check

Then, using the quantum calculation simulator qlazy, the surface code is constructed by generating defects in one lattice plane, and the CNOT operation by Braiding is executed correctly. Let's see what we can do. I would like to execute the example shown above as it is. The initial state is $ \ ket {+ ^ {L} 0 ^ {L}} $ and the CNOT operation is performed on it, so the final state is the logical Bell state $ (\ ket {0 ^ {L} 0 ^ {L }} + \ ket {1 ^ {L} 1 ^ {L}}) / \ sqrt {2} $ should be. When measured based on the logical $ Z $, the state $ \ ket {0 ^ {L} 0 ^ {L}} $ or the state $ \ ket {1 ^ {L} 1 ^ {L}} $ has a 50-50 chance. It should be observed, so check it.

Implementation

Here is the entire Python code.

from collections import Counter
from qlazypy import Stabilizer

def get_common_qid(obj_A, obj_B):

    return list(set(obj_A['dat']) & set(obj_B['dat']))

def create_lattice(row, col):

    face = [[None]*col for _ in range(row)]
    vertex = [[None]*(col+1) for _ in range(row+1)]

    q_row = 2 * row + 1
    q_col = 2 * col + 1
    q_id = 0
    for i in range(q_row):
        for j in range(q_col):
            if i%2 == 1 and j%2 == 1: # face
                dat = []
                dat.append((i - 1) * q_col + j)  # up
                dat.append((i + 1) * q_col + j)  # down
                dat.append(i * q_col + (j - 1))  # left
                dat.append(i * q_col + (j + 1))  # right
                face[i//2][j//2] = {'anc': q_id, 'dat': dat}
            elif i%2 == 0 and j%2 == 0: # vertex
                dat = []
                if i > 0: dat.append((i - 1) * q_col + j)          # up
                if i < q_row - 1: dat.append((i + 1) * q_col + j)  # down
                if j > 0: dat.append(i * q_col + (j - 1))          # left
                if j < q_col - 1: dat.append(i * q_col + (j + 1))  # right
                vertex[i//2][j//2] = {'anc': q_id, 'dat': dat}
            q_id += 1
            
    return {'face': face, 'vertex': vertex}

def initialize(sb, lattice):

    sb.set_all('Z')
    for face_list in lattice['face']:
        for face in face_list:
            sb.h(face['anc'])
            [sb.cz(face['anc'], target) for target in face['dat']]
            sb.h(face['anc'])
            sb.m(qid=[face['anc']])

    for vertex_list in lattice['vertex']:
        for vertex in vertex_list:
            sb.h(vertex['anc'])
            [sb.cx(vertex['anc'], target) for target in vertex['dat']]
            sb.h(vertex['anc'])
            sb.m(qid=[vertex['anc']])

def create_move_defect_p(sb, pos_A, pos_B, path, lattice):

    # create defect pair
    face_A = lattice['face'][pos_A[0]][pos_A[1]]
    face_B = lattice['face'][pos_B[0]][pos_B[1]]
    q = get_common_qid(face_A, face_B)[0]
    md = sb.h(q).m(qid=[q])
    sb.h(q)
    if md.last == '1': [sb.z(i) for i in face_B['dat']]
 
    # move defect
    chain = [q]
    for i in range(1,len(path)):
        # extend defect
        face_A = lattice['face'][path[i-1][0]][path[i-1][1]]
        face_B = lattice['face'][path[i][0]][path[i][1]]
        q = get_common_qid(face_A, face_B)[0]
        md = sb.h(q).m(qid=[q])
        sb.h(q)
        if md.last == '1': [sb.z(i) for i in face_B['dat']]
                
        # remove defect
        sb.h(face_A['anc'])
        [sb.cz(face_A['anc'], target) for target in face_A['dat']]
        sb.h(face_A['anc'])
        md = sb.m(qid=[face_A['anc']])
        if md.last == '1': [sb.x(i) for i in chain]
            
        chain.append(q)

def create_move_defect_d(sb, pos_A, pos_B, path, lattice):

    # create defect pair
    vertex_A = lattice['vertex'][pos_A[0]][pos_A[1]]
    vertex_B = lattice['vertex'][pos_B[0]][pos_B[1]]
    q = get_common_qid(vertex_A, vertex_B)[0]
    md = sb.m(qid=[q])
    if md.last == '1': [sb.x(i) for i in vertex_B['dat']]
 
    # move defect
    chain = [q]
    for i in range(1,len(path)):
        # extend defect
        vertex_A = lattice['vertex'][path[i-1][0]][path[i-1][1]]
        vertex_B = lattice['vertex'][path[i][0]][path[i][1]]
        q = get_common_qid(vertex_A, vertex_B)[0]
        md = sb.m(qid=[q])
        if md.last == '1': [sb.x(i) for i in vertex_B['dat']]
                
        # remove defect
        sb.h(vertex_A['anc'])
        [sb.cx(vertex_A['anc'], target) for target in vertex_A['dat']]
        sb.h(vertex_A['anc'])
        md = sb.m(qid=[vertex_A['anc']])
        if md.last == '1': [sb.z(i) for i in chain]
            
        chain.append(q)

def get_chain(pos_list, lattice):

    chain = []
    for i in range(1,len(pos_list)):
        pos_A = pos_list[i-1]
        pos_B = pos_list[i]
        chain.append(get_common_qid(lattice['vertex'][pos_A[0]][pos_A[1]],
                                    lattice['vertex'][pos_B[0]][pos_B[1]])[0])
    return chain
    
def measure_logical_Z(sb, face, chain, shots=10):

    mval_list = []
    for _ in range(shots):
        sb_tmp = sb.clone()
        mval_0 = sb_tmp.m(qid=face['dat']).last
        mval_1 = sb_tmp.m(qid=chain).last
        mval = (str(sum([int(s) for s in list(mval_0)])%2)
                + str(sum([int(s) for s in list(mval_1)])%2))
        mval_list.append(mval)
        sb_tmp.free()
    return Counter(mval_list)

if __name__ == '__main__':

    lattice_row = 4
    lattice_col = 6
    lattice = create_lattice(lattice_row, lattice_col)

    # make vacuum state
    qubit_num = (2*lattice_row + 1) * (2*lattice_col + 1)
    sb = Stabilizer(qubit_num=qubit_num)
    initialize(sb, lattice)

    # logical qubit #1
    d_pos_A = [2,1]
    d_pos_B = [2,2]
    d_path = [[2,2],[2,3],[2,4]]
    create_move_defect_d(sb, d_pos_A, d_pos_B, d_path, lattice)

    # logical qubit #0
    p_pos_A = [0,0]
    p_pos_B = [0,1]
    # p_path = [[0,1],[0,2]]
    p_path = [[0,1],[0,2],[1,2],[2,2],[3,2],[3,3],[3,4],[3,5],
              [2,5],[1,5],[0,5],[0,4],[0,3],[0,2]]
    create_move_defect_p(sb, p_pos_A, p_pos_B, p_path, lattice)

    # measure logical qubits: #0 and #1
    face = lattice['face'][p_pos_A[0]][p_pos_A[1]]
    chain = get_chain([[2,1],[2,2],[2,3],[2,4]], lattice)
    freq = measure_logical_Z(sb, face, chain, shots=100)
    print(freq)

    sb.free()

I will explain what you are doing. Look at the main processing section.

lattice_row = 4
lattice_col = 6
lattice = create_lattice(lattice_row, lattice_col)

Creates grid information for arranging qubits. The lattice_row and lattice_col given as arguments are the number of vertical grids (faces) and the number of horizontal grids (faces), respectively. As you can see by looking at the contents of the function, the output data is dictionary data {'face': face,'vertex': vertex}. Here, face is a two-dimensional array (list of lists) arranged according to the position of the face, and its elements are {'anc': q0,'dat': [q1, q2, q3, q4]}. It is the dictionary data. anc is the number of the auxiliary qubit for the corresponding surface and dat is the number of the data qubit located at the boundary of that surface. Also, vertex is a two-dimensional array (list of lists) arranged according to the position of the vertices, and its elements are the same as face {'anc': q0,'dat': [q1, q2, q3, q4 ]} Dictionary data. anc is the number of the auxiliary qubit for the corresponding vertex and dat is the number of the data qubit located on the side connecting to that vertex. With this, we have defined the grid information that we are thinking about without omission [^ 10]. See the function definition for details.

[^ 10]: I think there are various ways to define the grid information as data, but as a result of thinking about the ease of later processing and devising it myself, it looks like this. It has become a little complicated in the wind.

next,

# make vacuum state
qubit_num = (2*lattice_row + 1) * (2*lattice_col + 1)
sb = Stabilizer(qubit_num=qubit_num)
initialize(sb, lattice)

Then, create a vacuum state. First, calculate the number of qubits and store it in qubit_num. The current assumption is 117 qubits. It is impossible to calculate a quantum state of this scale with a simulator. But don't worry. I wanted to do this spinning Braidinng, so I added a class Stabilizer to qlazy to perform stabilizer calculations (v.0.1.1). Basically, only Clifford operation can be performed, but calculation can be performed without limitation on the number of qubits (as long as memory allows). I will use this this time. Give the number of qubits to the argument of the Stabilizer constructor, create an instance and use it as the variable sb. In the initial state, all generators are identity operators, so initialize them appropriately to create a vacuum state. I'm doing that with the initialize function. In the first line of the function initialize

sb.set_all('Z')

However, this means that all qubits are set to the $ Z $ operator. That is, it means that all qubits are initialized to $ \ ket {0} $. A vacuum state is created by indirectly measuring the generators in order with this as the starting point. Specifically, do the following:

for face_list in lattice['face']:
    for face in face_list:
        sb.h(face['anc'])
        [sb.cz(face['anc'], target) for target in face['dat']]
        sb.h(face['anc'])
        sb.m(qid=[face['anc']])

Only the part that measures the surface operator is extracted, but the part that measures the vertex operator is also the same [^ 11].

[^ 11]: The device of the grid data defined earlier is alive here. The structure of the grid data may have been a little confusing, but I think the implementation using it is relatively easy to understand and simple, but how about it?

Once the vacuum is created, the next step is to create logical qubits by pair-producing and moving defects. For convenience of implementation, start from the 1st logical qubit instead of the 0th.

# logical qubit #1
d_pos_A = [2,1]
d_pos_B = [2,2]
d_path = [[2,2],[2,3],[2,4]]
create_move_defect_d(sb, d_pos_A, d_pos_B, d_path, lattice)

Here, d_pos_A and d_pos_B each represent the vertex coordinates of the d-type defect to be pair-produced first. I think it will be easier to understand if you also look at the figure used in the explanation earlier. The movement route information for moving the pair-generated right defect is d_path. Indicates that the defect moves in the order of vertex coordinates [2,2]-> [2,3]-> [2,4]. Create_move_defect_d is a function that actually changes the state by taking sb, d_pos_A, d_pos_B, and d_path as arguments. Let's take a look at the contents of the function.

# create defect pair
vertex_A = lattice['vertex'][pos_A[0]][pos_A[1]]
vertex_B = lattice['vertex'][pos_B[0]][pos_B[1]]
q = get_common_qid(vertex_A, vertex_B)[0]
md = sb.m(qid=[q])
if md.last == '1': [sb.x(i) for i in vertex_B['dat']]

Creates a pair of d-type defects. To identify the qubit to be measured at the $ Z $ basis, pair-produced vertex information is obtained from lattice and stored in vertex_A and vertex_B. Call the function get_common_qid to get the qubit numbers commonly contained in vertex_A and vertex_B (see function definition for details). The variable q is that number, so measure it on a $ Z $ basis. The measurement result is stored in the variable md and the last property becomes the measured value. If the measurement result is $ \ ket {0} $, nothing is done, and if it is $ \ ket {1} $, the logical $ X $ operator is operated to invert the eigenvalue. This means that the defects have been pair-produced, but since they are adjacent to each other, move one (to the right).

# move defect
chain = [q]
for i in range(1,len(path)):
    # extend defect
    vertex_A = lattice['vertex'][path[i-1][0]][path[i-1][1]]
    vertex_B = lattice['vertex'][path[i][0]][path[i][1]]
    q = get_common_qid(vertex_A, vertex_B)[0]
    md = sb.m(qid=[q])
    if md.last == '1': [sb.x(i) for i in vertex_B['dat']]
                
   # remove defect
   sb.h(vertex_A['anc'])
   [sb.cx(vertex_A['anc'], target) for target in vertex_A['dat']]
   sb.h(vertex_A['anc'])
   md = sb.m(qid=[vertex_A['anc']])
   if md.last == '1': [sb.z(i) for i in chain]
            
   chain.append(q)

The contents of the above for loop are divided into two parts. The first half is the part that expands the defect by one square according to the movement route information, and the second half is the part that eliminates the original defect. In the first half, the vertex corresponding to the original defect is vertex_A and the vertex corresponding to the expanding defect is vertex_B, and the qubit common to both is measured for $ Z $. As before, if the measurement result is $ \ ket {0} $, nothing is done, and if it is $ \ ket {1} $, the logical $ X $ operator is used to invert the eigenvalues. In the latter part to be extinguished, the vertex operator at the original defect position is restored, so the vertex operator is measured. In this case, indirect measurement using auxiliary qubits must be performed, so the CNOT operation targeting the four data qubits that make up the vertex operator is sandwiched by the Hadamard for the auxiliary qubits. Then, the auxiliary qubit is measured based on $ Z $, but if the measurement result is $ \ ket {0} $, nothing is done, and if it is $ \ ket {1} $, the eigenvalue is inverted. Operates the logical $ Z $ operator. Here, chain is a list of qubit numbers contained in the chain that the moving defect is carrying. Since the tensor product of the $ Z $ operator deployed on this qubit becomes the logical $ Z $ operator, the qubit number is appended each time it is moved, and the logical $ Z $ operation at that point is executed correctly. I will be able to do it. If you need to invert the eigenvalues, use the logical $ Z $ operator based on this chain. The first logical qubit is now in the state $ \ ket {0 ^ {L}} $.

Go back to the main processing section and then create the 0th logical qubit. In other words, p-type defects are pair-produced and moved.

# logical qubit #0
p_pos_A = [0,0]
p_pos_B = [0,1]
# p_path = [[0,1],[0,2]]
p_path = [[0,1],[0,2],[1,2],[2,2],[3,2],[3,3],[3,4],[3,5],
          [2,5],[1,5],[0,5],[0,4],[0,3],[0,2]]
create_move_defect_p(sb, p_pos_A, p_pos_B, p_path, lattice)

Where p_pos_A and p_pos_B represent the coordinates of the face that first pair-produces p-type defects. p_path represents the movement route information when moving one of the pair-produced defects (to the right). The commented out p_path is a short move, so the resulting logical state is the unique state $ \ ket {+ ^ {L}} $ of the logical $ X $ operator. On the other hand, the uncommented p_path is a short move, and (as you can see by comparing it with the previous figure) it is a move that goes around the d-type defect. So, if you move it for a long time, you should be able to realize the CNOT operation for the logical state. With these as arguments, execute the create_move_defect_p function to change the state. The contents of the function behave in the same way as the d type, so the explanation is omitted.

Finally, we measure the two logical qubits at the logical $ Z $ basis and display the result.

# measure logical qubits: #0 and #1
face = lattice['face'][p_pos_A[0]][p_pos_A[1]]
chain = get_chain([[2,1],[2,2],[2,3],[2,4]], lattice)
freq = measure_logical_Z(sb, face, chain, shots=100)
print(freq)

Since the 0th logical qubit is a p-type defect, the logical $ Z $ operator is the $ Z $ operator that encloses one of the defects. The variable face is for locating the $ Z $ operator. Also, since the first logical qubit is a d-type defect, the logical $ Z $ operator is the $ Z $ operator on the chain connecting the two defects. The variable chain is for storing the location of the $ Z $ operator. Get it with the get_chain function. The frequency data is acquired by giving the stabilizer sb, face, chain, and shots = 100, which represents the number of measurements, to the function measure_logical_Z as arguments. In the function, the measurement is repeated honestly based on face and chain, the result is counted up, frequency data (instance of Python standard Counter class) is created and returned. that's all.

result

Now, let's show the execution result. First, let's look at the case where the 0th logical qubit is not wrapped. Uncomment the route data that travels a short distance, which was commented out in the above code, and comment out the route data that travels a long distance. In other words

p_path = [[0,1],[0,2]]
# p_path = [[0,1],[0,2],[1,2],[2,2],[3,2],[3,3],[3,4],[3,5],
#          [2,5],[1,5],[0,5],[0,4],[0,3],[0,2]]

I will try to run it as. Then

Counter({'00': 53, '10': 47})

have become. The 0th logical qubit is $ \ ket {+ ^ {L}} $ and the 1st logical qubit is $ \ ket {0 ^ {L}} $. In other words

\ket{+^{L} 0^{L}} = \frac{1}{\sqrt{2}} (\ket{0^{L} 0^{L}} + \ket{1^{L} 0^{L}})   \tag{21}

So, it is correct that $ \ ket {0 ^ {L} 0 ^ {L}} $ and $ \ ket {1 ^ {L} 0 ^ {L}} $ are observed with 50% probability. is.

Let's consider this as the initial state, and then look at the case where the p-type defect goes around the d-type defect and returns to the initial state location. Let's reverse the previous comment and execute it. In other words

# p_path = [[0,1],[0,2]]
p_path = [[0,1],[0,2],[1,2],[2,2],[3,2],[3,3],[3,4],[3,5],
         [2,5],[1,5],[0,5],[0,4],[0,3],[0,2]]

When executed as

Counter({'11': 53, '00': 47})

The result is. This is just

\frac{1}{\sqrt{2}} (\ket{0^{L} 0^{L}} + \ket{1^{L} 1^{L}})   \tag{22}

It is equal to the result of measuring the Bell state. So, I was able to confirm that the CNOT operation was certainly realized. Congratulations, congratulations.

in conclusion

I wanted to somehow reproduce the interesting phenomenon that if I make a defect on a plane and wind it around another defect, it becomes a CNOT operation, so I started by implementing a stabilizer type calculation this time. When I tried it, it wasn't as easy as I expected (just because of my lack of knowledge), for example, how to calculate the number of independent generators of stabilizers, and one operator of stabilizers. I was a little worried about how to determine whether it was an element (although I ended up using the "sweep method" to calculate the rank of the check matrix). Also, regarding the movement of defects in the surface code, the textbook says "Measure", but it doesn't even say what to do when the eigenvalue becomes -1 (that is, "line spacing". "Read it"), but I made a trial and error while writing the program (I found out that I should do it as described in the text). So, it took me an unexpected amount of time to complete this article. However, I am glad that I learned that much.

Well, next time. I haven't decided yet, but now that I have realized the Clifford operation related to the surface code, I am thinking about how to realize the non-Clifford logical operation next.

that's all

Recommended Posts

Basics of Quantum Information Theory: Logical Operation by Toric Code (Brading)
Basics of Quantum Information Theory: Universal Quantum Calculation by Toric Code (1)
Basics of Quantum Information Theory: Topological Toric Code
Basics of Quantum Information Theory: Quantum Error Correction (Shor's Code)
Basics of Quantum Information Theory: Quantum Error Correction (CSS Code)
Basics of Quantum Information Theory: Quantum Error Correction (Stabilizer Code: 4)
Basics of Quantum Information Theory: Entropy (2)
Basics of Quantum Information Theory: Quantum Error Correction (Classical Linear Code)
Basics of Quantum Information Theory: Data Compression (1)
Basics of Quantum Information Theory: Horebaud Limits
Basics of Quantum Information Theory: Trace Distance
Basics of Quantum Information Theory: Quantum State Tomography
Basics of Quantum Information Theory: Data Compression (2)
Basics of Quantum Information Theory: Fault Tolerant Quantum Computation
Read "Basics of Quantum Annealing" Day 5
Notify LINE of train operation information
Example of code rewriting by ast.NodeTransformer