Nobisuke
Dekisugi
RAG
Privacy policy
2024/11/10 01:09
あんまり本業には関係ないのですが、光量子コンピュータが出たというニュースが出ましたので、測定型量子計算についてちょっと趣味で調べてみたいと思います。完全に想像でやってるので、間違っていたら専門の方、ぜひ指摘をお願いします。
今回の光量子コンピュータは光連続量計算ということなので、本来は別のライブラリを使うべきなのかもしれませんが、ビットに慣れている人が多いのと、原理的には連続量での位置と運動量の測定に直せばいいので、とりあえず今回は量子ビットで試してみたいと思います。光連続量プログラミングを学びたい方はXanaduのツールなどをみてくださいませ。
また、原理を学びたい人は
こちらにも多少永井くんのブログをトレースしたものがあります。
また、本家の解説記事が一番わかりやすいと思いました。
今回は原理を学びたいので、QiskitとシミュレータであるQiskit Aerを利用します。
pip install qiskit pylatexenc qiskit_aer
最初にツールを読み込んで量子回路を作ります。まずはクラスタ状態を作ってみます。
まずは2量子ビットで行ってみます。
クラスタ状態はHゲートを適用した後にCZを接続したい量子ビット間に繋ぎます(多分)。
from qiskit_aer import AerSimulator
from qiskit import QuantumCircuit
from qiskit.visualization import plot_histogram, plot_state_city
# 量子回路を作成
qc = QuantumCircuit(2)
qc.h(0)
qc.h(1)
qc.cz(0, 1)
# 状態ベクトルを取得するために準備
qc.save_statevector()
# 状態ベクトルを取得してから測定(出ないと状態が確定してしまう)
qc.measure_all()
ポイントは今回は状態ベクトルを確認したいのですが、測定のゲートの前に状態ベクトルを保存しておかないと状態が確定してしまいます。
状態ベクトルを保存したら測定を入れます。最初に量子回路ができたかどうか確認します。
# 量子回路を確認のために記述
display(qc.draw(output='mpl'))
丁寧に状態ベクトルを保存した位置も量子回路上に表示してくれました。
早速計算をして確認してみます。
# 今回はシミュレータを利用して状態ベクトルとサンプルの両方を取ります
sim = AerSimulator()
# 計算結果
result = sim.run(qc).result()
# 計算結果からサンプルのカウントを取得
counts = result.get_counts()
# 計算結果から状態ベクトルを取得
statevector = result.get_statevector()
# 結果をテキストで表示
print("Measurement Counts:", counts)
print("State Vector:", statevector)
# サンプルのカウントはヒストグラムでも一応確認
display(plot_histogram(counts))
結果をまずテキストから見てみます。
Measurement Counts: {'11': 251, '00': 268, '10': 271, '01': 234}
State Vector: Statevector([ 0.5+0.j, 0.5+0.j, 0.5+0.j, -0.5+0.j], dims=(2, 2))
カウントの方はヒストグラムで確認できますが、重ね合わせ状態になっていて00から11まで満遍なく出ています。
状態ベクトルの方も重ね合わせになっています。|11>の状態だけマイナスがついているのが確認できます。
念の為ヒストグラムを確認します。
次に4量子ビットに増やして同じことをしてみます。グラフ状態は1次元を作ってみます。隣接する量子ビットに順番にCZをかけてみます。
# 4量子ビット量子回路を作成
qc = QuantumCircuit(4)
qc.h(0)
qc.h(1)
qc.h(2)
qc.h(3)
qc.cz(0, 1)
qc.cz(1, 2)
qc.cz(2, 3)
# 状態ベクトルを取得するために準備
qc.save_statevector()
# 状態ベクトルを取得してから測定(出ないと状態が確定してしまう)
qc.measure_all()
# 量子回路を確認のために記述
display(qc.draw(output='mpl'))
# 今回はシミュレータを利用して状態ベクトルとサンプルの両方を取ります
sim = AerSimulator()
result = sim.run(qc).result()
counts = result.get_counts()
statevector = result.get_statevector()
# Print the results
print("Measurement Counts:", counts)
print("State Vector:", statevector)
# Plot the histogram and state vector
display(plot_histogram(counts))
Measurement Counts: {'0000': 65, '1101': 64, '0011': 58, '0111': 78, '1011': 64, '0100': 68, '0110': 57, '1000': 62, '1100': 64, '1110': 67, '1001': 67, '0101': 59, '0010': 68, '1010': 59, '1111': 59, '0001': 65}
State Vector: Statevector([ 0.25+0.j, 0.25+0.j, 0.25+0.j, -0.25+0.j, 0.25+0.j,
0.25+0.j, -0.25+0.j, 0.25-0.j, 0.25+0.j, 0.25+0.j,
0.25+0.j, -0.25+0.j, -0.25+0.j, -0.25+0.j, 0.25-0.j,
-0.25+0.j],
dims=(2, 2, 2, 2))
いい感じ(?)ですかね。
ちなみにちょっと興味があったので、CZの計算順序を変更したら状態が変わるのかを確認してみます。途中の、
qc.cz(0, 1)
qc.cz(1, 2)
qc.cz(2, 3)
を
qc.cz(0, 1)
qc.cz(2, 3)
qc.cz(1, 2)
のように、2番目と3番目のCZを交換してみます。計算上は変わらないはずですが、
見た目はこっちの方がかっこいいですね。
「交換した後」
State Vector: Statevector([ 0.25+0.j, 0.25+0.j, 0.25+0.j, -0.25+0.j, 0.25+0.j,
0.25+0.j, -0.25+0.j, 0.25-0.j, 0.25+0.j, 0.25+0.j,
0.25+0.j, -0.25+0.j, -0.25+0.j, -0.25+0.j, 0.25-0.j,
-0.25+0.j],
dims=(2, 2, 2, 2))
「交換する前」
State Vector: Statevector([ 0.25+0.j, 0.25+0.j, 0.25+0.j, -0.25+0.j, 0.25+0.j,
0.25+0.j, -0.25+0.j, 0.25-0.j, 0.25+0.j, 0.25+0.j,
0.25+0.j, -0.25+0.j, -0.25+0.j, -0.25+0.j, 0.25-0.j,
-0.25+0.j],
dims=(2, 2, 2, 2))
状態ベクトルも特に符号を含めて変化はなさそうです。
では、早速測定を通じて量子計算をしてみます。4量子ビットでいきなり大きくすると大変なので、2量子ビットに戻してみます。
# 量子回路を作成
# 量子回路の準備には2量子ビットと2つの古典レジスタ(結果の格納)を準備
qc = QuantumCircuit(2,2)
qc.h(0)
qc.h(1)
qc.cz(0, 1)
# 0番目だけを測定してみる
#qc.measure_all()
qc.measure([0], [0])
# 片方の測定を確定した状態で状態ベクトルを取得
qc.save_statevector()
# 量子回路を確認のために記述
display(qc.draw(output='mpl'))
# 今回はシミュレータを利用して状態ベクトルとサンプルの両方を取ります
sim = AerSimulator()
result = sim.run(qc).result()
statevector = result.get_statevector()
# Print the results
print("State Vector:", statevector)
行っていることは、0番目の量子ビットのみを測定して、1番目の量子ビットは測定をしません。
その上で、状態ベクトルが変化するかをみてみます。
量子回路は下記のようになりました。
計算結果は、
State Vector: Statevector([ 0.70710678+0.j, 0. +0.j, 0.70710678+0.j,
-0. +0.j],
dims=(2, 2))
となっています。こちらは、
|00>と|10>のもつれ(重ね合わせ)状態ですね。Qiskitの状態ベクトルや古典レジスタの格納順番はおそらく0番目の量子ビットが右側なので、0番目が0に確定した状態で、1番目の量子ビットが0か1になるような重ね合わせ状態になっています。
この状態ですと、量子ビットq1だけに注目すると、|psi> = H|0> を実行したのと同じことになっています。
これで終わりではなく、何回か同じ量子回路を計算してみると、計算結果が、
State Vector: Statevector([ 0. +0.j, 0.70710678+0.j, 0. +0.j,
-0.70710678+0.j],
dims=(2, 2))
のように状態ベクトルが変化します。これは当然で、0番目の量子ビットが1になった時には、全体の量子状態は変化します。
この場合には、
|01>と|11>の量子ビットのもつれになっています。0番目の量子ビットが|1>に確定していますので、1番目の量子ビットは同じように|0>と|1>の重ね合わせになっています。
ただ、よくみると先ほどのようにはいかなさそうです。
|01>の確率振幅は0.70710678+0.jなのでいいのですが、
|11>の確率振幅は-0.70710678+0.jになっていて符号が反転しています。
ですので、0番目の量子ビットが1の場合には、1番目の量子ビットの確率振幅の実部を反転させる必要があります。
Zゲートをかけると、
State Vector: Statevector([ 0. +0.j, 0.70710678+0.j, -0. +0.j,
0.70710678-0.j],
dims=(2, 2))
となり、同じになりました。
ここでちょっと気になりましたが、初期のクラスタ状態ではCZを使っているので、初期の状態ベクトルによって確率振幅の符号の状態が変わる気がします。HとCZを使って2量子ビットのクラスタ状態を作った際の状態ベクトルは、
State Vector: Statevector([ 0.5+0.j, 0.5+0.j, 0.5+0.j, -0.5+0.j], dims=(2, 2))
のように、|11>状態にはどうしてもマイナスがついてしまいます。
次に1番目の量子ビットを測定してから0番目の量子ビットの状態ベクトルを見てみます。
状態は、
State Vector: Statevector([ 0.70710678+0.j, 0.70710678+0.j, 0. +0.j,
-0. +0.j],
dims=(2, 2))
と
State Vector: Statevector([ 0. +0.j, 0. +0.j, 0.70710678+0.j,
-0.70710678+0.j],
dims=(2, 2))
の両方が出てきました。
前者は|00>と|01>のもつれ(重ね合わせ)
後者は|10>と|11>のもつれ(重ね合わせ)
です。1番目の量子ビットの値によってやはり値が変わりました。
特に何も考えないで、1番目の量子ビットが1の時には0番目の量子ビットに0をかけると、
State Vector: Statevector([ 0. +0.j, -0. +0.j, 0.70710678+0.j,
0.70710678-0.j],
状態ベクトルの値も変わりなくできました。
次に任意の量子状態をテレポートするために、適当に作ってみます。状態べくとるは測定前に入れることを忘れないでください。
# 量子回路を作成
qc = QuantumCircuit(1)
qc.rx(0.5,0)
qc.rz(0.5,0)
# 片方の測定を確定した状態で状態ベクトルを取得
qc.save_statevector()
# 1番目だけを測定してみる
qc.measure_all()
State Vector: Statevector([0.93879128-0.23971277j, 0.06120872-0.23971277j],
dims=(2,))
次に、この量子状態を使ってテレポートをしてみます。
量子テレポーテーション回路のベル測定部分をCXからH-CZ-Hに変更します。
# 量子回路を作成
qc = QuantumCircuit(2,2)
qc.rx(0.5,0)
qc.rz(0.5,0)
qc.h(1)
qc.cz(0,1)
qc.h(0)
# 0番目だけを測定してみる
#qc.measure_all()
qc.measure([0], [0])
# テレポート後の量子状態に再度アダマール
qc.h(1)
# 片方の測定を確定した状態で状態ベクトルを取得
qc.save_statevector()
# 量子回路を確認のために記述
display(qc.draw(output='mpl'))
# 今回はシミュレータを利用して状態ベクトルとサンプルの両方を取ります
sim = AerSimulator()
result = sim.run(qc).result()
statevector = result.get_statevector()
# Print the results
print("State Vector:", statevector)
得られた量子状態は、
State Vector: Statevector([0.93879128-0.23971277j, 0. +0.j ,
0.06120872-0.23971277j, 0. +0.j ],
dims=(2, 2))
と、
State Vector: Statevector([ 0. +0.j , 0.93879128-0.23971277j,
0. +0.j , -0.06120872+0.23971277j],
dims=(2, 2))
が出てきました。前者は|00>と|01>のもつれ、後者は|10>と|11>のもつれです。
q0が1の時に符号を訂正したいと思います。
H-CZ-Hと軸が変わっているので、Zではなく、Xで訂正します。
State Vector: Statevector([0. +0.j , 0.93879128-0.23971277j,
0. +0.j , 0.06120872-0.23971277j],
dims=(2, 2))
見事に量子状態が変わりましたね。量子状態を別の量子ビットに移行できました。
最後は計算というより単に量子状態を移しただけになりましたが、間に量子計算を入れれば、計算しながら移動できそうです。
ちょっと疲れたので、2量子ビットで一旦記事を閉じますが、この操作を連続して順番にすることで、3から4量子ビットと量子状態を移しながら測定を通じて計算ができそうです。
今回は量子ビットで行いましたが、これをビットではなくモードにして、測定のフィードバックをxとかpの物理量に変更すれば光連続量の測定型ができそうです。
おじさんなので体力がないです。
© 2024, blueqat Inc. All rights reserved