물리/양자정보

2024 양자정보경진대회 복기본 (임의 양자상태 생성)

hideh 2025. 1. 24. 22:30

$$\newcommand{\ket}[1]{\left|{#1}\right\rangle}\newcommand{\bra}[1]{\left\langle{#1}\right|}$$

2024 양자정보경진대회 복기본 (임의 양자상태 생성)

기간: 2024년 6월 21일 → 2024년 6월 23일

기본적으로 대회에서 구상할 때의 논리 순서대로 기술한 것이기에 각 과정에서 개연성이 부족할 수 있습니다. 여러가지 이유로 (당장 본인이 양자정보 잘 모르는 상태에서 대회 참여) 독자분이 필수적으로 있어야 한다고 생각되는 논의가 없거나 아예 틀린 내용이 있을 가능성이 높습니다. (예를 들면 상한만 구해놓고 하한은 안구한다던가...등등)

결과 : 대학원생 부문 2위 (근데 나빼고 다 학부생이었는데 대학원생부문이라니)

문제

풀이 과정

0.일반적인 \(N\) 에 대해서 어려운 이유

문제의 설명처럼, \(N=2^n\) 인 경우 구하는 상태의 파동함수는 다음과 같다.

$$
\ket{\psi_{2^r}} = \frac 1 {\sqrt {2^r}} \sum_{k=0}^{2^r-1} \ket{k}
$$

이 경우 우변이 \(\ket 0\) 의 Hadamard Transform, 즉 \(H_r \ket 0^{\otimes r}\) 이 되고 다음과 같이 정의된다.

$$
H_r = H_1 \otimes H_{r-1}
$$

여기서


$$
H_1 = \dfrac 1 {\sqrt 2} \begin{bmatrix} 1&1 \\
1&-1 \end{bmatrix}
$$

이므로 \(\ket 0^{\otimes r}\) 상태에 H gate \(r\) 개를 적용하면 구할 수 있다.

그러나 문제는 일반적인 경우를 요구하며, 이는 아래 식을 만족하는 operator를 찾고 구현하는 문제가 된다.

$$\frac 1 {\sqrt N} \sum_{k=0}^{N-1} \ket k = U \ket{0}^{\otimes r}, \quad 2^r \geq N
$$

이때 사용하는 conditioned gate들이 다른 큐비트 하나만을 체크한다면, 최소 \(\log_2 N\)개의 gate를 사용해야 하는데 이는 \(2^r\) 인 경우보다 항상 많다. 사용하는 gate수가 많아지면 연산 과정에서 발생하는 오류가 커지게 되어 원하는 상태를 만들기 어렵다.

1.개요

해당 식은 \(\ket{0}^{\otimes r}\) 에 적당한 rotation을 준 후 DFT를 적용한 것으로 해석할 수 있다. 그러나 DFT matrix는 아래와 같이 주어진다.
$$F_N = \begin{bmatrix} 1&1&1&\cdots &1\\
1&w&w^2&\cdots &w^{(N-1)}\\
1&w^2&w^4&\cdots&w^{2(N-1)}\\
\vdots&\vdots&\vdots&& \vdots \\
1&w^{2(N-1)}&w^{3(N-1)}&\cdots&w^{(N-1)^2}
\end{bmatrix}$$

모든 gate는 항상 \(2^n \times 2^n\) 행렬의 형태로 표현되어야 하나, 위의 행렬은 \(N \times N\) 행렬이므로 적당히 \( {\rm diag}( I_{2^r - N}, F_{N})\) 꼴의 연산자로 만들거나 \(F_{2^{r_k}}\) 꼴을 가지는 operator들의 tensorproduct 형태로 만들어주어야 한다.

우리는 원하는 연산자가 다음과 같은 형태를 가진다고 가정하고 주어진 \(N\) 에 해당하는 파라미터 \(m_k, \theta_k, p_k, q_k, n_k\) 를 구하려고 한다.

$$
U = R_{m_s}(\theta_{s}) {\rm diag}(I_{p_{s}} , F_{2^{n_s}} , I_{q_{s}} ) \cdots R_{m_1}(\theta_1){\rm diag}(I_{p_1} , F_{2^{n_1}} , I_{q_1} ) R_0(\theta_0)
$$

여기에서 \(n_k\)는 \(k\)번째로 적용되는 \(2^r\)-DFT 의 크기를 지정하며, \(p_k, q_k\) 는 각각 padding 을 위해 채워주는 단위행렬의 크기, \(m_k\) 는 \(k\)번째 \(R_Y\) gate가 작용하는 큐비트, \(\theta_k\)는 이때 회전되는 위상의 각도를 의미한다.

\(R_{m_k}\)가 한번에 여러 큐비트의 위상을 회전하는것은 상정하지 않았는데, 이는 \(n_k=0\) 으로 두면 표현될 수 있기 때문이다.

2.진폭 분배

아래와 같은 gate 조합이 주어졌다고 하자.

일반성을 잃지 않고 다음과 같이 가정할 수 있다.

$$
\ket{q_0} = \cos \theta_0 \ket 0 + \sin \theta_0 \ket 1\ket{q_1} = \ket 0
$$

이제 이 게이트를 통과한 다음의 상태를 구해보자. 먼저 \(\theta =-2 \cos^{-1}(\sin \theta_0)\) 로 두면 conditioned- \(R_Y\) gate를 통과한 상태는 다음과 같다.

$$
\ket{q_0q_1} = \cos \theta_0\ket{00} + \sin \theta_0( \sin \theta_0 \ket{10} + \cos \theta_0 \ket{11})
$$

이제 conditioned- \(H\) gate 까지 통과한 상태는 다음과 같다.

$$
\ket {q_0q_1} =\cos \theta_0 \ket{00} + \sin^2 \theta_0 \ket{10} +\frac 1 {\sqrt 2} \cos \theta_0 \sin \theta_0 \ket{11} + \frac 1 {\sqrt{2}} \cos \theta_0 \sin \theta_0 \ket{01}
$$

따라서 두 게이트를 모두 통과한 후, \(\ket {q_0}\)와 \(\ket{q_1}\) 가 \(1\)일 확률은 각각 다음과 같다.

$$
\begin{align*}
\ket {q_0}&:\sin^2 \theta_0 - \frac 1 2 \cos^2 \theta_0 \sin^2 \theta_0\ \ket{q_1}&:\frac 1 2 \cos^2 \theta_0 \sin^2 \theta_0
\end{align*}
$$

따라서 우리는 처음에 \(\ket{q_0}= R_Y(2\theta_0) \ket 0\)으로 두면 상태가 \(1\)일 확률을 \(\frac 1 2 \cos^2 \theta_0 \sin^2 \theta_0\) 만큼 \(\ket{q_1}\) 에 나누어줄 수 있다.

이때

$$
\tan\theta_1 = \dfrac{ \sin \theta_0 }{\sqrt{2+ 2\sin^2 \theta_0 \tan^2 \theta_0}}
$$

이다.

3.CNOT 게이트 개수

각 게이트의 구조는 다음과 같이 주어진다.

따라서 위의 조합으로 크기가 \(a+b\)인 진폭을 하나의 \(a\)와 \(p\)개의 \(b\)로 분배할때 \(2+p\)개의 cnot이 필요함을 알 수 있다. 또한 첫 번째 분배의 경우 초기화 과정에서 conditioned-\(R_Y\)를 생략할 수 있으므로 최종 cnot 개수에서 2를 빼줄 수 있다.

이제 \(n\)개의 동일한 상태를 만드는데 필요한 CNOT gate의 개수를 \(c_n\)이라 두면 다음이 성립한다.

$$
c_N \leq c_s + c_{N-s} + 3
$$

(이는 위의 예시에서 \(\theta_0 = \sin^{-1}\sqrt{s/N}\) 또는 \(\theta_0 = \cos^{-1}\sqrt{s/N}\) 로 두어 크기 \(1\)인 진폭을 \(s/N\)과 \((N-s)/N\)으로 나누어주는 상황과 같다.)

또한 \(2m\)의 경우 \(m\)에 대해 푼 후 하나의 비트를 추가하여 \(H\) gate를 씌우면 추가적인 cnot 없이 구현할 수 있으므로 \(c_{2m} = c_m\) 임을 알 수 있다. (따라서, \(0=c_1 = c_{2^k}\))

이제 최적의 분할 \(s : N-s\)를 찾아보자. 먼저 각각을 다음과 같이 이진법으로 나타내자.

$$
s = \sum_{i=1}^p 2^i u_i\
N - s = \sum_{j=1}^q 2^j v_j
$$

여기에서 \(u_i\)와 \(v_j\)는 이진법의 계수로 0 또는 1이다.

그러면 다음을 얻는다. 이 과정은 첫 번째 분할을 포함하지 않으므로 상한을 구할 때 \(2\)를 빼지 않는다.

$$
c_s \leq \sum_{i=1}^p c_{2^i u_i} + 3(p-1) \leq 4p -3 c_{N-s} \leq \sum_{i=1}^q c_{2^i v_i} + 3(q-1) \leq 4q -3
$$

따라서 대략적인 상한은 첫 번째 분할에서 사용하는 CNOT 1개를 포함해 다음과 같이 구해진다.

$$
c_N \leq (4p-3) + (4q -3)+1 = 4p+4q-5
$$

그런데 \(p \leq \lfloor \log_2 s \rfloor+1\) 이고, \(q \leq \lfloor \log_2 (N-s) \rfloor+1\) 이므로 다음을 얻는다.

$$
c_N \leq 4 (\lfloor \log_2(s)\rfloor +\lfloor \log_2(N-s) \rfloor )+3
$$

이 함수의 대략적인 개형은 아래와 같다.

따라서 \(N\)이 짝수가 아니면 \(s=1\) 또는 \(s=N-1\) 로 두는 것이 (\(\theta_0 =\cos^{-1}\sqrt{1/N}\) ) 최적일 것이라고 생각할 수 있다. 그런데 이 경우 분할 이후 각각 짝수개가 된다는 것을 알 수 있다.

다시 말해, 이진법으로 나타내었을 때 \(1\)로 표현되는 비트에 대해서만 '진폭 분할'을 해 주면 충분하다.

구체적인 방법 제시에 앞서 \(s=1\) 에 대한 CNOT gate 상한을 먼저 제시하겠다. 이진법을 이용하여 다음과 같이 표현하자.

$$
N = 2^{s_1} + 2^{s_2} + \cdots + 2^{s_r}
$$

그러면 다음과 같다.

$$
\begin{align}
c_N &= c_{2^{-s_1}N}\
&\leq c_{2^{-s_1}N-1 } + 1
\
&\leq c_{2^{-s_2(}2^{-s_1}N-1)-1 } + 1+3\
&\leq c_{2^{-s_{r-1} }(\cdots(2^{-s_1}N-1)\cdots )-1 } + 1+3(r-2)\
&=c_{2^{s_r}} + 1 + 3(r-2)\
&=3r-5
\end{align}
$$

4.구현

위의 방법을 종합하면 일반적인 \(N\)에 대해, 최대 \(3r-5\)개의 CNOT gate를 사용하는 알고리즘을 제시할 수 있다. 파이썬으로 작성한 코드는 아래와 같다. (IBM의 qiskit 사용)(다만 아래의 코드는 예외처리를 안해놔서 \(2^r\) 을 넣으면 제대로 동작하지 않음)

import numpy as np
from qiskit import QuantumCircuit

def uniform(M):
    n = int(np.ceil(np.log2(M)))

    q = QuantumCircuit(n)
    l = []
    for i in range(n):
        if int(bin(M)[-i-1]):
            l+=[i]
    k = len(l)
    for i in range(1,k):
        q.x(l[i] )
    Ms = [2**l[0]]
    for m in range(1,k):
        Ms+=[Ms[-1] + 2**l[m]]
    Ms+=[0]

    for i in range(l[0]):
        q.h(i)

    for m in range(k-1):
        theta = -2*np.arccos( np.sqrt( 2**l[m] / (M - Ms[m-1]))  )
        if m == 0:
            q.ry(theta , l[m+1])
        else:
            q.cry(theta , l[m], l[m+1] , ctrl_state = '0')
        for i in range(l[m], l[m+1] ):
            q.ch(l[m+1] , i , ctrl_state = '0')

    q.measure_all()
    display(q.draw("mpl"))
    return q

이를 이용해 제시된 \(N\)들에 대해 회로를 생성하고 실행하여 (이론적인) 진폭을 구하면 다음을 얻는다.

  • N=3


  • N=5


  • N=17


  • N=29


  • N=30


  • N=31


5.QFT를 이용한 방법

QFT(Quantum DFT) 는 다음과 같이 구현된다.

import numpy as np
from qiskit import QuantumCircuit

def qft(n , q):

    for i in range(n):
        q.h(i)
        for j in range(i+1, n ):
            theta =  2*np.pi / 2**(j+1)
            q.cp( theta , i , j, ctrl_state = '1')
    q.measure_all()
    display(q.draw("mpl"))
    return q

N=3 인 경우 QFT 회로 및 결과는 다음과 같다.


\(\ket{00}\) 과 \(\ket{01}\) 이 분리되지 않도록 \(q_1\)에 걸린 H gate를 conditioned로 바꾸어주면 된다.

\(N=5, 17\) 과 같이 \(N=2^r+1\) 인 경우 같은 방법으로 해결된다.

일반적인 경우에는, 결국 H gate 자체가 기저 2개에 대한 QFT로 해석할 수 있으므로, QFT 자체가 파동함수를 각 기저에 사영한 결과임을 이용할 수 있다. 원하지 않는 상태에 대해서는 사영된 결과가 0이 나오도록 QFT의 단계 사이에 \(R_Y\) gate를 삽입해 위상을 회전시켜 주면 문제를 해결할 수 있다.

이때, H gate가 가능한 상태의 경우의 수를 2개씩 늘려주므로 나누어주는 진폭의 비율은 2배씩 커진다. 따라서 \(R_Y\)의 각도 역시 이에 맞추어 조절해준다.

  • N=29 = 1 + 4 + 8 +16

      import numpy as np
      from qiskit import QuantumCircuit
    
      q29 = QuantumCircuit(5)
      q29.h(0)
      q29.ry(  2*np.arccos(-np.sqrt(1/29)) , 0   )
      q29.h(0)
    
      for i in range(1,5):
          if i < 3:
              q29.ch(0, i)
          elif i ==3 :
              q29.cry( 2*np.arccos(-np.sqrt(4/28)) , 0 , 3 )
              q29.ch(3 , 0 , ctrl_state = '1')
          elif i == 4:
              q29.cry( 2*np.arccos(-np.sqrt(8/24)) , 3 , 4 )
              q29.ch(4 , 3     , ctrl_state = '1')
          for j in range(i, 5 ):
              theta =  2*np.pi / 2**(j)
              q29.cp( theta , i-1 , j, ctrl_state = '1')
      q29.measure_all()
      display(q29.draw("mpl"))
      result = exe_qc(q29)

  • N= 30 = 2(1+2+4+8)

      import numpy as np
      from qiskit import QuantumCircuit
    
      q30 = QuantumCircuit(5)
      q30.h(0)
      q30.ry(  2*np.arccos(-np.sqrt(1/15)) , 0   )
    
      q30.h(0)
      for i in range(1,4):
          if i<2:
              q30.ch(0, i)
          elif i == 2:
              q30.cry( 2*np.arccos(-np.sqrt(2/14)) , 0 , 2 )
              q30.ch(2 , 0 , ctrl_state = '1')
          elif i == 3:
              q30.cry( 2*np.arccos(-np.sqrt(4/12)) , 2,3 )
              q30.ch( 3,2 , ctrl_state = '1')
          else:
              q30.h(i)
          for j in range(i, 4):
              theta =  2*np.pi / 2**(j)
              q30.cp( theta , i-1 , j, ctrl_state = '1')
    
      q30.h(4)
      q30.measure_all()
      display(q30.draw("mpl"))
      result = exe_qc(q30 , True , False)

  • N=31=1+2+4+8+16

      import numpy as np
      from qiskit import QuantumCircuit
    
      q31 = QuantumCircuit(5)
      q31.h(0)
      q31.ry(  2*np.arccos(-np.sqrt(1/31)) , 0   )
    
      q31.h(0)
      for i in range(1,5):
          if i<2:
              q31.ch(0, i)
          elif i == 2:
              q31.cry( 2*np.arccos(-np.sqrt(2/30)) , 0 , 2 )
              q31.ch(2 , 0 , ctrl_state = '1')
          elif i == 3:
              q31.cry( 2*np.arccos(-np.sqrt(4/28)) , 2,3 )
              q31.ch( 3,2 , ctrl_state = '1')
          elif i == 4:
              q31.cry( 2*np.arccos(-np.sqrt(8/24)) , 3,4 )
              q31.ch( 4,3 , ctrl_state = '1')
          else:
              q31.h(i)
          for j in range(i, 5):
              theta =  2*np.pi / 2**(j+1)
              q31.cp( theta , i-1 , j, ctrl_state = '1')
      q31.measure_all()
      display(q31.draw("mpl"))
      result = exe_qc(q31 , True , False)