Cirqでベル状態を作って学ぶ量子コンピューティング

- arata-furukawa

Cirqは、NISQ(Noisy Intermediate-Scale Quantum Computer)向けの量子回路を作成、編集、実行するためのPythonフレームワークです。 NISQは直訳すると「ノイズあり中間スケール量子コンピュータ」です。

この記事では、量子ゲートの基本的な計算を理解するために、Cirqでベル状態をシミュレーションすることを目指します。なるべく難しい話は避けて、数式は途中式を省かず、量子ゲートの働きを理解しやすいように説明していきたいと思います。

Cirqとは

量子コンピュータが古典的コンピュータではシミュレーション不可能な性能を発揮することをQuantum Supremacy(量子超越性)といいます。数年内に、「特定のアルゴリズムに関する量子超越性が実証される」のではないか、という関心が高まっています。

古典的コンピュータや通信と同様に、量子コンピュータでもノイズエラーが発生します。現在の量子コンピュータはノイズによるエラーを訂正するには量子ビット数が少なすぎるため、十分にスケールしません。

しかし近年、ノイズエラーの訂正が完全でなくとも、量子ビット数がある程度あれば、「特定のアルゴリズムにおいて」は量子超越性を実証できる可能性が指摘されています。このような弱い量子コンピュータをNISQ(Noisy Intermediate-Scale Quantum Computer)といいます。以前は49量子ビットあれば量子超越性を示せるという見解をGoogleの研究チームは示していましたが、古典的スーパーコンピュータが49量子ビットをシミュレーションできたとする実験結果もあり、まだ決着はついていません。

Googleは、(少なくとも発表されているものとしては、)FluxmonGmonXmonの三種類の量子コンピュータチップを開発しています。物理的なアーキテクチャが異なりそれぞれに特徴がありますが、特にXmonはゲートコンピューティングに適したエラー訂正を必要とするタイプのアーキテクチャで、以前には22量子ビットのXmonチップであるFoxtailを開発しています。さらに、2018年の3月にはBristlecone[1]と名付けられた72量子ビットを持つチップを発表しています。これは、昨年トップのIBMの50量子ビットを大きく上回る量子ビットの数です。もちろん、量子ビットの数が全てではないのですが、とても大きなインパクトのあるニュースでしたね。

そして、2018年7月に、Cirqのパブリックアルファ版が公開されました。

Cirqは抽象化された量子回路を作成し、実際のアーキテクチャやそのシミュレータで実行可能なオペレーションにコンパイルし、最適化します。すでにXmonアーキテクチャのシミュレータを実装しており、将来的な実機での利用を想定していることが見て取れるAPI設計になっています。いずれ他のアーキテクチャや、実機やクラウド提供での実行もサポートしていくと考えられます。

Cirqの使い方

CirqはPython環境があればすぐに始められます。pipコマンドでインストールしましょう。プラットフォームによっては追加の作業が必要になりますが、詳細はドキュメントを参照してください。

$ pip install -Uq cirq

PythonでCirqを使うときは、cirqモジュールをインポートします。

import cirq

ここからは、実際のコードを見ながらCirqの使い方を確認していきましょう。

量子回路

Cirqで回路を作るためには、以下の要素を理解する必要があります:

  • Qubit: 量子ビット
  • Operation: 量子ビットの操作
  • Moment: オペレーションのタイムスライス
  • Circuit: モーメントのシーケンスで構成される量子回路

量子回路は、1つ以上の量子ビットに対する量子ゲートのシーケンスです。しばしば以下のような図で表されます:

これは例であり回路に意味はありません。この場合は2つの量子ビット(QubitQ0Q_0,Q1Q_1があります。この図は、2本の線がそれぞれの量子ビットの時間的発展の時間軸を示しています。

この図では量子ビットに対する作用が4種類使われています。HH,ZZ,XXと、2つの量子ビットを結ぶCNOTCNOTです。量子ビットに対する作用をオペレーションと呼びます。オペレーションは、単一または複数の量子ビットに対する作用であり、その中でも量子ゲートは最も一般的なオペレーションです。

回路図を時間軸で見たときのタイムスライスを、モーメントといいます。この場合、モーメントはHIH \otimes Iと、CNOTCNOTと、XZX \otimes Zの、3つとなります。モーメントは量子ゲートによる量子ビットの時間的発展の「抽象的な」タイムスライスであり、ハードウェアまたはシミュレータ上のスケジューリングに直接引き継がるものではありません。ゆえに同一のモーメント内のオペレーションが実際のデバイスで同時発生するようにスケジュールされる保証はありませんが、回路上のモーメントが示すトポロジカルな順序は守られます。

Cirqでの実装

まず量子ビットを作成します。量子ビットは一意なIDを持ちます。作り方は様々です。

cirq.LineQubit(3) # 整数ID
cirq.GridQubit(2, 1) # 2つの整数のID
cirq.NamedQubit('somename') # 文字列ID

どれで作っても構いませんが、重要なのは量子ビットがどういう順序で回路に与えられるかです。Cirqでは、cirq.QubitOrderか量子ビットのリストで順序を制御します。実際の量子コンピュータでは、ハードウェア上の制約や実行パフォーマンスなどのためにその配置が重要になるケースがあります。シミュレータしかないので現時点ではあまり意識する必要はありません。

なお、以下のコードでは、LineQubitで定義し、IDの順番のリスト(インデックス=IDとなるようにします)で順序を制御します。

まずはN個の量子ビットのリストQを作成してみましょう:

N = 2
Q = [cirq.LineQubit(i) for i in range(N)]

次に、先程の例の図の回路を作ります。作り方はfrom_opsを使う方法と、appendで追加する方法があります。回路は、print関数などにより、テキストダイアグラムで表示することができます。

C = cirq.Circuit.from_ops(
  cirq.H(Q[0]),
  cirq.CNOT(Q[0], Q[1]),
  cirq.X(Q[0]),
  cirq.Z(Q[1]),
)

# または
C = cirq.Circuit() # 空の回路の作成
C.append([
  cirq.H(Q[0]),
  cirq.CNOT(Q[0], Q[1]),
  cirq.X(Q[0]),
  cirq.Z(Q[1]),
])

print(C) # 回路をテキストで表示

回路図が以下のように出力されるはずです:

0: ───H───@───X───
          │
1: ───────X───Z───

Google Xmonシミュレータで実行

シミュレータには、simulaterunの2つの実行方法があります。もちろん、シミュレータで実行する以上はどちらもシミュレーションですが、前者は任意の量子状態を与えたり、本来なら観測で壊れてしまう量子状態の値を取得することができたりと、デバッグ用途に向いています。

ただし、量子コンピュータデバイスによってサポートされる演算は限られるため、回路は実行前にプリセットの量子ゲートの組合せに変換、最適化されます。当然ながら観測結果が変わるようなことはありませんが、デバイスの慣例による絶対位相差によって、simulateで取得できる内部状態の値は変わる可能性があります。simulateはあくまでも小さな回路のデバッグ用途であって、実際にはrunによる検証が推奨されます。

以後、頻繁にシミュレータで回路を実行するので、Xmonシミュレータを作成し、simulaterunを実行し、結果をテキストとグラフで出力する関数を定義しておきます。

まず実行に必要な設定をします(ここはCirqとは関係ありません):

import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['figure.figsize'] = (6, 4)
plt.rcParams['figure.dpi'] = 200
repr_bin = lambda N, x: ('{:0%d}'%N).format(int(bin(x)[2:]))

# シミュレーション結果を表示する関数
def plot_results(circuit, N, simulated, ran, repetitions, name):
  title = 'Results of "{}"'.format(name)
  bins = np.arange(2**N)
  labels = [repr_bin(N, i) for i in bins]
  probs = np.asarray([np.power(np.abs(s),2) for s in simulated.final_state])
  hist = ran.histogram(key='m')
  for i in bins:
    hist.setdefault(i, 0)
  print('----- {} -----'.format(title))
  print('Circuit:')
  print(circuit)
  print('')
  print('Simulation:')
  for i, p in enumerate(probs):
    print(' {} : {:>7.2%}'.format(labels[i], p))
  print('')
  print('Run(repetitions={})'.format(repetitions))
  for i in bins:
    print(' {} : {:>4}'.format(labels[i], hist[i]))
  plt.title(title)
  plt.yticks(bins, labels)
  plt.barh(bins - 0.2, hist.values(), height=0.2, align='edge', label='measure')
  plt.barh(bins, probs * repetitions, height=0.2, align='edge', label='expect')
  plt.legend(loc='best')
  plt.ylim(ymax=-0.4, ymin=2**N-0.6)
  plt.show()

次に、Cirqを使ってシミュレーションを実行するrun関数を定義しておきます:

def run(circuit, qubits, repetitions=1000, enable_plot=True, name=None):
  """ 回路の実行

  与えられた回路の最後にすべてのQubitの観測を追加し、
  Xmonシミュレータで反復実行した結果を返します

  Args:
    circuit: 実行する回路
    qubits: 最後に観測するQubitのリスト
    repetitions: 反復回数(デフォルト: 1000)
    enable_plot: グラフをプロットする(デフォルト: True)
    name: 表示する文字列
  """
  def _simulate():
    simulator = cirq.google.XmonSimulator()
    return simulator.simulate(circuit.copy(), qubit_order=qubits)
  def _run():
    # すべてのQubitの観測を回路のコピーに追加します
    # measureの引数keyに与えた名前であとから参照できます
    C = circuit.copy()
    C.append(cirq.measure(*qubits, key='m'))
    simulator = cirq.google.XmonSimulator()
    return simulator.run(C, repetitions=repetitions, qubit_order=qubits)

  s, r = _simulate(), _run()
  if enable_plot:
    # 結果を表示する
    plot_results(circuit, len(qubits), s, r, repetitions, name)
  return s, r

では、1量子ビットを観測する空の回路を実行してみましょう!

N = 1
Q = [cirq.LineQubit(i) for i in range(N)]
C = cirq.Circuit()
_ = run(C, Q, name='Empty Circuit')

出力は以下のようなものを得るでしょう:

----- Results of "Empty Circuit" -----
Circuit:


Simulation:
 |0> : 100.00%
 |1> :   0.00%

Run(repetitions=1000)
 |0> : 1000
 |1> :    0

シミュレーション結果は、0\ket{0}の観測確率が100%となり、実際に1000回の反復実行の結果は100%の割合で0\ket{0}を得ました。現在、Xmonシミュレータはノイズを想定していないので、多くの場合はシミュレーションの期待値通りの結果を得ますが、実際のデバイスでは、多くの場合はノイズエラーにより、この通りの観測結果が得られるわけではありません(例えば、数%の確率で1\ket{1}が観測されたりします)。

上記の結果より、天下り的にもわかることですが、量子ビットの初期状態は0\ket{0}です。

NNビットの量子状態ベクトルψ\ket{\psi}は、直交正規性を満たす0,1,,2N1\ket{0},\ket{1},\cdots,\ket{2^N-1}を基底とする2N2^N次元複素数ベクトルでした。

ψ=c00+c11++cN12N1=(c0c1c2N1)ckC\begin{aligned} \ket{\psi} &= c_0 \ket{0} + c_1 \ket{1} + \cdots + c_{N-1} \ket{2^N-1} &= \begin{pmatrix} c_0 \\ c_1 \\ \vdots \\ c_{2^N-1} \end{pmatrix} \quad c_k \in \mathbb{C} \end{aligned}

ただし、k=02N1ck2=1\sum_{k=0}^{2^N-1}{|c_k|^2} = 1です。

回路の初期状態は0\ket{0}です。1量子ビットの回路の初期状態0\ket{0}の初期状態は以下のようになります:

0=(1+0i0+0i)\ket{0} = \begin{pmatrix} 1+0i \\ 0+0i \end{pmatrix}

0\ket{0}が観測される確率は1+0i2=1|1+0i|^2 = 11\ket{1}が観測される確率は0+0i2=0|0+0i|^2 = 0ですから、実行結果と一致しました。

1-qubitに作用する基本的な量子ゲート

基礎的な単一量子ビットに対する量子ゲートを式とCirqの実行結果で確認してみましょう。

XXゲート

量子ゲートの中でも最も基本のゲートの1つに、XXゲートがあります。NOTゲート、Bit Flipper、Pauli-XXゲートなどとも呼ばれます。

XXゲートのユニタリ行列は以下のようなものです:

X=(0110)X = \begin{pmatrix} 0 & 1 \\ 1 & 0 \end{pmatrix}

(αβ)\begin{pmatrix} \alpha \\ \beta \end{pmatrix}を入力とすると、

X(αβ)=(0110)(αβ)=(βα)\begin{aligned} X \begin{pmatrix} \alpha \\ \beta \end{pmatrix} &= \begin{pmatrix} 0 & 1 \\ 1 & 0 \end{pmatrix} \begin{pmatrix} \alpha \\ \beta \end{pmatrix} \\ &= \begin{pmatrix} \beta \\ \alpha \end{pmatrix} \end{aligned}

となります。よって、X0=1X\ket{0} = \ket{1}X1=0X\ket{1} = \ket{0}となります。

初期状態0\ket{0}で、XXゲートを適用する回路を実行してみましょう。

N = 1
Q = [cirq.LineQubit(i) for i in range(N)]
C = cirq.Circuit.from_ops(
  cirq.X(Q[0]) # Xゲートを、0番目のqubitに作用します
)
_ = run(C, Q, name='X|0>')
----- Results of "X|0>" -----
Circuit:
0: ───X───

Simulation:
 |0> :   0.00%
 |1> : 100.00%

Run(repetitions=1000)
 |0> :    0
 |1> : 1000

実際に、X0=1X\ket{0} = \ket{1}となることが確認できました。

ZZゲート

ZZゲートは、しばしば位相反転ゲート、Pauli-ZZゲート、R1R_1ゲートとも呼ばれます。

ZZゲートのユニタリ行列は以下のようなものです:

Z=(1001)Z = \begin{pmatrix} 1 & 0 \\ 0 & -1 \end{pmatrix}

(αβ)\begin{pmatrix} \alpha \\ \beta \end{pmatrix}を入力とすると、

Z(αβ)=(1001)(αβ)=(αβ)\begin{aligned} Z \begin{pmatrix} \alpha \\ \beta \end{pmatrix} &= \begin{pmatrix} 1 & 0 \\ 0 & -1 \end{pmatrix} \begin{pmatrix} \alpha \\ \beta \end{pmatrix} \\ &= \begin{pmatrix} \alpha \\ -\beta \end{pmatrix} \end{aligned}

となります。よって、Z0=0Z\ket{0} = \ket{0}Z1=1Z\ket{1} = -\ket{1}となります。実行してみましょう。

Z0Z \ket{0}の結果

N = 1
Q = [cirq.LineQubit(i) for i in range(N)]
C = cirq.Circuit.from_ops(
  cirq.Z(Q[0]),
)
_ = run(C, Q, name='Z|0>')
----- Results of "Z|0>" -----
Circuit:
0: ───Z───

Simulation:
 |0> : 100.00%
 |1> :   0.00%

Run(repetitions=1000)
 |0> : 1000
 |1> :    0

Z1Z \ket{1}の結果

N = 1
Q = [cirq.LineQubit(i) for i in range(N)]
C = cirq.Circuit.from_ops(
  cirq.X(Q[0]),
  cirq.Z(Q[0]),
)
_ = run(C, Q, name='Z|1>')
----- Results of "Z|1>" -----
Circuit:
0: ───X───Z───

Simulation:
 |0> :   0.00%
 |1> : 100.00%

Run(repetitions=1000)
 |0> :    0
 |1> : 1000

位相は観測不可能なので、測定結果からは違いがわかりません。

Hadamardゲート

Hadamard(アダマール)ゲートは、量子のSuperposition(重ね合わせ)状態を作る基本的なゲートです。HHゲートとも呼びます。

HHゲートのユニタリ行列は以下のようなものです:

H=12(1111)H = {1 \over \sqrt{2}} \begin{pmatrix} 1 & 1 \\ 1 & -1 \end{pmatrix}

(αβ)\begin{pmatrix} \alpha \\ \beta \end{pmatrix}を入力とすると、

H(αβ)=12(1111)(αβ)=12(α+βαβ)=12{α(11)+β(11)}=12{α(0+1)+β(01)}=α{12(0+1)}+β{12(01)}\begin{aligned} H \begin{pmatrix} \alpha \\ \beta \end{pmatrix} &= {1 \over \sqrt{2}} \begin{pmatrix} 1 & 1 \\ 1 & -1 \end{pmatrix} \begin{pmatrix} \alpha \\ \beta \end{pmatrix} \\ &= {1 \over \sqrt{2}} \begin{pmatrix} \alpha + \beta \\ \alpha - \beta \end{pmatrix} \\ &= {1 \over \sqrt{2}} \Bigl\{ \alpha \begin{pmatrix} 1 \\ 1 \end{pmatrix} + \beta \begin{pmatrix} 1 \\ -1 \end{pmatrix} \Bigr\} \\ &= {1 \over \sqrt{2}} \Bigl\{ \alpha (\ket{0} + \ket{1}) + \beta (\ket{0} - \ket{1}) \Bigr\} \\ &= \alpha \Bigl\{ {1 \over \sqrt{2}} (\ket{0} + \ket{1}) \Bigr\} + \beta \Bigl\{ {1 \over \sqrt{2}} (\ket{0} - \ket{1}) \Bigr\} \end{aligned}

となります。12(0±1){1 \over \sqrt{2}} (\ket{0} \pm \ket{1})は、0\ket{0}1\ket{1}の状態が、等しく122=12\bigl|{1 \over \sqrt{2}}\bigr|^2 = {1 \over 2}の確率で重ね合わさった状態です。これをSuperposition(重ね合わせ)状態といいます。計算基底から±\ket{\pm}基底への変換とみなすこともできます。

12(0±1){1 \over \sqrt{2}} (\ket{0} \pm \ket{1})は頻繁に用いられるため、しばしば以下のように±\ket{\pm}で表記されることもあります。

+=12(0+1)=12(01)\begin{aligned} \ket{+} = {1 \over \sqrt{2}} (\ket{0} + \ket{1}) \\ \ket{-} = {1 \over \sqrt{2}} (\ket{0} - \ket{1}) \\ \end{aligned}

これを用いると、H(αβ)=α++βH \begin{pmatrix} \alpha \\ \beta \end{pmatrix} = \alpha \ket{+} + \beta \ket{-}と書けます。

0\ket{0}または1\ket{1}が入力の場合は以下のとおりです。

H0=+H1=\begin{aligned} H \ket{0} &= \ket{+} \\ H \ket{1} &= \ket{-} \end{aligned}

H0H\ket{0}H1H\ket{1}はいずれも0\ket{0}1\ket{1}が50%の確率で観測されることが期待されます。ためしにH0H\ket{0}を実行してみましょう。

N = 1
Q = [cirq.LineQubit(i) for i in range(N)]
C = cirq.Circuit.from_ops(
  cirq.H(Q[0]),
)
_ = run(C, Q, 1000, name='H|0>')
----- Results of "H|0>" -----
Circuit:
0: ───H───

Simulation:
 |0> :  50.00%
 |1> :  50.00%

Run(repetitions=1000)
 |0> :  504
 |1> :  496

たしかに約50%の割合で観測されました。コイントスをすると(おそらく)表と裏が50/50で出るのと同じように、観測をすると50/50で0\ket{0}1\ket{1}が出る様子から、これを「量子コイン」と呼ぶこともあります。

2-qubitsに作用する基本的な量子ゲート

次は、2-qubitsに作用する基本的な量子ゲートを確認しましょう。

CNOTCNOTゲート

CNOTCNOTゲートは、Control-NOT(制御NOT)ゲートのことです。Control-X(制御X)ゲートともいいます。

CNOTCNOTのユニタリ行列は以下のように定義されます:

CNOT=(1000010000010010)CNOT = \begin{pmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 0 & 1 \\ 0 & 0 & 1 & 0 \end{pmatrix}

2qubitsに作用するため、4×44 \times 4のユニタリ行列です。入力は、ψ=ψCψT\ket{\psi} = \ket{\psi_C} \otimes \ket{\psi_T}としたとき、ψC\ket{\psi_C}を_Control(制御)ビット_、ψT\ket{\psi_T}を_ターゲットビット_と呼びます。入力の各量子ビットが0\ket{0}1\ket{1}のいずれかであった場合、出力は以下のようになります。

ψ\ket{\psi}

CNOTψCNOT \ket{\psi}

00\ket{0} \otimes \ket{0}

00\ket{0} \otimes \ket{0}

01\ket{0} \otimes \ket{1}

01\ket{0} \otimes \ket{1}

10\ket{1} \otimes \ket{0}

11\ket{1} \otimes \ket{1}

11\ket{1} \otimes \ket{1}

10\ket{1} \otimes \ket{0}

この表の通り、CNOTCNOTゲートは、制御ビットが0\ket{0}のときのみ、ターゲットビットにXXゲートを適用するように作用します。

# Control = 0
# Target  = 0
# 期待される出力: 00

N = 2
Q = [cirq.LineQubit(i) for i in range(N)]
C = cirq.Circuit.from_ops(
  cirq.CNOT(Q[0], Q[1]),
)
_ = run(C, Q, name='CNOT|00>')

実際に、00\ket{00}01\ket{01}10\ket{10}11\ket{11}のそれぞれの場合で試してみましょう。

CNOT00CNOT\ket{00}の結果

----- Results of "CNOT|00>" -----
Circuit:
0: ───@───
      │
1: ───X───

Simulation:
 |00> : 100.00%
 |01> :   0.00%
 |10> :   0.00%
 |11> :   0.00%

Run(repetitions=1000)
 |00> : 1000
 |01> :    0
 |10> :    0
 |11> :    0

CNOT01CNOT\ket{01}の結果

# Control = 0
# Target  = 1
# 期待される出力: 01

N = 2
Q = [cirq.LineQubit(i) for i in range(N)]
C = cirq.Circuit.from_ops(
  cirq.X(Q[1]),
  cirq.CNOT(Q[0], Q[1]),
)
_ = run(C, Q, name='CNOT|01>')
----- Results of "CNOT|01>" -----
Circuit:
0: ───────@───
          │
1: ───X───X───

Simulation:
 |00> :   0.00%
 |01> : 100.00%
 |10> :   0.00%
 |11> :   0.00%

Run(repetitions=1000)
 |00> :    0
 |01> : 1000
 |10> :    0
 |11> :    0

CNOT10CNOT\ket{10}の結果

# Control = 1
# Target  = 0
# 期待される出力: 11

N = 2
Q = [cirq.LineQubit(i) for i in range(N)]
C = cirq.Circuit.from_ops(
  cirq.X(Q[0]),
  cirq.CNOT(Q[0], Q[1]),
)
_ = run(C, Q, name='CNOT|10>')
----- Results of "CNOT|10>" -----
Circuit:
0: ───X───@───
          │
1: ───────X───

Simulation:
 |00> :   0.00%
 |01> :   0.00%
 |10> :   0.00%
 |11> : 100.00%

Run(repetitions=1000)
 |00> :    0
 |01> :    0
 |10> :    0
 |11> : 1000

CNOT11CNOT\ket{11}の結果

# Control = 1
# Target  = 1
# 期待される出力: 10

N = 2
Q = [cirq.LineQubit(i) for i in range(N)]
C = cirq.Circuit.from_ops(
  cirq.X.on_each(Q),
  cirq.CNOT(Q[0], Q[1]),
)
_ = run(C, Q, name='CNOT|11>')
----- Results of "CNOT|11>" -----
Circuit:
0: ───X───@───
          │
1: ───X───X───

Simulation:
 |00> :   0.00%
 |01> :   0.00%
 |10> : 100.00%
 |11> :   0.00%

Run(repetitions=1000)
 |00> :    0
 |01> :    0
 |10> : 1000
 |11> :    0

量子ゲートの組合せ

ここまでいくつかの量子ビットに作用する量子ゲートを見てきました。1-qubitの量子ゲートは、2×22 \times 2のユニタリ行列でした。NN-qubitsの量子状態ベクトルは2N2^N次元複素数ベクトルですから、それに作用する量子ゲートののユニタリ行列は 2N×2N2^N \times 2^N です。

このとき、複数の量子ゲートに作用するゲートを、これまで見てきた単一または複数の量子ビットに作用する量子ゲートの組合せで表現することを考えます。

例として、2つの量子ビットからなるシステムを考えます。この量子ビットに、それぞれZZゲートとXXゲートを作用させることを考えます。

このとき、$ \ket{\psi} = \ket{\psi_0} \otimes \ket{\psi_1} $ であり、2つのゲートからなるユニタリ行列は $ Z \otimes X $ で求められます。\otimesは、クロネッカー積(テンソル積)です。

ψ=00\ket{\psi} = \ket{00}のとき、(ZX)ψ(Z \otimes X) \ket{\psi}を求めます。

ψ=00=00=(10)(10)=((10)(00))=(1000)\begin{aligned} \ket{\psi} &= \ket{00} = \ket{0} \otimes \ket{0} = \begin{pmatrix} 1 \\ 0 \end{pmatrix} \otimes \begin{pmatrix} 1 \\ 0 \end{pmatrix} = \begin{pmatrix} \begin{pmatrix} 1 \\ 0 \end{pmatrix} \\ \begin{pmatrix} 0 \\ 0 \end{pmatrix} \end{pmatrix} = \begin{pmatrix} 1 \\ 0 \\ 0 \\ 0 \end{pmatrix} \end{aligned}

ZX=(1001)(0110)=((0110)(0000)(0000)(0110))=(0100100000010010)\begin{aligned} Z \otimes X &= \begin{pmatrix} 1 & 0 \\ 0 & -1 \end{pmatrix} \otimes \begin{pmatrix} 0 & 1 \\ 1 & 0 \end{pmatrix} \\ &= \begin{pmatrix} \begin{pmatrix} 0 & 1 \\ 1 & 0 \end{pmatrix} & \begin{pmatrix} 0 & 0 \\ 0 & 0 \end{pmatrix} \\ \begin{pmatrix} 0 & 0 \\ 0 & 0 \end{pmatrix} & \begin{pmatrix} 0 & -1 \\ -1 & 0 \end{pmatrix} \end{pmatrix} \\ &= \begin{pmatrix} 0 & 1 & 0 & 0 \\ 1 & 0 & 0 & 0 \\ 0 & 0 & 0 & -1 \\ 0 & 0 & -1 & 0 \end{pmatrix} \end{aligned}

(ZX)00=(0100100000010010)(1000)=(0100)=01\begin{aligned} (Z \otimes X) \ket{00} &= \begin{pmatrix} 0 & 1 & 0 & 0 \\ 1 & 0 & 0 & 0 \\ 0 & 0 & 0 & -1 \\ 0 & 0 & -1 & 0 \end{pmatrix} \begin{pmatrix} 1 \\ 0 \\ 0 \\ 0 \end{pmatrix} = \begin{pmatrix} 0 \\ 1 \\ 0 \\ 0 \end{pmatrix} = \ket{01} \end{aligned}

Cirqで実行して、U00=01U \ket{00} = \ket{01}となることを確認しましょう。

N = 2
Q = [cirq.LineQubit(i) for i in range(N)]
C = cirq.Circuit.from_ops(
  cirq.Z(Q[0]),
  cirq.X(Q[1]),
)
_ = run(C, Q, name='(Z⊗X)|00>')
----- Results of "(Z⊗X)|00>" -----
Circuit:
0: ───Z───

1: ───X───

Simulation:
 |00> :   0.00%
 |01> : 100.00%
 |10> :   0.00%
 |11> :   0.00%

Run(repetitions=1000)
 |00> :    0
 |01> : 1000
 |10> :    0
 |11> :    0

Bell State

ここまで出てきたゲートを使って、Bell State(ベル状態)を作るゲートを作ります。

ベル状態を作るゲートBBは、CNOT(HI)CNOT(H \otimes I)で表されます(IIは単位行列)。

ユニタリ行列を計算すると、以下になります。

CNOT(HI)=(1000010000010010)(12(1111)(1001))=12(1000010000010010)(1010010110100101)=12(1010010101011010)\begin{aligned} CNOT(H \otimes I) &= \begin{pmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 0 & 1 \\ 0 & 0 & 1 & 0 \end{pmatrix} \Bigl( {1 \over \sqrt{2}} \begin{pmatrix} 1 & 1 \\ 1 & -1 \end{pmatrix} \otimes \begin{pmatrix} 1 & 0 \\ 0 & 1 \end{pmatrix} \Bigr) \\ &= {1 \over \sqrt{2}} \begin{pmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 0 & 1 \\ 0 & 0 & 1 & 0 \end{pmatrix} \begin{pmatrix} 1 & 0 & 1 & 0 \\ 0 & 1 & 0 & 1 \\ 1 & 0 & -1 & 0 \\ 0 & 1 & 0 & -1 \end{pmatrix} \\ &= {1 \over \sqrt{2}} \begin{pmatrix} 1 & 0 & 1 & 0 \\ 0 & 1 & 0 & 1 \\ 0 & 1 & 0 & -1 \\ 1 & 0 & -1 & 0 \end{pmatrix} \end{aligned}

入力を00,01,10,11\ket{00},\ket{01},\ket{10},\ket{11}とすると、以下の結果が得られます。

CNOT(HI)00=12(00+11)CNOT(HI)01=12(01+10)CNOT(HI)10=12(0011)CNOT(HI)11=12(0110)\begin{aligned} CNOT(H \otimes I) \ket{00} &= {1 \over \sqrt{2}} (\ket{00} + \ket{11}) \\ CNOT(H \otimes I) \ket{01} &= {1 \over \sqrt{2}} (\ket{01} + \ket{10}) \\ CNOT(H \otimes I) \ket{10} &= {1 \over \sqrt{2}} (\ket{00} - \ket{11}) \\ CNOT(H \otimes I) \ket{11} &= {1 \over \sqrt{2}} (\ket{01} - \ket{10}) \\ \end{aligned}

これらを**Bell State(ベル状態)**といます。

ベル状態は2量子ビットが取りうる4通りの基底のうち2つのいずれかである確率が121 \over 2であるような重ね合わせ状態ですが、さらに重要な特徴があります。

量子状態をψϕ\ket{\psi} \otimes \ket{\phi}のように積の形に分離可能(セパラブル)であるものを積状態といい、分離不可能なものを**エンタングルしている(もつれている)**といいます。ベル状態はいずれの場合も2つの量子ビットがエンタングルしています。

例えば、12(00+11){1 \over \sqrt{2}} (\ket{00} + \ket{11})が、2つの量子状態ベクトルに分離できるかを考えてみます。もし分離できる場合、量子状態ベクトルは(a0+b1)(c0+d1)=ac00+ad01+bc10+bd11(a\ket{0} + b\ket{1}) \otimes (c\ket{0} + d\ket{1}) = ac\ket{00} + ad\ket{01} + bc\ket{10} + bd\ket{11}の形で表せるはずです。係数に着目すると、ad=bc=0ad = bc = 0かつac=bd=12ac = bd = {1 \over \sqrt{2}}となるはずですが、そのような係数の組合せは存在しません。

CNOT(HI)CNOT(H \otimes I)は量子情報理論における代表的な**Quantum Entanglement(量子エンタングルメント、量子もつれ)**の生成方法の1つです。

それぞれ、観測結果には2通りの可能性が等しい確率で存在する重ね合わせ状態でした。まずは実行結果が一致するか確認してみましょう。

CNOT(HI)00CNOT(H \otimes I)\ket{00}の結果

# 初期状態 |00>
# 期待される最終状態 |00>+|11> / sqrt(2)

N = 2
Q = [cirq.LineQubit(i) for i in range(N)]
C = cirq.Circuit.from_ops(
  cirq.H(Q[0]),
  cirq.CNOT(Q[0], Q[1]),
)
_ = run(C, Q, name='BELL|00>')
----- Results of "BELL|00>" -----
Circuit:
0: ───H───@───
          │
1: ───────X───

Simulation:
 |00> :  50.00%
 |01> :   0.00%
 |10> :   0.00%
 |11> :  50.00%

Run(repetitions=1000)
 |00> :  488
 |01> :    0
 |10> :    0
 |11> :  512

CNOT(HI)01CNOT(H \otimes I)\ket{01}の結果

# 初期状態 |01>
# 期待される最終状態 |01>+|10> / sqrt(2)

N = 2
Q = [cirq.LineQubit(i) for i in range(N)]
C = cirq.Circuit.from_ops(
  cirq.X(Q[1]),
  cirq.H(Q[0]),
  cirq.CNOT(Q[0], Q[1]),
)
_ = run(C, Q, name='BELL|01>')
----- Results of "BELL|01>" -----
Circuit:
0: ───H───@───
          │
1: ───X───X───

Simulation:
 |00> :   0.00%
 |01> :  50.00%
 |10> :  50.00%
 |11> :   0.00%

Run(repetitions=1000)
 |00> :    0
 |01> :  499
 |10> :  501
 |11> :    0

CNOT(HI)10CNOT(H \otimes I)\ket{10}の結果

# 初期状態 |10>
# 期待される最終状態 |00>-|11> / sqrt(2)

N = 2
Q = [cirq.LineQubit(i) for i in range(N)]
C = cirq.Circuit.from_ops(
  cirq.X(Q[0]),
  cirq.H(Q[0]),
  cirq.CNOT(Q[0], Q[1]),
)
_ = run(C, Q, name='BELL|10>')
----- Results of "BELL|10>" -----
Circuit:
0: ───X───H───@───
              │
1: ───────────X───

Simulation:
 |00> :  50.00%
 |01> :   0.00%
 |10> :   0.00%
 |11> :  50.00%

Run(repetitions=1000)
 |00> :  518
 |01> :    0
 |10> :    0
 |11> :  482

CNOT(HI)11CNOT(H \otimes I)\ket{11}の結果

# 初期状態 |11>
# 期待される最終状態 |01>-|10> / sqrt(2)

N = 2
Q = [cirq.LineQubit(i) for i in range(N)]
C = cirq.Circuit.from_ops(
  cirq.X.on_each(Q),
  cirq.H(Q[0]),
  cirq.CNOT(Q[0], Q[1]),
)
_ = run(C, Q, name='BELL|11>')
----- Results of "BELL|11>" -----
Circuit:
0: ───X───H───@───
              │
1: ───X───────X───

Simulation:
 |00> :   0.00%
 |01> :  50.00%
 |10> :  50.00%
 |11> :   0.00%

Run(repetitions=1000)
 |00> :    0
 |01> :  502
 |10> :  498
 |11> :    0

量子エンタングルメント

確かにいずれも重ね合わせ状態の通り121 \over 2の確率で2通りの結果が観測されました。しかし、エンタングルメントとは何だったのでしょうか。これだけだといまいちわかりませんね。

先ほどはCNOT(HI)CNOT(H \otimes I)のユニタリ行列をいきなり求めましたが、もう少し理解するために、少し条件を絞った上で、時間的発展を順番に確認してみましょう。

初期状態が00\ket{00}の場合を考えます。まず、1つ目の量子ビットはHHゲートの作用を受け、H0=+H\ket{0} = \ket{+}となります。2つ目の量子ビットは何も変化せず、0\ket{0}です。

次に、CNOTCNOT+0\ket{+} \otimes \ket{0}が入力されます。さきほどの節では制御ビットが基底状態の場合の入力しか考えませんでしたが、今回の入力は+\ket{+}の重ね合わせ状態です。実際に行列を計算してみましょう。

CNOT(+0)=CNOT{12(0+1)0}=CNOT{12(00+10)}=12CNOT{00+10}=12(1000010000010010){(1000)+(0010)}=12(1000010000010010)(1010)=12(1001)=12{(1000)+(0001)}=12(00+11)\begin{aligned} CNOT(\ket{+} \otimes \ket{0}) &= CNOT \Bigl\{ {1 \over \sqrt{2}} (\ket{0} + \ket{1}) \otimes \ket{0} \Bigr\} \\ &= CNOT \Bigl\{ {1 \over \sqrt{2}} (\ket{00} + \ket{10}) \Bigr\} \\ &= {1 \over \sqrt{2}} CNOT \Bigl\{ \ket{00} + \ket{10} \Bigr\} \\ &= {1 \over \sqrt{2}} \begin{pmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 0 & 1 \\ 0 & 0 & 1 & 0 \end{pmatrix} \Bigl\{ \begin{pmatrix} 1 \\ 0 \\ 0 \\ 0 \end{pmatrix} + \begin{pmatrix} 0 \\ 0 \\ 1 \\ 0 \end{pmatrix} \Bigr\} \\ &= {1 \over \sqrt{2}} \begin{pmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 0 & 1 \\ 0 & 0 & 1 & 0 \end{pmatrix} \begin{pmatrix} 1 \\ 0 \\ 1 \\ 0 \end{pmatrix} \\ &= {1 \over \sqrt{2}} \begin{pmatrix} 1 \\ 0 \\ 0 \\ 1 \end{pmatrix} \\ &= {1 \over \sqrt{2}} \Bigl\{ \begin{pmatrix} 1 \\ 0 \\ 0 \\ 0 \end{pmatrix} + \begin{pmatrix} 0 \\ 0 \\ 0 \\ 1 \end{pmatrix} \Bigr\} \\ &= {1 \over \sqrt{2}} (\ket{00} + \ket{11}) \end{aligned}

確かに、CNOT(+0)=12(00+11)CNOT(\ket{+} \otimes \ket{0}) = {1 \over \sqrt{2}} (\ket{00} + \ket{11})となり、最初の結果と同じものが得られました。

この12(00+11){1 \over \sqrt{2}} (\ket{00} + \ket{11})のうち、1つ目の量子ビットのみを観測したとき、1\ket{1}だったとします。この場合、2つ目の量子ビットを同じ基底で観測すると、0\ket{0}1\ket{1}のどちらが得られるでしょうか?この例では、量子状態は00\ket{00}11\ket{11}の可能性しかないのですから、1つ目が1\ket{1}ならば、2つ目は理論的には1\ket{1}しか考えられません。つまり、片方のみ観測すると、観測していない方の状態も決定するのです。よく知られているように、これは2つの量子ビットが地理的にどれだけ離れていても、片方が観測で基底状態に変わると、もう片方の量子状態も決定します。

まとめ

Cirqを使うことで、量子コンピューティングの基礎となるゲート計算をとても簡単にシミュレートすることができました。今回はせいぜい二つの量子ビットしか扱わなかったですから、この程度であれば式を手で計算するほうが早いかもしれません。ここで学んだのはとても簡単な一部のゲートの働きだけで、ここから量子テレポーテーション、量子フーリエ変換、GroverのQuantum Amplitude Amplification、Shorの素因数分解…といった基礎的な量子アルゴリズムの話が始まります。

現在は肝心の量子コンピュータの実機が私達の手元にはないので、実際に試せないことが残念ではありますが、GoogleやIBMを始めとした企業や様々な研究機関が実用化を進めて目覚ましい成果を出しています。実際に量子コンピュータが量子超越性を示したとき、どのフレームワークがデファクトスタンダードになっていくのかも注目ですね。Cirqはその候補となる十分なポテンシャルを持っていると思います。今後に期待を込めつつ、今のうちから勉強して備えておきましょう。

参考文献

  • 古澤明. (2004). 量子テレポーテーションとマルチパータイトエンタングルメント. 光学, 33(5), 278-283.
  • 鄭琳琳, 松枝秀明. (2005). 量子エンタングルメントによる量子情報処理. Kochi University (Information Science), Vol.26, No.6, pdf (閲覧日: 2018年9月22日).
  • 丸山不二夫. (2018). 量子情報理論基礎演習 I. pdf

  1. 和訳すると「イガゴヨウ」。調べてみたら、マツ科の植物で、世界最古の樹齢を持つ木の種でもあるらしいです。 ↩︎