참고
이 페이지는 tutorials/circuits/3_summary_of_quantum_operations.ipynb 에서 생성되었다.
IBM 퀀텀 랩 에서 대화식으로 실행하시오.
양자 연산 요약¶
본 섹션에서는 Qiskit Terra에서 이용 가능한 여러 가지 연산들에 대하여 다룬다. 목록은 다음과 같다:
단일 큐비트 양자 게이트
다중 큐비트 양자 게이트
측정
초기화 (Reset)
조건
상태 초기화
또한 세 가지 종류의 시뮬레이터를 사용하는 방법도 설명한다.
unitary_simulator
qasm_simulator
statevector_simulator
[1]:
# Useful additional packages
import matplotlib.pyplot as plt
%matplotlib inline
import numpy as np
from math import pi
[2]:
from qiskit import QuantumCircuit, ClassicalRegister, QuantumRegister, execute
from qiskit.tools.visualization import circuit_drawer
from qiskit.quantum_info import state_fidelity
from qiskit import BasicAer
backend = BasicAer.get_backend('unitary_simulator')
단일 큐비트 양자 상태¶
단일 큐비트 양자 상태는 다음과 같이 기술할 수 있다
여기서 \(\alpha\) 및 \(\beta\) 는 복소수이다. 측정에서 큐비트가 \(\left|1\right\rangle\) 에있을 확률은 \(|\alpha|^2\) 이고 \(\left|1\right\rangle\) 은 \(|\beta|^ 2\). 벡터로서 이것은
확률의 보존으로 인해 \(|\alpha|^2+ |\beta|^2 = 1\) 이며 전역 위상이 감지되지 않기 때문에 \(\left|\psi\right\rangle := e^{i\delta} \left|\psi\right\rangle\) 단일 큐비트 양자 상태를 설명하기 위해 두 개의 실수 만 필요하다.
\(0\leq\phi<2\pi\) 과 \(0\leq\theta\leq\pi\) 때 다음과 같이 나타낼수 있다.
여기서 \(0\leq\phi<2\pi\) 및 \(0\leq\theta\leq\pi\) 이다. 물론 큐비트 상태 (\(\mathbb{C}^2\))와 단위 구 표면의 점 (\(\mathbb{R}^3\))은 일대일로 대응된다. 이를 큐비트 상태의 블로흐 구체 표현이라고 한다.
양자 게이트/연산은 일반적으로 행렬로 표현한다. 특히 큐비트에 작용하는 게이트는 \(2\times2\) 유니테리 행렬 \(U\) 로 표현 된다. 그리고 양자 게이트의 작용은 양자 상태를 나타내는 벡터에 게이트를 나타내는 행렬을 곱함으로써 알 수 있다.
일반적으로 유니테리 상태에서는 \(\left|0\right\rangle\) 을 위의 상태로 만들 수 있어야 한다. 따라서 \(U\) 는 다음과 같다.
여기서 임의로 주어진 \(0\leq\theta\leq\pi\) 와 \(0\leq\phi<2\pi\) 에 대해 \(a\) 와 \(b\) 를 만족하는 복소수이다. 이는 세가지 제약 조건을 제공하며 이를 만족하는 \(a\) 와 \(b\) 로 표현할 수 있다. 따라서
이는 단일 큐비트에 작용되는 유니테리의 가장 일반적인 형태이다.
단일 큐비트 게이트¶
사용할 수 있는 단일 큐비트 게이트로는 다음이 있다. -u 게이트 - 항등 (Identity) 게이트 - 파울리 (Pauli) 게이트 - 클리포드 (Clifford) 게이트- \(C3\) 게이트 - 표준 회전 (Standard rotation) 게이트
우리는 이러한 유니테리 행렬 계산이 가능하도록 해 주는 백엔드: unitary_simulator
를 제공한다.
[3]:
q = QuantumRegister(1)
U 게이트¶
Qiskit 에서는 일반적인 유니테리를 사용할 수 있도록 :math:’u3’ 게이트를 제공한다.
[4]:
qc = QuantumCircuit(q)
qc.u3(pi/2,pi/2,pi/2,q)
qc.draw()
[4]:
┌────────────────────┐ q0_0: ┤ U3(pi/2,pi/2,pi/2) ├ └────────────────────┘
[5]:
job = execute(qc, backend)
job.result().get_unitary(qc, decimals=3)
[5]:
array([[ 0.707+0.j , -0. -0.707j],
[ 0. +0.707j, -0.707+0.j ]])
\(u2(\phi,\lambda)=u3(\pi/2,\phi,\lambda)\) 게이트는 다음과 같은 행렬로 표현된다.
이것은 우리가 중첩을 만들 수 있게 해주는 유용한 게이트이다.
[6]:
qc = QuantumCircuit(q)
qc.u2(pi/2,pi/2,q)
qc.draw()
[6]:
┌───────────────┐ q0_0: ┤ U2(pi/2,pi/2) ├ └───────────────┘
[7]:
job = execute(qc, backend)
job.result().get_unitary(qc, decimals=3)
[7]:
array([[ 0.707+0.j , -0. -0.707j],
[ 0. +0.707j, -0.707+0.j ]])
게이트 \(u1(\lambda)= u3(0, 0, \lambda)\) 는 다음과 같은 행렬로 표현된다.
이는 양자 위상을 만들어 주므로 유용하다.
[8]:
qc = QuantumCircuit(q)
qc.u1(pi/2,q)
qc.draw()
[8]:
┌──────────┐ q0_0: ┤ U1(pi/2) ├ └──────────┘
[9]:
job = execute(qc, backend)
job.result().get_unitary(qc, decimals=3)
[9]:
array([[1.+0.j, 0.+0.j],
[0.+0.j, 0.+1.j]])
항등(identity) 게이트¶
항등 게이트는 \(Id = u0(1)\).
[13]:
qc = QuantumCircuit(q)
qc.id(q)
qc.draw()
[13]:
┌───┐ q0_0: ┤ I ├ └───┘
[14]:
job = execute(qc, backend)
job.result().get_unitary(qc, decimals=3)
[14]:
array([[1.+0.j, 0.+0.j],
[0.+0.j, 1.+0.j]])
파울리(Pauli) 게이트¶
\(X\): 비트 플립 게이트¶
비트 플립 게이트 \(X\) 는 다음과 같이 정의된다.
[15]:
qc = QuantumCircuit(q)
qc.x(q)
qc.draw()
[15]:
┌───┐ q0_0: ┤ X ├ └───┘
[16]:
job = execute(qc, backend)
job.result().get_unitary(qc, decimals=3)
[16]:
array([[0.+0.j, 1.-0.j],
[1.+0.j, 0.+0.j]])
\(Y\): 비트와 위상 플립 게이트¶
\(Y\) 게이트는 다음과 같이 정의된다.
[17]:
qc = QuantumCircuit(q)
qc.y(q)
qc.draw()
[17]:
┌───┐ q0_0: ┤ Y ├ └───┘
[18]:
job = execute(qc, backend)
job.result().get_unitary(qc, decimals=3)
[18]:
array([[ 0.+0.j, -0.-1.j],
[ 0.+1.j, 0.+0.j]])
\(Z\): 위상 플립 게이트¶
위상 플립 게이트 \(Z\) 는 다음과 같이 정의된다.
[19]:
qc = QuantumCircuit(q)
qc.z(q)
qc.draw()
[19]:
┌───┐ q0_0: ┤ Z ├ └───┘
[20]:
job = execute(qc, backend)
job.result().get_unitary(qc, decimals=3)
[20]:
array([[ 1.+0.j, 0.+0.j],
[ 0.+0.j, -1.+0.j]])
클리포드 (Clifford) 게이트¶
하다마드(Hadamard) 게이트¶
[21]:
qc = QuantumCircuit(q)
qc.h(q)
qc.draw()
[21]:
┌───┐ q0_0: ┤ H ├ └───┘
[22]:
job = execute(qc, backend)
job.result().get_unitary(qc, decimals=3)
[22]:
array([[ 0.707+0.j, 0.707-0.j],
[ 0.707+0.j, -0.707+0.j]])
\(S\) (or, \(\sqrt{Z}\) 위상) 게이트¶
[23]:
qc = QuantumCircuit(q)
qc.s(q)
qc.draw()
[23]:
┌───┐ q0_0: ┤ S ├ └───┘
[24]:
job = execute(qc, backend)
job.result().get_unitary(qc, decimals=3)
[24]:
array([[1.+0.j, 0.+0.j],
[0.+0.j, 0.+1.j]])
\(S^{\dagger}\) (또는 \(\sqrt{Z}\) 위상의 켤레) 게이트¶
[25]:
qc = QuantumCircuit(q)
qc.sdg(q)
qc.draw()
[25]:
┌─────┐ q0_0: ┤ SDG ├ └─────┘
[26]:
job = execute(qc, backend)
job.result().get_unitary(qc, decimals=3)
[26]:
array([[1.+0.j, 0.+0.j],
[0.+0.j, 0.-1.j]])
\(C3\) 게이트¶
\(T\) (혹은, \(\sqrt{S}\) 위상) 게이트¶
[27]:
qc = QuantumCircuit(q)
qc.t(q)
qc.draw()
[27]:
┌───┐ q0_0: ┤ T ├ └───┘
[28]:
job = execute(qc, backend)
job.result().get_unitary(qc, decimals=3)
[28]:
array([[1. +0.j , 0. +0.j ],
[0. +0.j , 0.707+0.707j]])
\(T^{\dagger}\) (혹은, \(\sqrt{S}\) 위상의 켤레) 게이트¶
[29]:
qc = QuantumCircuit(q)
qc.tdg(q)
qc.draw()
[29]:
┌─────┐ q0_0: ┤ TDG ├ └─────┘
[30]:
job = execute(qc, backend)
job.result().get_unitary(qc, decimals=3)
[30]:
array([[1. +0.j , 0. +0.j ],
[0. +0.j , 0.707-0.707j]])
표준 회전¶
표준 회전 게이트들은 파울리 \(P=\{X,Y,Z\}\) 에 대응하는 블로흐 벡터 축을 기준으로한 회전을 정의하는 게이트다. 다음과 같이 정의되는데,
X축에 관한 회전¶
[31]:
qc = QuantumCircuit(q)
qc.rx(pi/2,q)
qc.draw()
[31]:
┌──────────┐ q0_0: ┤ RX(pi/2) ├ └──────────┘
[32]:
job = execute(qc, backend)
job.result().get_unitary(qc, decimals=3)
[32]:
array([[ 0.707+0.j , -0. -0.707j],
[ 0. -0.707j, 0.707+0.j ]])
Y축에 관한 회전¶
[33]:
qc = QuantumCircuit(q)
qc.ry(pi/2,q)
qc.draw()
[33]:
┌──────────┐ q0_0: ┤ RY(pi/2) ├ └──────────┘
[34]:
job = execute(qc, backend)
job.result().get_unitary(qc, decimals=3)
[34]:
array([[ 0.707+0.j, -0.707+0.j],
[ 0.707+0.j, 0.707+0.j]])
Z축에 관한 회전¶
여기서 전역 위상만 \(e^{-i \phi/2}\) 만큼 차이난다는 의미에서 동치를 사용하였다.
[35]:
qc = QuantumCircuit(q)
qc.rz(pi/2,q)
qc.draw()
[35]:
┌──────────┐ q0_0: ┤ RZ(pi/2) ├ └──────────┘
[36]:
job = execute(qc, backend)
job.result().get_unitary(qc, decimals=3)
[36]:
array([[1.+0.j, 0.+0.j],
[0.+0.j, 0.+1.j]])
이는 전역 위상 (Global Phase) 값만 다를 뿐이라는 사실을 주목하자.
다중 큐비트 게이트¶
수학적 예비¶
양자 컴퓨터를 기술하는 공간의 차원은 큐비트의 개수가 증가함에 따라 지수적으로 증가한다. \(n\) 큐비트를 기술하는 복소 벡터 공간은 \(d=2^n\) 차원을 가지기 때문이다. 다중 큐비트 시스템의 상태를 표현할 때는 텐서 곱을 사용하여 연산자들이나 기저 벡터들 사이를 “붙여줄” 수 있다.
우선 두 개의 큐비트들로 구성된 시스템을 생각해 보자. 각 큐비트에 작용하는 두 개의 연산자 \(A\) 와 \(B\) 가 주어졌을 때 두 개의 큐비트에 동시에 작용하는 연합 연산자 \(A \otimes B\) 은 다음과 같다.
여기서 \(A_{jk}\) 와 \(B_{lm}\) 는 각각 \(A\) 와 \(B\) 의 행렬 성분이다.
유사하게 두 큐비트 시스템의 기저 벡터들은 단일 큐비트의 기저 벡터들을 텐서 곱하여 형성된다.
Note we’ve introduced a shorthand for the tensor product of basis vectors, wherein \(\left|0\right\rangle \otimes \left|0\right\rangle\) is written as \(\left|00\right\rangle\). The state of an \(n\)-qubit system can be described using the \(n\)-fold tensor product of single-qubit basis vectors. Notice that the basis vectors for a 2-qubit system are 4-dimensional; in general, the basis vectors of an \(n\)-qubit system are \(2^{n}\)-dimensional, as noted earlier.
키스킷에 사용되는 기저 벡터의 순서¶
물리학계에서는 다중 큐비트 시스템의 큐비트들을 나열할때 대부분 첫번째 큐비트를 텐서 곱의 가장 왼쪽에 놓고 마지막 큐비트를 가장 오른쪽에 놓는 순서를 따른다. 예를 들어, 첫번째 큐비트의 상태가 \(\left|0\right\rangle\) 이고 두번째 큐비트의 상태가 \(\left|1\right\rangle\) 라면 그들의 전체 상태는 \(\left|01\right\rangle\) 이 된다. Qiskit은 조금 다른 순서로 큐비트를 나열하는데 가장 중요한 비트 (MSB)가 좌측에 있고 가장 덜 중요한 비트 (LSB)가 우측에 놓이는 big-endian 방식을 따른다. 이 것은 고전 컴퓨터에서 사용되는 비트열 표현과 비슷한데, 이는 측정을 한 후에 문자열을 정수로 변환하는 과정을 쉽게 만든다. 앞선 예의 경우 전체 상태는 \(\left|10\right\rangle\) 로 표현된다. 중요한 점은 이러한 다중 큐비트 상태의 표기법의 차이가 Qiskit에서 표현되는 다중 큐비트 게이트을 나타내는 방법에 변화를 준다 는 것이다. 다음을 살펴보자.
Qiskit에서 사용하는 표현은 기저 벡터를 표현하는 숫자가 증가하는 순으로 숫자를 메긴다. 예를 들어 두 큐비트 시스템의 기저 벡터들은 \(\left|00\right\rangle\), \(\left|01\right\rangle\), \(\left|10\right\rangle\), and \(\left|11\right\rangle\) 순으로 배열된다. 이때 기저 벡터에 대응하는 비트열을 살펴보면 각각 0,1,2,3이 인코딩 되어 있다.
큐비트에 적용되는 조절 연산들¶
일반적인 다중 큐비트 게이트는 한 큐비트에 가하는 게이트나 다른 큐비트에 조건부로 작용하는 게이트나 그 응용을 모두 포함한다. 예를들어 첫번째 큐빗이 \(\left|0\right\rangle\) 일 때 두번째 큐비트가 플립되는 게이트를 고려하자. 이러한 게이트는 제어 게이트 (controlled gates)로 알려져 있다. 표준 다중 큐비트 게이트는 두 큐비트 게이트와 세 큐비트 게이트로 구성되어 있는데 두 비트 게이트에는 -제어 파울리 게이트 (controlled Pauli gates) - 제어 하다마드 게이트 (controlled Hadamard gate) - 제어 회전 게이트 (controlled rotation gates) - 제어 위상 게이트 (controlled phase gate) - 제어 u3게이트 (controlled u3 gate) - 교환 게이트 (swap gate)가 있으며
세 큐비트 게이트에는 - Toffoli gate - Fredkin gate가 있다.
이중 큐비트 게이트¶
대부분의 두 큐비트 게이트들은 다른 큐비트에 의해 조절되는 유형이다. (SWAP 게이트는 예외) 일반적으로 다른 큐비트에 의해 조절되는 두 큐비트 게이트 \(C_{U}\) 는 첫번째 큐비트의 상태가 \(\left|1\right\rangle\) 일 때 단일 큐비트 유니터리 \(U\) 를 두번째 큐비트에 가하는 식이다. 유니터리 \(U\) 가 다음 행렬이라고 가정하자.
우리는 \(C_{U}\) 가 어떻게 작용하는 다음과 같이 살펴볼 수 있다. 우선 두 큐비트 시스템의 기저 벡터는 \(\left|00\right\rangle, \left|01\right\rangle, \left|10\right\rangle, \left|11\right\rangle\) 순으로 쓸 수 있다. 제어 큐비트 는 큐비트 0 이라 가정하자(Qiskit 표기에 따르면 텐서곱 연산의 오른쪽 에 해당). 만약 제어 큐비트가 \(\left|1\right\rangle\) 이면 \(U\) 가 작용할 때 기저 벡터들은 다음과 같이 변환된다.
\(C_{U}\) 를 행렬로 나타내면 다음과 같다.
이러한 행렬 성분을 알아내려면,
위에 주어진 \(C_{U}\) 의 작용을 먼저 계산하고 내적을 계산한다.
아래 예에서 볼 수 있듯이 이 연산은 Qiskit에서 cU(q[0],q[1])
로 구현되어 있다.
만약 큐비트 1이 제어 큐비트 그리고 큐비트 0이 표적 큐비트 이라면, 기저 벡터는 다음과 같이 변환된다.
이는 \(C_{U}\) 의 행렬 형태가 다음과 같음을 의미한다.
[37]:
q = QuantumRegister(2)
제어 파울리 게이트(Controlled Pauli Gate)¶
제어 반전 게이트 (Controlled-X or Controlled-not Gate)¶
제어 반전 게이트는 조절 큐비트의 상태가 \(\left|1\right\rangle\) 일 때 표적
큐비트을 반전 시킨다. 만약 조절 큐비트가 MSB라면 (e.g. cx(q[1],q[0])
) 그 행렬은 다음과 같다.
그러나 조절 큐비트가 LSB이면 (e.g. cx(q[0],q[1])
), 이 게이트는 다음 행렬과 동일하다.
[38]:
qc = QuantumCircuit(q)
qc.cx(q[0],q[1])
qc.draw()
[38]:
q1_0: ──■── ┌─┴─┐ q1_1: ┤ X ├ └───┘
[39]:
job = execute(qc, backend)
job.result().get_unitary(qc, decimals=3)
[39]:
array([[1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
[0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j],
[0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j],
[0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j]])
제어 \(Y\) 게이트 (Controlled Y Gate)¶
조절 큐비트이 MSB이면 \(Y\) 게이트를 목표 큐비트에 적용한다.
만약 조절 큐비트이 LSB이면 다음을 적용한다.
[40]:
qc = QuantumCircuit(q)
qc.cy(q[0],q[1])
qc.draw()
[40]:
q1_0: ──■── ┌─┴─┐ q1_1: ┤ Y ├ └───┘
[41]:
job = execute(qc, backend)
job.result().get_unitary(qc, decimals=3)
[41]:
array([[1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
[0.+0.j, 0.+0.j, 0.+0.j, 0.-1.j],
[0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j],
[0.+0.j, 0.+1.j, 0.+0.j, 0.+0.j]])
위상 제어 게이트 (controlled \(Z\) or controlled phase gate)¶
비슷하게, 위상 반전 게이트는 표적 큐비트의 상태가 \(\left|1\right\rangle\) 일 때 제어 큐비트의 위상을 뒤집어 준다. 제어 큐비트가 MSB인 경우나 LSB인 경우나 상관없이 행렬은 같은 모양이다.
[42]:
qc = QuantumCircuit(q)
qc.cz(q[0],q[1])
qc.draw()
[42]:
q1_0: ─■─ │ q1_1: ─■─
[43]:
job = execute(qc, backend)
job.result().get_unitary(qc, decimals=3)
[43]:
array([[ 1.-0.j, 0.+0.j, 0.+0.j, 0.+0.j],
[ 0.+0.j, 1.-0.j, 0.+0.j, 0.+0.j],
[ 0.+0.j, 0.+0.j, 1.-0.j, 0.+0.j],
[ 0.+0.j, 0.+0.j, 0.+0.j, -1.+0.j]])
조절된 하다마드 게이트¶
만약 제어 큐비트가 \(\left|1\right\rangle\) 이면 \(H\) 를 타겟 큐비트에 적용한다. 아래는 제어 큐비트가 LSB 큐비트인 경우이다.
[44]:
qc = QuantumCircuit(q)
qc.ch(q[0],q[1])
qc.draw()
[44]:
q1_0: ──■── ┌─┴─┐ q1_1: ┤ H ├ └───┘
[45]:
job = execute(qc, backend)
job.result().get_unitary(qc, decimals=3)
[45]:
array([[ 1. -0.j, 0. +0.j, 0. +0.j, 0. +0.j],
[ 0. +0.j, 0.707-0.j, 0. +0.j, 0.707-0.j],
[ 0. +0.j, 0. +0.j, 1. -0.j, 0. +0.j],
[ 0. +0.j, 0.707+0.j, 0. +0.j, -0.707+0.j]])
회전 제어 게이트 (Controlled rotation gate)¶
Z-축을 기준으로 한 회전 제어¶
만약 조절 큐비트 (여기서는 LSB)가 :math:`left|1rightrangle`인 경우 z-축을 중심으로 회전 변환을 실행한다.
[46]:
qc = QuantumCircuit(q)
qc.crz(pi/2,q[0],q[1])
qc.draw()
[46]:
q1_0: ─────■────── ┌────┴─────┐ q1_1: ┤ RZ(pi/2) ├ └──────────┘
[47]:
job = execute(qc, backend)
job.result().get_unitary(qc, decimals=3)
[47]:
array([[1. +0.j , 0. +0.j , 0. +0.j , 0. +0.j ],
[0. +0.j , 0.707-0.707j, 0. +0.j , 0. +0.j ],
[0. +0.j , 0. +0.j , 1. +0.j , 0. +0.j ],
[0. +0.j , 0. +0.j , 0. +0.j , 0.707+0.707j]])
위상 제어 회전¶
만약 두 큐비트가 모두 \(\left|11\right\rangle\) 상태에 있으면 위상 회전을 실시한다. 이 경우 조절 큐비트가 MSB나 LSB에 상관없이 행렬은 같은 형태이다.
[48]:
qc = QuantumCircuit(q)
qc.cu1(pi/2,q[0], q[1])
qc.draw()
[48]:
q1_0: ─■───── │pi/2 q1_1: ─■─────
[49]:
job = execute(qc, backend)
job.result().get_unitary(qc, decimals=3)
[49]:
array([[1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
[0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j],
[0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j],
[0.+0.j, 0.+0.j, 0.+0.j, 0.+1.j]])
\(u3\) 회전 제어¶
Perform controlled-\(u3\) rotation on the target qubit if the control qubit (here LSB) is \(\left|1\right\rangle\).
[50]:
qc = QuantumCircuit(q)
qc.cu3(pi/2, pi/2, pi/2, q[0], q[1])
qc.draw()
[50]:
q1_0: ──────────■─────────── ┌─────────┴──────────┐ q1_1: ┤ U3(pi/2,pi/2,pi/2) ├ └────────────────────┘
[51]:
job = execute(qc, backend)
job.result().get_unitary(qc, decimals=3)
[51]:
array([[ 1. +0.j , 0. +0.j , 0. +0.j , 0. +0.j ],
[ 0. +0.j , 0.707+0.j , 0. +0.j , -0. -0.707j],
[ 0. +0.j , 0. +0.j , 1. +0.j , 0. +0.j ],
[ 0. +0.j , 0. +0.707j, 0. +0.j , -0.707+0.j ]])
교환 게이트(SWAP gate)¶
SWAP 게이트는 두 큐비트의 상태를 서로 바꿔 준다. 이는 기저 벡터를 다음과 같이 변환한다.
이것은 다음의 행렬 표현식을 가진다.
[52]:
qc = QuantumCircuit(q)
qc.swap(q[0], q[1])
qc.draw()
[52]:
q1_0: ─X─ │ q1_1: ─X─
[53]:
job = execute(qc, backend)
job.result().get_unitary(qc, decimals=3)
[53]:
array([[1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
[0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j],
[0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j],
[0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j]])
삼중 큐비트 게이트¶
일반적으로 사용되는 두 가지 삼중 큐비트 게이트가 있다. 세 큐비트의 경우 기저 벡터는 다음과 같이 정렬된다.
비트열 처럼 정수 \(0,1,2,\cdots, 7\) 를 나타낸다. 다시한번 말하지만 Qiskit은 첫번째 큐비트를 가장 오른쪽에 세번째 큐비트를 가장 왼쪽에 위치하는 표현을 사용한다.
토폴리(Toffoli) 게이트 (\(ccx\) 게이트)¶
토폴리 게이트 는 첫번째와 두번째 큐비트(LSB)이 모두 \(\left|1\right\rangle\) 일 때 세번째 큐비트을 플립한다.
행렬식으로 표현한 토폴리 게이트는 다음과 같다.
[54]:
q = QuantumRegister(3)
[55]:
qc = QuantumCircuit(q)
qc.ccx(q[0], q[1], q[2])
qc.draw()
[55]:
q2_0: ──■── │ q2_1: ──■── ┌─┴─┐ q2_2: ┤ X ├ └───┘
[56]:
job = execute(qc, backend)
job.result().get_unitary(qc, decimals=3)
[56]:
array([[1.-0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
[0.+0.j, 1.-0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
[0.+0.j, 0.+0.j, 1.-0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
[0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 1.-0.j],
[0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 1.-0.j, 0.+0.j, 0.+0.j, 0.+0.j],
[0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 1.-0.j, 0.+0.j, 0.+0.j],
[0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 1.-0.j, 0.+0.j],
[0.+0.j, 0.+0.j, 0.+0.j, 1.-0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j]])
교환 제어 게이트(프레드킨 게이트)(Controlled swap gate, Fredkin gate)¶
프레드킨 게이트 혹은 교환 제어 게이트(controlled swap gate) 는 첫번째(LSB) 큐비트이 \(\left|1\right\rangle\) 일 때 두번째와 세번째 큐비트를 교환한다.
프레드킨 게이트를 행렬로 표현하면 다음과 같다.
[57]:
qc = QuantumCircuit(q)
qc.cswap(q[0], q[1], q[2])
qc.draw()
[57]:
q2_0: ─■─ │ q2_1: ─X─ │ q2_2: ─X─
[58]:
job = execute(qc, backend)
job.result().get_unitary(qc, decimals=3)
[58]:
array([[1.-0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
[0.+0.j, 1.-0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
[0.+0.j, 0.+0.j, 1.-0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
[0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 1.-0.j, 0.+0.j, 0.+0.j],
[0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 1.-0.j, 0.+0.j, 0.+0.j, 0.+0.j],
[0.+0.j, 0.+0.j, 0.+0.j, 1.-0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
[0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 1.-0.j, 0.+0.j],
[0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 1.-0.j]])
비 유니테리 연산들 (Non-unitary operations)¶
양자 회로에서 사용가능한 모든 유니터리 연산들을 살펴보았으니 이제 유니터리가 아닌 연산들을 살펴보도록 하자. 유니터리가 아닌 연산에는 측정, 큐비트의 reset, classical conditional 연산이 있다.
[59]:
q = QuantumRegister(1)
c = ClassicalRegister(1)
측정¶
양자 컴퓨터에서 측정을 수행하여도 모든 정보를 얻을 수는 없다. 양자 상태는 표준 기저에 사영된다. 아래 두 예는 각각 양자상태들이 기저 상태 중 하나로 준비되거나 중첩 상태로 준비되는 회로를 보여준다.
[60]:
qc = QuantumCircuit(q, c)
qc.measure(q, c)
qc.draw()
[60]:
┌─┐ q3_0: ┤M├ └╥┘ c0_0: ═╩═
[61]:
backend = BasicAer.get_backend('qasm_simulator')
job = execute(qc, backend, shots=1024)
job.result().get_counts(qc)
[61]:
{'0': 1024}
이 시뮬레이터는 100퍼센트 확률로 고전적인 레지스터의 값이 0 임을 예측한다.
[62]:
qc = QuantumCircuit(q, c)
qc.h(q)
qc.measure(q, c)
qc.draw()
[62]:
┌───┐┌─┐ q3_0: ┤ H ├┤M├ └───┘└╥┘ c0_0: ══════╩═
[63]:
job = execute(qc, backend, shots=1024)
job.result().get_counts(qc)
[63]:
{'0': 532, '1': 492}
이 시뮬레이터는 50퍼센트 확률로 고전적인 레지스터의 값이 0이거나 1임을 예측한다.
초기화 (Reset)¶
계산하는 동안 큐비트들을 \(\left|0\right\rangle\) 상태로 재설정
하는 것도 가능하다. 재설정
은 가역연산이 아니므로 게이트 연산이 아님을 주목하자.
[64]:
qc = QuantumCircuit(q, c)
qc.reset(q[0])
qc.measure(q, c)
qc.draw()
[64]:
┌─┐ q3_0: ─|0>─┤M├ └╥┘ c0_0: ══════╩═
[65]:
job = execute(qc, backend, shots=1024)
job.result().get_counts(qc)
[65]:
{'0': 1024}
[66]:
qc = QuantumCircuit(q, c)
qc.h(q)
qc.reset(q[0])
qc.measure(q, c)
qc.draw()
[66]:
┌───┐ ┌─┐ q3_0: ┤ H ├─|0>─┤M├ └───┘ └╥┘ c0_0: ═══════════╩═
[67]:
job = execute(qc, backend, shots=1024)
job.result().get_counts(qc)
[67]:
{'0': 1024}
여기서 우리는 시뮬레이터가 두 회로 모두 결과값이 0 상태임을 100 퍼센트의 확률로 예측함을 알 수 있다.
조건 연산자¶
고전적인 레지스터의 상태에 따라 연산을 하는 것도 가능하다.
[68]:
qc = QuantumCircuit(q, c)
qc.x(q[0]).c_if(c, 0)
qc.measure(q,c)
qc.draw()
[68]:
┌───┐ ┌─┐ q3_0: ─┤ X ├─┤M├ └─┬─┘ └╥┘ ┌──┴──┐ ║ c0_0: ╡ = 0 ╞═╩═ └─────┘
여기서 고전 비트의 값은 항상 0이라서 큐비트의 상태는 항상 플립된다.
[69]:
job = execute(qc, backend, shots=1024)
job.result().get_counts(qc)
[69]:
{'1': 1024}
[70]:
qc = QuantumCircuit(q, c)
qc.h(q)
qc.measure(q,c)
qc.x(q[0]).c_if(c, 0)
qc.measure(q,c)
qc.draw()
[70]:
┌───┐┌─┐ ┌───┐ ┌─┐ q3_0: ┤ H ├┤M├─┤ X ├─┤M├ └───┘└╥┘ └─┬─┘ └╥┘ ║ ┌──┴──┐ ║ c0_0: ══════╩═╡ = 0 ╞═╩═ └─────┘
[71]:
job = execute(qc, backend, shots=1024)
job.result().get_counts(qc)
[71]:
{'1': 1024}
여기서 첫번째 측정에 따른 고전 비트는 무작위이지만 조건 연산의 결과 상태는 결정적으로 \(\left|1\right\rangle\) 이다.
임의 초기화¶
큐비트 레지스터를 임의의 상태로 초기화 하고 싶다면 어떻게 하면 될까? \(n\) 큐비트의 임의의 상태는 각 진폭을 절대값 해서 제곱한 값을 모두 더했을 때 1이 되는 \(2^n\) 개의 진폭을 나타내는 숫자로 구성된 벡터로 표현할 수 있다. 예를 들어, 다음의 세 큐비트 상태는 이렇게 준비될 수 있다.
[77]:
# Initializing a three-qubit quantum state
import math
desired_vector = [
1 / math.sqrt(16) * complex(0, 1),
1 / math.sqrt(8) * complex(1, 0),
1 / math.sqrt(16) * complex(1, 1),
0,
0,
1 / math.sqrt(8) * complex(1, 2),
1 / math.sqrt(16) * complex(1, 0),
0]
q = QuantumRegister(3)
qc = QuantumCircuit(q)
qc.initialize(desired_vector, [q[0],q[1],q[2]])
qc.draw()
[77]:
┌───────────────────────────────────────────────────────────────────┐ q28_0: ┤0 ├ │ │ q28_1: ┤1 initialize(0.25j,0.35355,0.25+0.25j,0,0,0.35355+0.70711j,0.25,0) ├ │ │ q28_2: ┤2 ├ └───────────────────────────────────────────────────────────────────┘
[74]:
backend = BasicAer.get_backend('statevector_simulator')
job = execute(qc, backend)
qc_state = job.result().get_statevector(qc)
qc_state
[74]:
array([2.50000000e-01+0.j , 5.55111512e-17-0.35355339j,
2.50000000e-01-0.25j , 0.00000000e+00+0.j ,
0.00000000e+00+0.j , 7.07106781e-01-0.35355339j,
8.67361738e-17-0.25j , 0.00000000e+00+0.j ])
Fidelity 는 두 상태가 같은지 확인하는데 유용하다. 두 pure 양자 상태 \(\left|\psi_1\right\rangle\) 와 \(\left|\psi_2\right\rangle\) 의 fidelity는 다음과 같다.
두 상태가 동일할 때만 fidelity 값은 \(1\) 이 된다.
[75]:
state_fidelity(desired_vector,qc_state)
[75]:
1.0
자세한 내용:¶
어떻게 원하는 상태가 준비될 수 있을까? 이를 수행할 수 있는 다양한 방법들이 있지만 Qiskit에서는 Shende et al이 제안한 방법 을 사용한다. 이 논문의 아이디어는 양자 레지스터가 애초에 원하는 상태로 시작했다고 가정한 다음 \(\left|00..0\right\rangle\) 상태로 변환하는 회로를 구성하는 것이다. 그리고 이 회로의 역변환을 하여 초기화 회로를 얻는다.
우리는 임의의 양자 상태를 계산가능한 기저(computational basis)의 영 상태(zero state)로 만들고자 큐비트을 얽힘을 푸는 과정을 반복한다. 임의의 단일 큐비트 상태 \(\left|\rho\right\rangle\) 는 Z축을 중심으로 회전하는 \(\phi\)-자유도 회전과 Y축을 중심으로 회전하는 \(\theta\)-자유도 회전을 연이어 수행하여 상태 \(\left|0\right\rangle\) 로 변환할 수 있다.
우리가 지금 다루고 있는 큐비트은 한 개가 아닌 \(n\) 개이므로 상태 벡터에서 가정 덜 중요한 비트(LSB)를 분리해 내고자 다음과 같이 성분 별로 묶어 분해 한다.
이제 각 단일 큐비트 상태 \(\left|\rho_0\right\rangle, ..., \left|\rho_{2^{n-1}-1}\right\rangle\) 는 적절한 각도 \(\phi\) and \(\theta\) 를 찾아내므로써 \(\left|0\right\rangle\) 로 변환할 수 있다. LSB의 얽힘을 풀어 나가는 이 과정을 모든 상태 동시에 수행하는 유니터리는 다음과 같다.
그러므로,
U는 블록 대각 행렬이므로 “양자 멀티플렉서(quantum multiplexor)” 게이트로 구현할 수 있다. 양자 멀티플렉서에서 크기가 :math:`2^n times 2^n`이며 :math:`2^s`개의 블록 행렬로 구성된 블록 대각 행렬은 선택 큐비트이 :math:`s`개이고 데이터 큐비트가 :math:`n-s`개인 멀티플렉서와 동일하다. 이때 선택 큐비트의 상태에 따라 데이터 큐빗에 적용되는 블록이 선택된다. 이러한 종류의 멀티플렉서는 기본게이트 cx, rz 그리고 ry를 이용하여 재귀적으로 분해한 형태로 구현할 수 있다.
[76]:
import qiskit.tools.jupyter
%qiskit_version_table
%qiskit_copyright
Version Information
Qiskit Software | Version |
---|---|
Qiskit | None |
Terra | 0.14.0 |
Aer | 0.6.0 |
Ignis | 0.3.0 |
Aqua | None |
IBM Q Provider | 0.6.1 |
System information | |
Python | 3.7.7 (default, Mar 26 2020, 10:32:53) [Clang 4.0.1 (tags/RELEASE_401/final)] |
OS | Darwin |
CPUs | 4 |
Memory (Gb) | 16.0 |
Tue Apr 28 22:09:33 2020 EDT |
This code is a part of Qiskit
© Copyright IBM 2017, 2020.
This code is licensed under the Apache License, Version 2.0. You may
obtain a copy of this license in the LICENSE.txt file in the root directory
of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
Any modifications or derivative works of this code must retain this
copyright notice, and modified files need to carry a notice indicating
that they have been altered from the originals.
[ ]: