Qiskitを使った量子最適化アルゴリズムの応用へ 〜パート1 量子計算の基礎〜
*この記事はQiita記事の再投稿となります。
こんにちは!
株式会社アイディオットでデータサイエンティストをしています、秋田と申します。
このシリーズは、量子最適化アルゴリズムを具体的な問題に対して使うことによって、量子コンピュータのユースケースとしてどんなものがありそうかを妄想することを目的に、量子コンピュータの基礎から学ぼうというものになります。
その上で、IBM社が開発している量子コンピューティング用SDKのQiskitの使い方を学びながら、どのように量子計算が行われているのかを体験しましょう!
また参考までに、以前行った「初めてQiskitを使ってからQiskitで量子機械学習ができるようになるまで」シリーズも是非見てみてください(バージョン的に古いかもですが...)。
本記事と同様の実験をGoogle Colaboratoryで簡単に出来るように、このプロジェクトのGitHubリポジトリにノートブックを作成しました。
量子コンピュータとは
量子コンピュータのオリジン
そもそも量子コンピュータとは、一体どのようなコンピュータなのでしょうか。
量子コンピュータの対比として、従来のコンピュータを古典コンピュータなどと言いますが、簡単に言うと「量子力学の理論に基づいたコンピュータ」のことです。
物理学の世界では、ニュートンが落ちるリンゴを見て運動方程式を見つけたような古典力学の他に、ミクロな世界でのみ適用されるような説明が難しい現象を取り扱う量子力学というものがあります。
これらは自然現象を解明するために日々研究されている分野になりますが、同時にコンピュータというものも自然を模倣するように考えられています。
古典コンピュータが古典力学を反映させているのだとしたら、量子力学に対応するようなコンピュータがあっても良いだろうという考えから生み出されたのが量子コンピュータです。
量子コンピュータは万能なの?
ここで何となく想像していただきたいのですが、古典力学では説明がつかないようなことが量子力学では起きているということは、あくまでも古典力学でカバーしきれない理論を量子力学で補っているという関係があり、それをそのまま古典・量子コンピュータの方にも持ってきています。
すなわち、量子コンピュータが古典コンピュータの完全上位互換ということではなく、古典コンピュータでは難しかったことでも量子コンピュータなら計算できるかもしれないと仮説を立てるレベルの話になります。
実際に、量子コンピュータにも得意・不得意な計算は存在し、もっと言うとどんな分野で量子計算が役に立つのかということがほとんど分かっていないのが現状です。
そこで、最適化の分野で量子コンピュータの優位性が見られるのではないかと研究が盛んに行われているのですが、このシリーズのゴールとしては、その最適化の分野における量子コンピュータのユースケースが無いかを探ることになります!
量子計算
量子コンピュータで行う計算を「量子計算」と言います。
量子計算を行うための「ビット」に相当するのが「量子ビット」です。
その量子ビットの状態を変化させるのに必要なのが「量子回路」です。
量子ビット
古典コンピュータでは、ビットは
一方で、量子コンピュータでは、量子状態
重ね合わせ状態とは、次のような線型結合した状態を表します。
ここで、
まずは簡単な量子状態
つまり、
よって、一般的な量子状態
さらに、

では、
量子状態
ここで、
テンソル積の計算は次のようなものになります。
このとき、ベクトルの次数は
量子回路
量子回路は、様々な量子ゲートを組み合わせて量子状態を変化させるための一連の流れを図式化したものになります。
まずはどのような量子ゲートがあるかを見てみましょう。
- パウリゲート
-
ゲートX -
ゲートY -
ゲートZ -
ゲートI
-
- アダマールゲート
- 回転ゲート
-
ゲートR_x -
ゲートR_y -
ゲートR_z
-
まずはパウリゲートですが、それぞれ次のように定義されます。
これらの量子ゲートをパウリゲートと言い、それぞれ量子状態
続いて、アダマールゲート(
ここまでに定義された5種類の量子ゲートは、定義から次のことが言えます。
最後に回転ゲートですが、それぞれ次のように定義されます。
ここで、
量子状態
今度は
テンソル積の考えをベクトルから行列に拡張することにより、
一方で、元から
この作用を見ると、1つ目の量子ビットが
この参照している1つ目の量子ビットのことを制御量子ビット、またはコントロール量子ビットと呼び、反転させる2つ目の量子ビットをターゲット量子ビットと呼びます。
さて、ここで量子計算特有の「量子もつれ」というものを作ってみましょう。
これは古典計算では非常に作るのが難しい状態ですが、上で定義されてきたことを踏まえると簡単に出来てしまいます!
その前に、一度テンソル積について見直しましょう。
先ほど2つの
一方で、
例えば、次のような
この等式が正しい場合、
ここで、
よって4つの等式を同時に満たすことはできないため、状態
この量子もつれをベル状態と言います。
ベル状態にはいくつか種類があり、次のようなものです。
このベル状態を作る量子回路を作ってみましょう。
まず、
次に、アダマールゲートを作用させた方の量子ビットを制御量子ビットとしてCNOTゲートを作用させます。
このとき、
また、
Qiskitを使ったプログラミング
ここからは、いよいよQiskitを使って量子計算をしてみましょう!
まずはGoogle Colaboratoryの環境下にQiskit系のライブラリをダウンロードして使えるようにします。
!pip3 install qiskit[visualization] qiskit-ibm-runtime qiskit-aer
これでライブラリが使えるようになりましたので、早速プログラムを組んでみましょう!
量子回路の作成
まずはライブラリのインポートからしましょう。
# ライブラリのインポート
from qiskit import __version__, QuantumCircuit
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit.visualization import plot_histogram
from qiskit_aer import AerSimulator, StatevectorSimulator
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2
# Qiskitバージョン確認
print(__version__)
ここでは量子回路を実際に作ってみます。
まずは量子ビットを用意して、量子ゲートを作用させる方法を紹介します。
# 1量子ビットの量子回路を作成
qc_0 = QuantumCircuit(1)
# X, Y, Z, Hゲートの作用のさせ方
qc_0.x(0) # Xゲートを作用させる
# qc_0.y(0) # Yゲートを作用させる
# qc_0.z(0) # Zゲートを作用させる
# qc_0.h(0) # Hゲートを作用させる
# 量子回路を図(画像)で表示
qc_0.draw("mpl")

簡単に解説しますと、 QuantumCircuit クラスのインスタンス qc_0 は、
qc_0 = QuantumCircuit(1)
より
ここで引数を 2 や 3 に変更することにより
続いて、この量子回路インスタンスに対してゲートを作用させるには、 qc_0.x(0) のように対応するメソッドを実行します。
この場合、 0 は
もちろん今回は 1 以上の引数を入れるとエラーが発生します。
他にも量子ゲートを作用させる方法はあるのですが、少しややこしいのでまずはこれで慣れるようにしましょう。
そして、出来た量子回路を表示するのが draw() メソッドになります。
引数には "mpl" や "latex"などのオプションがあります。
今回は画像として表示/保存できる "mpl" を用いています。
では、先ほど学んだベル状態の量子回路を作って描画してみましょう!
# 2量子ビットの量子回路を作成
qc_1 = QuantumCircuit(2)
# アダマールゲートを作用
qc_1.h(0)
# CNOTゲートを作用
qc_1.cx(0, 1) # NOTはXと同じ意味なのでCNOTゲートを作用させるメソッドは .cx() となる
# 量子回路を図(画像)で表示
qc_1.draw("mpl")

このような図が描画できました!
1つ目の量子ビットにアダマールゲートがかかり、それを制御量子ビット、2つ目の量子ビットをターゲット量子ビットとしてCNOTゲートをかけていることが分かります。
シミュレーション
量子回路を作成し、描画することが出来ました。
しかし、これだけでは本当にベル状態が作れているのかがわかりません。
状態ベクトルシミュレータを使って量子状態を確認してみましょう。
# 状態ベクトルシミュレータの実行
backend = StatevectorSimulator()
result = backend.run(qc_1).result()
# 量子状態のベクトルを出力
state_vec = result.get_statevector(qc_1)
print(state_vec)
Statevector([0.70710678+0.j, 0. +0.j, 0. +0.j,
0.70710678+0.j],
dims=(2, 2))
状態ベクトルシミュレータは、作成した量子回路が最終的にどのような量子状態になるのかを実際に計算するシミュレータです。
Qiskit Aerライブラリの StatevectorSimulator() クラスを使うことでできるようになります。
そして出力結果の1つ目の要素に、状態ベクトルが格納されています。
ベル状態のベクトル
と比べると、係数
今度は「測定」まで行ってみましょう。
量子コンピュータでは、量子回路が生成する状態を途中で確認することが出来ません。
その量子回路によって状態が変化した量子ビットを繰り返し読むことで、その状態が持つ確率分布を調べることが出来ます。
これを測定と言います。
# 2量子ビットの量子回路を作成
qc_2 = QuantumCircuit(2, 2) # 2つ目の引数は古典ビット数
# ベル状態の作成
qc_2.h(0)
qc_2.cx(0, 1)
# 測定
for i in range(2):
qc_2.measure(i, i) # measure() メソッドの引数は measure("測定する量子ビットのインデックス", "測定結果を保存する古典レジスタのインデックス") を表す
# 量子回路を図(画像)で表示
qc_2.draw("mpl")

先ほどの回路とは少し異なるのが分かります。
右側にメーターのようなアイコンのゲートが2つ出来ていると思いますが、こちらで測定を行います。
また、一番下に
この古典レジスタにあるものが量子計算の結果ということになります。
# バックエンドにシミュレータを設定
backend = AerSimulator()
# Sampler を使用しシミュレーション
sampler = SamplerV2(backend)
result = sampler.run([qc_2], shots=1024).result() # 1024回測定を行う
# 測定結果の出力
counts = result[0].data.c.get_counts()
print(counts)
# ヒストグラムを描画
plot_histogram(counts)
{'11': 531, '00': 493}

ほぼ等確率で
実機実験
今度は実際の量子コンピュータを使って実験してみましょう。
まずはIBM Quantum Platformのアカウントにログインします。
そこにあるトークンをコピーして、以下のコードの QiskitRuntimeService.save_account(token="") の中に入れます。
すると自身のIBM Quantum Platformと連携出来るので、実機が使えるようになります。
# アカウント情報を連携
QiskitRuntimeService.save_account(
channel="ibm_quantum",
instance="ibm-q/open/main",
token="hogehoge",
overwrite=True
)
# 使える実機を確認
service = QiskitRuntimeService()
print(service.backends())
[<IBMBackend('ibm_brisbane')>,
<IBMBackend('ibm_sherbrooke')>,
<IBMBackend('ibm_kyiv')>]
上記で表示された実機をバックエンドに指定します。
各実機は、全ての量子ゲートを自在に扱えるわけではありません。
実機ごとにトポロジカルな制約があるため、それに合わせるように「翻訳」します。
よって、先ほど作った量子回路とは全く異なる形の量子回路が生成されるのですが、やっている計算そのものは同じなので安心してください。
また、馴染みのない量子ゲートも出てきますが、そこも今は理解する必要はありません。
# 実機の選択
backend = service.backend("ibm_kyiv")
# 実機が扱える量子ゲートのみで量子回路を再構築
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_qc_2 = pm.run(qc_2)
isa_qc_2.draw("mpl", idle_wires=False)

それでは、実機での実験をしましょう。
IBM Quantum Platformの方でもジョブが反映されると思うので、そちらも適宜確認してください。
# Sampler でサンプリングしてジョブを作成
sampler = SamplerV2(backend)
running_job = sampler.run([isa_qc_2], shots=1024)
# ジョブのIDを取得
job_id = running_job.job_id()
print(f"Job ID:\t{job_id}")
# ジョブIDと照合
job = service.job(job_id)
# ステータスの確認
print(f"Job Status:\t{job.status()}")
QUEUED
こちら、すぐの状態だとステータスは "QUEUED" となるかと思います。
実機にジョブを投げるときに、先客がいるとキューに入った状態がしばらく続きます。
こちらが "DONE" になるまで待たなければ次のプログラムを実行することは出来ません。
ただし、上記セルを再度実行してしまうと新しいジョブを作成することになり、結局また待つことになってしまいます。
最後2行の
job = service.job(job_id)
print(f"Job Status:\t{job.status()}")
こちらだけをコピーして新しいセルで実行してステータスを確認するか、IBM Quantum Platformで実行が終わったことを確認するのが良いと思います。
もし、そのまま下記の結果確認まで同じセル内で完結させたいのであれば、 while 文と time ライブラリを駆使してステータスが "DONE" になったら次に進むというような形を取るのが良いかもしれません。
# ジョブの結果を確認
result = job.result()
counts = result[0].data.c.get_counts()
print(counts)
# ヒストグラムを描画
plot_histogram(counts)
{'11': 494, '00': 504, '01': 9, '10': 17}

結果が返ってきました!
これを見ると、ほとんどの場合
これは量子コンピュータ特有のエラーによるものです。
エラーの原因はいくつかあるのですが、これらを軽減するために日々研究が行われています。
特に、今はNISQ時代と言われていて、ノイズありの中規模な量子コンピュータでしか実験が行えないため、エラーを考慮したアルゴリズムが開発されてきています。
それに対し、誤り耐性量子コンピュータ、通称FTQCの実用化もそう遠くない未来に出来ると予想されています。
今後に期待ですね!
次回予告
次回は、量子もつれを深掘りすることをテーマに進めて参ります!
GHZ状態や、あまり世間に知られていないW状態というものを扱いますので、是非また読んでみてください。
Discussion