進捗のようなもの

やったこと書きます

IBM Quantum Challenge 2020 (4th anniversary) 振り返りEx01~Ex03編

私が参加した、 IBM Quantum Challengeの課題について今回は振り返って行きたいと思います。

まずはIBM Quantum Challengeの概要

IBM Quantum Experienceの4周年を記念して2020年5月4日~8日の4日間でハッカソンが開催されていました。
ハッカソンページ(終了したので、閉じられています)
quantum-computing.ibm.com
ハッカソン結果報告
www.ibm.com
結果報告によると、45カ国から1745人の参加者が集まり、IBM Cloud上で1日あたり10億の回路が実行されていたみたいです。(スゴイ!)


\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}}

課題のノートブックを準備

開催時はIBM Quantum Experience上にJupyterNotebookファイルが準備されていて、簡単に課題に取り組めました。開催終了後にNotebookがIBM Quantum Experienceからなくなってしまったので、課題のノートブックを準備します。

今回はでローカルでやる方法を紹介します。 (クラウド上のIBM Qiskit Notebooksでもできると思いますが、私の場合、実機backendが認識できなくて断念しました。)

①課題ノートブックの入手

ここから入手します。
例えば、右上の方にある【Clone or download】の【Download】から入手します。

②必要なパッケージのインストール

↓のパッケージをインストールします。 github.com

①で入手したノートブックをJupyterNotebook等で開いて、一番上にセルを挿入し、

%pip install -I git+https://github.com/qiskit-community/may4_challenge.git@0.4.30

をペーストして実行します。may4_challengeをimportできることを確認して準備完了です。

Ex01 Introduction to Qiskit

Exercise I: Basic Operations on Qubits and Measurements

ノートブックは「Challenge1_BasicQuantumCircuits.ipynb」です。
Ex01はQiskitの入門編です。Qiskitや量子回路に慣れることが目的です。qsphereを用いて量子状態を可視化しながら学びます。

量子ゲートによる量子状態の操作については、↓の画像がわかりやすいですね。 quantum gates

I.i) ビットフリップ

最初の問題は\ket{0}\ket{1}の量子操作です。
上の画像を見ればXゲートを使うことがわかります。

# Cell 3
def create_circuit():
    qc = QuantumCircuit(1)
    qc.x(0)
    return qc

# check solution
qc = create_circuit()
state = statevec(qc)
check1(state)
plot_state_qsphere(state.data, show_state_labels=True, show_state_angles=True)
Correct 🎉! Well done!

I.ii)重ね合わせ状態 \ket{+} =  \frac{1}{\sqrt{2}}\left(\ket{0} + \ket{1}\right)

重ね合わせ状態\ket{+}H(Hadamard)ゲートで作れます。

# Cell 4
def create_circuit2():
    qc = QuantumCircuit(1)
    qc.h(0)
    return qc

qc = create_circuit2()
state = statevec(qc)
check2(state)
plot_state_qsphere(state.data, show_state_labels=True, show_state_angles=True) 
Correct 🎉! Well done!

I.iii) 重ね合わせ状態\ket{-}= \frac{1}{\sqrt{2}}\left(\ket{0} - \ket{1}\right)

\ket{+} \ket{-}は位相が180度ズレた状態です。

 
\begin{aligned}
\require{color}
\ket{+} = \frac{1}{\sqrt{2}}\left(\ket{0} + \textcolor{red}{e^{i0}} \ket{1}\right)  =
 \frac{1}{\sqrt{2}}\left(\ket{0} + \ket{1}\right) \\
\ket{-} = \frac{1}{\sqrt{2}}\left(\ket{0} + \textcolor{red}{e^{i\pi}} \ket{1}\right)  = 
 \frac{1}{\sqrt{2}}\left(\ket{0} - \ket{1}\right)
\end{aligned}

Hゲートで\ket{+}を作ってZゲートで位相を180度回します。

# Cell 5
def create_circuit3():
    qc = QuantumCircuit(1)
    qc.h(0)
    qc.z(0)
    return qc

qc = create_circuit3()
state = statevec(qc)
check3(state)
plot_state_qsphere(state.data, show_state_labels=True, show_state_angles=True) 
Correct 🎉! Well done!

I.iv) 位相に複素数\ket{\circlearrowleft}  = \frac{1}{\sqrt{2}}\left(\ket{0} - i\ket{1}\right)

最後は、位相に複素数が入る量子状態 \ket{\circlearrowleft}です。
今度は\ket{+}から位相が270度(-90度)ズレた量子状態です。

 
\begin{aligned}
\require{color}
\ket{+} &= \frac{1}{\sqrt{2}}\left(\ket{0} + \textcolor{red}{e^{i0}} \ket{1}\right)  =
 \frac{1}{\sqrt{2}}\left(\ket{0} + \ket{1}\right) \\
\ket{\circlearrowleft} &= \frac{1}{\sqrt{2}}\left(\ket{0} + \textcolor{red}{e^{i\frac{3}{2}\pi}} \ket{1}\right)  = 
 \frac{1}{\sqrt{2}}\left(\ket{0}+\left(\cos{\frac{3}{2}\pi}+i\sin{\frac{3}{2}\pi}\right) \ket{1}\right) \\
&= \frac{1}{\sqrt{2}}\left(\ket{0} - i\ket{1}\right) 
\end{aligned}

HゲートをかけてS^\daggerゲートで位相を-90度回します。

# Cell 6
def create_circuit4():
    qc = QuantumCircuit(1)
    qc.h(0)
    qc.sdg(0)
    return qc

qc = create_circuit4()
state = statevec(qc)
check4(state)
plot_state_qsphere(state.data, show_state_labels=True, show_state_angles=True)
Correct 🎉! Well done!

Exercise II: Quantum Circuits Using Multi-Qubit Gates

続いて、複数の量子ビットで動作するゲートを見ていきます。

II.i) ベル状態\ket{\Phi^{+}}=\frac{1}{\sqrt{2}}\left(\ket{00} + \ket{11}\right)

 \ket{00} \overset{\text{H}}{\longrightarrow} \frac{1}{\sqrt{2}}\left(\ket{00} + \ket{01}\right)\overset{\text{CX(0,1)}}{\longrightarrow} \frac{1}{\sqrt{2}}\left(\ket{00} + \ket{11}\right) のようにすればOKですね。

# Cell 8
def create_circuit():
    qc = QuantumCircuit(2)
    qc.h(0)
    qc.cx(0,1)
    return qc

qc = create_circuit()
state = statevec(qc) # determine final state after running the circuit
display(Math(vec_in_braket(state.data)))
check5(state)
qc.draw(output='mpl') # we draw the circuit
Correct 🎉! Well done!

II.ii) ベル状態\ket{\Psi^{-}}=\frac{1}{\sqrt{2}}\left(\ket{01} - \ket{10}\right)

\ket{\Psi^{-}}は、\ket{11}を用意して前の問題と同様のHゲート、CXゲートで作ることが知られています。

# Cell 9
def create_circuit6():
    qc = QuantumCircuit(2,2) # this time, we not only want two qubits, but also 
    qc.x(0)
    qc.x(1)
    qc.h(0)
    qc.cx(0,1)
    return qc

qc = create_circuit6()
state = statevec(qc) # determine final state after running the circuit
display(Math(vec_in_braket(state.data)))
check6(state)
qc.measure(0, 0) # we perform a measurement on qubit q_0 and store the information on the classical bit c_0
qc.measure(1, 1) # we perform a measurement on qubit q_1 and store the information on the classical bit c_1
qc.draw(output='mpl') # we draw the circuit
Correct 🎉! Well done!

II.iii) スワップゲート

量子状態の交換はSWAPゲートを使えばイケます。
SWAPゲート(0,1)はCXゲート(0,1),CXゲート(1,0),CXゲート(0,1)と等価であることも知られています。
【参考】EMANの物理学・量子力学・2入力量子ゲート

# Cell 11
def create_circuit7():
    qc = QuantumCircuit(2)
    qc.rx(np.pi/3,0)
    qc.x(1)
    return qc

qc = create_circuit7()
qc.swap(0,1)
state = statevec(qc) # determine final state after running the circuit
display(Math(vec_in_braket(state.data)))
check7(state)
plot_state_qsphere(state.data, show_state_labels=True, show_state_angles=True) 
Correct 🎉! Well done!

II.iv) GHZ状態

GHZ(Greenberger–Horne–Zeilinger)状態は、3qubitがもつれた状態です。
ググると、 \ket{\text{GHZ}} = \frac{1}{\sqrt{2}} \left(\ket{000}+\ket{111}  \right) \ket{\text{GHZ}} = \frac{1}{\sqrt{2}} \left(\ket{000}-\ket{111}  \right)の2種類ありますが、今回は測定しちゃうのでどちらでも大丈夫だと思います。

qiskit.org ↑を参考に回路を作ります。測定回数を2000回にするのを忘れずに

# Cell 12
qc = QuantumCircuit(3,3) # this time, we not only want two qubits, but also 
qc.h(0)
qc.cx(0,1)
qc.cx(0,2)

qc.measure(0, 0)
qc.measure(1, 1)
qc.measure(2, 2)

def run_circuit(qc):
    backend = Aer.get_backend('qasm_simulator') # we choose the simulator as our backend
    result = execute(qc, backend, shots = 2000).result() # we run the simulation
    counts = result.get_counts() # we get the counts
    return counts

counts = run_circuit(qc)
print(counts)
check8(counts)
plot_histogram(counts)
{'000': 971, '111': 1029}
Correct 🎉! Well done!

これで、Ex01は終了です!

Ex02 Measurement Error Mitigation

ノートブックは「Challenge2_MeasurementErrorMitigation.ipynb」です。
Error Mitigationのトピックスですね。現在の量子コンピュータまだノイズが大きいため、ノイズを抑えるて真の値に近づけます。
ここでは特に、測定した結果に乗るノイズを補償することに注目しています。
あらかじめ使用するnqubitの2^{n}基底状態を測定することで、ノイズの影響度を表す行列Mを取得しておきます。 ノイズの乗った測定結果は [tex:\ket{\psi{noisy}} = M\ket{\psi}] と書くことできます。この式から分かるように、実際に測定された[tex:\ket{\psi{noisy}}]にM逆行列をかけることで、理想的な結果\ket{\psi}に近い値を得ることができます。ただし注意したいのは、通常M行列は非可逆行列であり、ノイズはゆらぎを持っているため、完全には理想的な結果は得られないことです。

例えば、'ibmq_london'で'the measurement calibration circuits'を測定(8192shots)すると次のような結果が得られました。 f:id:SJSY:20200516223333p:plain
対角線上にプロットが正しい結果で、それ以外のプロットがエラーとなっています。うっすらプロットがあることが確認できます。

i) 1つ目

赤がノイズが乗ったヒストグラム青が'ibmq_london'の読み出しエラーを補正したヒストグラムです。 f:id:SJSY:20200516223952p:plain
上の結果から理想的なカウントを表しているのは\ket{00000}\ket{11111}が50%、50%のヒストグラムである(c)が正解になります。

ii) 2つ目

赤がノイズが乗ったヒストグラム青が'ibmq_london'の読み出しエラーを補正したヒストグラムです。 f:id:SJSY:20200516225002p:plain
上の結果から理想的なカウントを表しているのは\ket{00000}\ket{11111}が0.385%、0.615%のヒストグラムである(d)が正解になります。

iii) 3つ目

赤がノイズが乗ったヒストグラム青が'ibmq_london'の読み出しエラーを補正したヒストグラムです。 f:id:SJSY:20200516225445p:plain 上の結果から8本ヒストグラムが立っているものが正解と考えられ、(b)となります。

iv) 4つ目

赤がノイズが乗ったヒストグラム青が'ibmq_london'の読み出しエラーを補正したヒストグラムです。 f:id:SJSY:20200516225756p:plain
上の結果から、右に段々で上がっている様子が一致する(b)が正解になります。

これで、Ex02は終了です!

Ex03 Quantum Cryptography

ノートブックは「Challenge3_BB84.ipynb」です。
量子暗号のトピックスですね。Charles BennettとGilles Brassardが1984年に開発したプロトコルBB84を扱います。二人は今でもIBM Quantumチームのメンバーだそうです。

BB84

BB84は送信者のAliceと受信者のBobとの間で秘密鍵を作成し、それを用いてメッセージの暗号化/復号化を行う量子鍵配送プロトコルです。 BB84プロトコルをステップごとに見ていきます。

ステップ1

Aliceはkbの2つのランダムなビット配列を生成します。kには暗号化したいビットが含まれていて、この配列から秘密鍵が作られます。一方、bは伝送に用いる基底の選択に使用します。ここでは、b_i=0の場合は\ket{0},\ket{1}の基底、b_i=1の場合は\ket{+},\ket{-}の基底を使います。
図に示すと下のようになります。

drawing

ステップ2

Aliceはnビットをステップ1の方法でエンコードして、Bobに送信します。Bobはnビットのランダムな配列\tilde{b}を生成し、その配列に従い測定する基底を決定します。測定した結果\tilde{k_i}と使用した基底\tilde{b_i}を記録しておきます。

ステップ3

Aliceの基底b_iとBobの基底\tilde{b_i}を比較してb_i \neq \tilde{b_i}の場合、AliceとBobの異なる基底で測定しているため、測定結果を破棄します。一方、b_i = \tilde{b_i}ならば、2人ともに同じ基底で測定したので、Aliceがエンコードしたqubit\tilde{k_i}=k_iを得ることができます。この結果が秘密鍵となります。
AliceとBobは互いに異なる基底をランダムに選択してるため、基底を一致させて測定できる確率は\frac{1}{2}になります。しかし、AliceとBobの通信の間に盗聴者Eveがいる場合、Aliceと完全に一致した基底をEveが選択しない限り、測定結果の一致率が\frac{1}{2}ではなくなるためEveの存在を検出することができます。

drawing

上の図はAliceのランダムビット配列がk='0111001'、b='1101000'で、Bobのランダムビット配列が\tilde{b}='1001101'とした場合のBB84プロトコルを図示したものです。秘密鍵'0110'を得ることができました。AliceとBobの一致率も50%なので盗聴者はいなさそうです。

以上が、BB84プロトコルによる量子鍵配送の流れになります。
演習ではBobになって、Aliceからの暗号メッセージを解読します。

i) Execute step 2 of the BB84 protocol to get your bitstring

まずは、測定に使用するランダムビット配列\tilde{b}を生成します。続いて、\tilde{b}に対応する基底で測定を行い結果を返すプログラムを作成します。
b_i=0の場合は\ket{0},\ket{1}の基底でそのまま測定、b_i=1の場合は\ket{+},\ket{-}の基底なのでHゲートをかけてから測定すればOKです。

%matplotlib inline

# Importing standard Qiskit libraries
import random
from qiskit import execute, Aer, IBMQ
from qiskit.tools.jupyter import *
from qiskit.visualization import *
from may4_challenge.ex3 import alice_prepare_qubit, check_bits, check_key, check_decrypted, show_message

# Configuring account
provider = IBMQ.load_account()
backend = provider.get_backend('ibmq_qasm_simulator')  # with this simulator it wouldn't work \

# Initial setup
random.seed(84) # do not change this seed, otherwise you will get a different key

# This is your 'random' bit string that determines your bases
numqubits = 100
bob_bases = str('{0:0100b}'.format(random.getrandbits(numqubits)))

def bb84():
    print('Bob\'s bases:', bob_bases)

    # Now Alice will send her bits one by one...
    all_qubit_circuits = []
    for qubit_index in range(numqubits):

        # This is Alice creating the qubit
        thisqubit_circuit = alice_prepare_qubit(qubit_index)

        # This is Bob finishing the protocol below
        bob_measure_qubit(bob_bases, qubit_index, thisqubit_circuit)

        # We collect all these circuits and put them in an array
        all_qubit_circuits.append(thisqubit_circuit)
    
    # Now execute all the circuits for each qubit
    results = execute(all_qubit_circuits, backend=backend, shots=1).result()

    # And combine the results
    bits = ''
    for qubit_index in range(numqubits):
        bits += [measurement for measurement in results.get_counts(qubit_index)][0]
        
    return bits

def run_circuit(qc):
    backend = Aer.get_backend('qasm_simulator') # we choose the simulator as our backend
    result = execute(qc, backend, shots = 1000).result() # we run the simulation
    counts = result.get_counts() # we get the counts
    return counts



# Here is your task
def bob_measure_qubit(bob_bases, qubit_index, qubit_circuit):
    base = bob_bases[qubit_index]
    
    #基底baseで場合分け
    if base == '0':
        qubit_circuit.measure(0, 0)
    else:
        qubit_circuit.h(0)
        qubit_circuit.measure(0, 0)
    ...

bits = bb84()        
print('Bob\'s bits: ', bits)
check_bits(bits)
Bob's bases: 1100111010011111111111110100000111010100100010011001001110100001010010111011010001011001111111011111
Bob's bits:  1000101010010100001110101011000101100100000011101001100010000111001010001110000111000001100000001001
So far, so good 🎉! You got the right bits!

ii) Execute step 3 of the BB84 protocol to extract the key

次は秘密鍵を取り出します。Aliceの基底bが与えられるのでBobの基底\tilde{b}と1ビットずつ比較して、一致しているビットのみ測定結果\tilde{k}から取り出したものが鍵です。

alice_bases = '10000000000100011111110011011001010001111101001101111110001100000110000010011000111'\
              '00111010010000110' # Alice's bases bits

key=''
for qubit_index in range(numqubits):
    a_base = alice_bases[qubit_index]
    b_base = bob_bases[qubit_index]
    
    #基底が一致している時のみ取り出す
    if a_base == b_base:
        key+=bits[qubit_index]
        
print(key)

check_key(key)
10000010001110010011101001010000110000110011100000
So far, so good 🎉! You got the right key!

iii) Decrypt the message using the extracted key

得られた鍵を使って、Ailceの暗号化されたメッセージを解読します。今回は200ビット長のメッセージを暗号化するために50ビット長の鍵を4回連続で使いました。(完全なセキュリティのためには鍵を使うのは一度のみですが)
Aliceは秘密鍵を使ってワンタイムパッドによる暗号化を行いました。
例えば、\text{key} = '0110'、送信したいメッセージのビット配列m='1100'のとき、暗号化されたメッセージcc = m \oplus \text{key}\mod{2} = \text{'1010'} になります。Bobは暗号化されたメッセージに鍵を加えることで復号化することができます。 m = c \oplus \text{key}\mod{2}

m = '0011011010100011101000001100010000001000011000101110110111100111111110001111100011100101011010111010111010001'\
    '1101010010111111100101000011010011011011011101111010111000101111111001010101001100101111011' # encrypted message
# 鍵は50ビット長を4回使って、200ビット長に
keys = key*4

decrypted = ''
# 1ビットずつ取り出す
for i in range(len(m)):
    mb = m[i]
    kb = keys[i]
    
    # mod2の場合分け(もっとうまい方法はあると思う)
    if (mb == '0' and kb == '0') or  (mb == '1' and kb == '1'):
        decrypted += '0'
    elif (mb == '1' and kb == '0') or  (mb == '0' and kb == '1'):
        decrypted += '1'
print(decrypted)
check_decrypted(decrypted)
10110100100110101001101010010100110010110101101011001101011010011011011001101100110101011010010110100110101011010011011001011001101011011001010101011001101101011001010110010110011010011001010110011011
So far, so good 🎉! You decrypted the message!

iv) Try to decode Alice's message!

Aliceはメッセージをモールス信号を使ってビット配列にエンコードしていたようです。

  • dot(・)は'1'
  • dash(ー)は'11'
  • dotとdashの区切りは'0'
  • 一文字の区切りは'00'
  • 単語の区切りは'000'

というエンコードルールになっています。

drawing

では、Aliceのメッセージをデコードします。

MORSE_CODE_DICT = { 'a':'.-', 'b':'-...', 
                    'c':'-.-.', 'd':'-..', 'e':'.', 
                    'f':'..-.', 'g':'--.', 'h':'....', 
                    'i':'..', 'j':'.---', 'k':'-.-', 
                    'l':'.-..', 'm':'--', 'n':'-.', 
                    'o':'---', 'p':'.--.', 'q':'--.-', 
                    'r':'.-.', 's':'...', 't':'-', 
                    'u':'..-', 'v':'...-', 'w':'.--', 
                    'x':'-..-', 'y':'-.--', 'z':'--..', 
                    '1':'.----', '2':'..---', '3':'...--', 
                    '4':'....-', '5':'.....', '6':'-....', 
                    '7':'--...', '8':'---..', '9':'----.', 
                    '0':'-----', ', ':'--..--', '.':'.-.-.-', 
                    '?':'..--..', '/':'-..-.', '-':'-....-', 
                    '(':'-.--.', ')':'-.--.-'} 
#words = decrypted.split('000') #今回は単語の区切りがなかったのでパス
#文字ごとに分ける
chars = decrypted.split('00')

de_chars = []
#一文字ずつデコードする。
for char in chars:
    codes = char.split('0')
    de_codes = []
    for code in codes:
        if code == '1':
            de_codes.append('.')
        elif code == '11':
            de_codes.append('-')
            
    mcode = ''.join(de_codes)
    de_chars.append([k for k, v in MORSE_CODE_DICT.items() if v == mcode][0])
         
solution=''.join(de_chars)
print(solution)
show_message(solution)
reddit.com/r/may4quantum

redditのurlが表示されました。リンクに飛んで見るとクリアした参加者たちが投稿していますね。次の問題であるEx4に対する議論もされています。

これで、Ex03は終了です!問題の導入も丁寧で初心者でも解きやすかったと思います。最後にredditのリンクが出てくるギミックも面白いですね。

Ex04は別記事にします

Ex03までで長くなってしまったのとEx04も更に長くなりそうなので、Ex04は別記事にします!

【参考】IBM Quantum Challenge 2019の記事も書いてます

sjsy.hatenablog.com

sjsy.hatenablog.com

sjsy.hatenablog.com

sjsy.hatenablog.com