IBM Quantum Challenge 2020 (4th anniversary) 振り返りEx01~Ex03編
私が参加した、 IBM Quantum Challengeの課題について今回は振り返って行きたいと思います。
- まずはIBM Quantum Challengeの概要
- 課題のノートブックを準備
- Ex01 Introduction to Qiskit
- Ex02 Measurement Error Mitigation
- Ex03 Quantum Cryptography
- Ex04は別記事にします
- 【参考】IBM Quantum Challenge 2019の記事も書いてます
まずは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億の回路が実行されていたみたいです。(スゴイ!)
課題のノートブックを準備
開催時は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
を用いて量子状態を可視化しながら学びます。
量子ゲートによる量子状態の操作については、↓の画像がわかりやすいですね。
I.i) ビットフリップ
最初の問題は→の量子操作です。
上の画像を見ればゲートを使うことがわかります。
# 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)重ね合わせ状態
重ね合わせ状態は(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) 重ね合わせ状態
とは位相が180度ズレた状態です。
ゲートでを作ってゲートで位相を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) 位相に複素数
最後は、位相に複素数が入る量子状態です。
今度はから位相が270度(-90度)ズレた量子状態です。
ゲートをかけてゲートで位相を-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) ベル状態
のようにすれば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) ベル状態
は、を用意して前の問題と同様のゲート、ゲートで作ることが知られています。
# 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) スワップゲート
量子状態の交換はゲートを使えばイケます。
ゲート(0,1)はゲート(0,1),ゲート(1,0),ゲート(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がもつれた状態です。
ググると、との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のトピックスですね。現在の量子コンピュータはまだノイズが大きいため、ノイズを抑えるて真の値に近づけます。
ここでは特に、測定した結果に乗るノイズを補償することに注目しています。
あらかじめ使用するqubitの基底状態を測定することで、ノイズの影響度を表す行列を取得しておきます。
ノイズの乗った測定結果は
[tex:\ket{\psi{noisy}} = M\ket{\psi}]
と書くことできます。この式から分かるように、実際に測定された[tex:\ket{\psi{noisy}}]にの逆行列をかけることで、理想的な結果に近い値を得ることができます。ただし注意したいのは、通常行列は非可逆行列であり、ノイズはゆらぎを持っているため、完全には理想的な結果は得られないことです。
例えば、'ibmq_london'
で'the measurement calibration circuits'を測定(8192shots)すると次のような結果が得られました。
対角線上にプロットが正しい結果で、それ以外のプロットがエラーとなっています。うっすらプロットがあることが確認できます。
i) 1つ目
赤がノイズが乗ったヒストグラム、青が'ibmq_london'
の読み出しエラーを補正したヒストグラムです。
上の結果から理想的なカウントを表しているのはとが50%、50%のヒストグラムである(c)が正解になります。
ii) 2つ目
赤がノイズが乗ったヒストグラム、青が'ibmq_london'
の読み出しエラーを補正したヒストグラムです。
上の結果から理想的なカウントを表しているのはとが0.385%、0.615%のヒストグラムである(d)が正解になります。
iii) 3つ目
赤がノイズが乗ったヒストグラム、青が'ibmq_london'
の読み出しエラーを補正したヒストグラムです。
上の結果から8本ヒストグラムが立っているものが正解と考えられ、(b)となります。
iv) 4つ目
赤がノイズが乗ったヒストグラム、青が'ibmq_london'
の読み出しエラーを補正したヒストグラムです。
上の結果から、右に段々で上がっている様子が一致する(b)が正解になります。
これで、Ex02は終了です!
Ex03 Quantum Cryptography
ノートブックは「Challenge3_BB84.ipynb」です。
量子暗号のトピックスですね。Charles BennettとGilles Brassardが1984年に開発したプロトコルのBB84を扱います。二人は今でもIBM Quantumチームのメンバーだそうです。
BB84
BB84は送信者のAliceと受信者のBobとの間で秘密鍵を作成し、それを用いてメッセージの暗号化/復号化を行う量子鍵配送プロトコルです。 BB84プロトコルをステップごとに見ていきます。
ステップ1
Aliceはとの2つのランダムなビット配列を生成します。には暗号化したいビットが含まれていて、この配列から秘密鍵が作られます。一方、は伝送に用いる基底の選択に使用します。ここでは、の場合はの基底、の場合はの基底を使います。
図に示すと下のようになります。
ステップ2
Aliceはビットをステップ1の方法でエンコードして、Bobに送信します。Bobはビットのランダムな配列を生成し、その配列に従い測定する基底を決定します。測定した結果と使用した基底を記録しておきます。
ステップ3
Aliceの基底とBobの基底を比較しての場合、AliceとBobの異なる基底で測定しているため、測定結果を破棄します。一方、ならば、2人ともに同じ基底で測定したので、Aliceがエンコードしたqubitを得ることができます。この結果が秘密鍵となります。
AliceとBobは互いに異なる基底をランダムに選択してるため、基底を一致させて測定できる確率はになります。しかし、AliceとBobの通信の間に盗聴者Eveがいる場合、Aliceと完全に一致した基底をEveが選択しない限り、測定結果の一致率がではなくなるためEveの存在を検出することができます。
上の図はAliceのランダムビット配列が'0111001'、'1101000'で、Bobのランダムビット配列が'1001101'とした場合のBB84プロトコルを図示したものです。秘密鍵'0110'を得ることができました。AliceとBobの一致率も50%なので盗聴者はいなさそうです。
以上が、BB84プロトコルによる量子鍵配送の流れになります。
演習ではBobになって、Aliceからの暗号メッセージを解読します。
i) Execute step 2 of the BB84 protocol to get your bitstring
まずは、測定に使用するランダムビット配列を生成します。続いて、に対応する基底で測定を行い結果を返すプログラムを作成します。
の場合はの基底でそのまま測定、の場合はの基底なのでゲートをかけてから測定すれば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の基底が与えられるのでBobの基底と1ビットずつ比較して、一致しているビットのみ測定結果から取り出したものが鍵です。
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は秘密鍵を使ってワンタイムパッドによる暗号化を行いました。
例えば、'0110'、送信したいメッセージのビット配列'1100'のとき、暗号化されたメッセージは
になります。Bobは暗号化されたメッセージに鍵を加えることで復号化することができます。
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'
というエンコードルールになっています。
では、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は別記事にします!