Q# Coding contestの問題の一部をCirqで解いてみる

今年の7月頃に(恐らく)世界初であろう量子プログラミングのプログラミングコンテストがありました.

codeforces.com

codeforces.com

このコンテストの際に使用できた言語はQ#のみでしたが,折角なので別の言語でもやってみようと思い今回Cirqを使用してWarmupの3問を解いてみました.

Cirqとは

Cirqとは,NISQ(Noisy Intermediate Scale Quantum)向けのPython量子コンピュータフレームワークです.詳しいドキュメントやGitHubリポジトリは以下になります.

Welcome to Cirq’s documentation! — Cirq 0.1 documentation

github.com

正直まだパブリックアルファですから,ドキュメントもそれほど充実していない状態なので,本当に詳細が知りたければコードを直接読むのが一番良い気がします.

Q# Coding contest

7月頃にCordeforcesで開催された,解答言語がQ#に限定されたプログラミングコンテンストです.

codeforces.com

codeforces.com

以下の解答例では,Warmupの方の最初の3問をCirqで実装しています.

解答コード

サンプルのコードは以下のリポジトリになります.

github.com

このリポジトリ内のgenerate.pyが今回の解答コードになります.

Generate plus state or minus state

signが1か-1で与えられるので,それに応じて \left|+\right\rangle = \frac{1}{\sqrt{2}}(\left|0\right\rangle + \left|1\right\rangle ) \left|-\right\rangle = \frac{1}{\sqrt{2}}(\left|0\right\rangle - \left|1\right\rangle )の状態を作成する問題です.最初に用意される量子ビット \left|0\right\rangleなので,ここから \left|+\right\rangleを作成するにはHゲートを適用するだけで出来ます.  \left|-\right\rangleを作成するには, \left|1\right\rangleにHゲートを適用すればいいので,最初の量子ビットにXゲートを適用して \left|0\right\rangleから \left|1\right\rangleへ変換させてからHゲートを適用させればOKです.この一連の操作のCirqのコードが次のようになります.

def generate_plus_or_minus(sign):
    circuit = cirq.Circuit()
    res_qubit = cirq.LineQubit(0)
    if (sign == -1):
        circuit.append([
            cirq.X(res_qubit),
        ])
    circuit.append([
        cirq.H(res_qubit),
    ])

    return circuit

まず最初のcircuit = cirq.Circuit()ですが,これでこれから量子ビットに対する操作を加えていく空の回路を用意します.

そして次に量子ビットres_qubit = cirq.LineQubit(0)で用意します.量子ビットを用意する方法は,LineQubit以外にも,GridQubitNamedQubitなどで用意することも出来ます.

ここまでで量子ビットとそれに対する操作を行う回路を用意したので,これ以降で実際に量子ビットに対する操作を行っていきます.  \left|-\right\rangleの状態を作りたいときだけ最初にXゲートを適用したいので,その場合にだけXゲートを用意した量子ビットに適用する操作を回路に加えます.

circuit.append([
    cirq.X(res_qubit),
])

cirq.X(res_qubit)で,res_qubitに対してXゲートを適用する操作を表しているので,それをappendを使って回路に加えることで回路を実行した時に実際にその操作が行われるようになります.

後はどちらの状態を作成するときでもHゲートを適用するだけなので,同様にappendを使ってHゲートを適用する操作を加えます.

最終的に回路を実行することで,ここで記述した操作が実行されることになります.

Generate Bell state

与えられたindexに応じて,各Bell状態を作成する問題です.Bell状態がどのような状態なのかということについては,実際の問題のページを見るのが早いです.やることとしては,まず基本となるindex:0のBell状態を作成して,その後作成するBell状態の各状態に応じて別のBell状態へ変換するための操作を行っていく感じになります.

def generate_bell_state(index):
    circuit = cirq.Circuit()
    res_qubit = [cirq.LineQubit(0), cirq.LineQubit(1)]

    circuit.append([
        cirq.H(res_qubit[0]),
        cirq.CNOT(res_qubit[0], res_qubit[1]),
    ])

    if index % 2 == 1:
        circuit.append([
            cirq.Z(res_qubit[0]),
        ])

    if index > 1:
        circuit.append([
            cirq.X(res_qubit[1]),
        ])

    return circuit

まず最初のHゲートとCNOTゲートを適用することでindex:0のBell状態を作成します.その後,目的とするBell状態に合わせてZゲートによって位相の反転を行ったり,Xゲートで片方のビットの反転を行ったりして目的とするBell状態を作成します.

Generate GHZ state

与えられたビット長のGreenberger–Horne–Zeilinger (GHZ) stateという,全てのビットが0の状態と,全てのビットが1の状態が同じ確率振幅で重ね合わせの状態にある状態を作成する問題です.

def generate_ghz_state(length):
    circuit = cirq.Circuit()
    res_qubit = [cirq.LineQubit(i) for i in range(length)]
    circuit.append([
        cirq.H(res_qubit[0]),
    ])

    for i in range(length - 1):
        circuit.append([
            cirq.CNOT(res_qubit[0], res_qubit[i + 1]),
        ])

    return circuit

目的とする状態は,1ビットの時は \left|+\right\rangle と同じ状態となります.2ビット以上の場合には,1ビット目を \left|+\right\rangleにして,2ビット目以降は1ビット目をControllビットとしてCNOTゲートを適用していけば,1ビット目が0の時は何も操作がされず,1の時にはXゲートが適用されて1になるので目的の状態を作成することができます.

実行結果

実際にこれらを実行してみます.generate.pyのmainにこれらのシミュレーションを実行して,結果を出力するためのコードが書いてあります. 実行される回路の図は,printにcircuitを渡してやると自動的に出力されます.

> print(generate_bell_state(0))
0: ───H───@───
          │
1: ───────X───

simulator = cirq.google.XmonSimulator()によって,今までに定義してきた回路を実行するシミュレータを用意します.このシミュレータに上記で定義した回路を渡すことでシミュレーションを実行し,結果を得ることが出来ます.

シミュレーション実行後に,final_stateを参照することで,シミュレーション実行後の量子ビットの状態がどのような状態になっているのかを見ることが出来ます. 例として,generate.pyを実行して,generate_plus_or_minusで1が与えられた時と,generate_bell_stateで0が与えられた時の回路と最終的な量子ビットの状態を見てみます.

$ python generate.py

Generate plus or minus state
Plus Circuit:
0: ───H───
Print Plus Circuit Result
[-0.-0.70711j  0.-0.70711j]

...

Generate Bell state
index: 0
Circuit:
0: ───H───@───
          │
1: ───────X───
Print index 0 Results
[ 0.70711-0.j  0.     +0.j -0.     +0.j  0.70711+0.j]

...

Generate plus or minus stateでは量子ビットが一つなので結果として出力されている量子ビットの状態はそれぞれ \left|0\right\rangle \left|1\right\rangleの係数を,Generate Bell stateは量子ビットが2つなので,左から順に \left|00\right\rangle \left|01\right\rangle \left|10\right\rangle \left|11\right\rangleの係数を表しています.

ここで出てきた結果を見てみると想定と結果が少しずれていることに気付きます.Generate Bell stateの方では,作成されているのは[ 0.70711-0.j 0. +0.j -0. +0.j 0.70711+0.j]の状態で,これは \frac{1}{\sqrt{2}}(\left|00\right\rangle + \left|11\right\rangle) なので正しい結果が出ているように見えます.一方Generate plus or minus stateの方では,[-0.-0.70711j 0.-0.70711j]となっており,これは\frac{1}{\sqrt{2}}(-j\left|0\right\rangle - j\left|1\right\rangle)となっており,\frac{1}{\sqrt{2}}(\left|0\right\rangle + \left|1\right\rangle)とは異なる状態が作成されているように見えます. \frac{1}{\sqrt{2}}(-j\left|0\right\rangle - j\left|1\right\rangle)を少し変形させると-j\frac{1}{\sqrt{2}}(\left|0\right\rangle + \left|1\right\rangle)となり,全体の位相が-jズレいている以外は想定通りとなっていることがわかります.この場合ズレているのが系全体の位相なので,基本的には想定していたものと同じ性質を示すのですが何故ズレが起きたのでしょうか.

どうやらこれはシミュレータの問題のようで,シミュレータがゲートを適用させていく時にグローバル位相を保存しないために起きる問題のようです.グローバル位相のズレのみなので,実際に測定などを行う際には全く問題にならないのですが,Cirq側でも問題は認識されていて修正中ではあるようです.

github.com

まとめ

今回はCirqを用いてQ#向けに作られた問題を解いてみました.Q#と比較してみると,Q#の方が書いてるコードの裏で実際に量子コンピュータ(のシミュレータ)が動いている感じがあるのに対して,Cirqの方は書いたコードを量子コンピュータ(のシミュレータ)に投げて実行してもらい,その結果を利用して何かするといった感じでした(あくまでも個人の感想である上にあまりうまく表現出来てませんが).

今回は簡単な量子状態を作成するコードを書いただけなのでそれほど大きな違いは感じませんでしたが,もう少し複雑なアルゴリズムを記述していった時にどういった違いがあるかという部分は気になるところなので,次はもう少し色々な操作が必要になるアルゴリズムを書いて比較してみたいです.