Qiskit実装
このセクションでは、このレッスンで紹介したコンセプトのQiskit実装をいくつか見ていきます。 これらの実装を実際に動かしてみることを強くお勧めします。セットアップ方法については、IBM Quantum Documentation内のQiskitのインストールページを参照してください。
Qiskitは継続的に開発が進められており、その主な目的は操作対象となる量子コンピューターのパフォーマンスを最大化することにあります。量子コンピューター自体も進化し続けています。 そのため、Qiskitはコードの非推奨化(deprecation)につながる変更が加えられることがあります。 このことを念頭に置き、本コースでQiskitコードの例を示す際は、常に以下のコマンドを最初に実行して、どのバージョンのQiskitが使用されているかを明示します。 Qiskit v1.0以降では、現在インストールされているQiskitのバージョンを確認するための簡単な方法です。
# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit
from qiskit import __version__
print(__version__)
2.2.3
クラウドベースのPython環境でこれを実行している場合、以下のパッケージをインストールする必要があるかもしれません:
#!pip install qiskit
#!pip install jupyter
#!pip install sympy
#!pip install matplotlib
#!pip install pylatexenc
Pythonにおけるベクトルと行列
QiskitはPythonプログラミング言語を使用しています。そのため、Qiskit自体について説明する前に、Pythonにおける行列・ベクトル計算について簡単に触れておくと役立つでしょう。
Pythonでは、多くの数値計算・科学計算機能を提供するNumPyライブラリのarrayクラスを使って行列・ベクトル計算を行うことができます。
以下のコードはこのライブラリを読み込み、量子ビットの状態ベクトル と に対応する2つの列ベクトル ket0 と ket1 を定義し、その平均を出力します。
import numpy as np
ket0 = np.array([[1], [0]])
ket1 = np.array([[0], [1]])
print(ket0 / 2 + ket1 / 2)
[[0.5]
[0.5]]
arrayを使って、演算を表す行列を作成することもできます。
M1 = np.array([[1, 1], [0, 0]])
M2 = np.array([[1, 0], [0, 1]])
M = M1 / 2 + M2 / 2
print(M)
[[1. 0.5]
[0. 0.5]]
本コースの各レッスン内のコードはすべて順番に実行することを前提としています。
そのため、NumPyはすでにインポート済みなので、ここで再度インポートする必要はありません。
行列の乗算(行列とベクトルの積を特殊なケースとして含む)は、@演算子を使って行えます。
print(M1 @ ket1)
print(M1 @ M2)
print(M @ M)
[[1]
[0]]
[[1 1]
[0 0]]
[[1. 0.75]
[0. 0.25]]
この出力フォーマットは、見た目の面でやや改善の余地があります。
より見やすい表示が必要な場合の解決策の一つとして、Qiskitのqiskit.visualizationモジュールにあるarray_to_latex関数を使う方法があります。
以下のコードでは、Pythonの汎用display関数を使用していることに注意してください。
一方、printの具体的な動作は出力内容によって異なります(例えば、NumPyで定義された配列の場合など)。
from qiskit.visualization import array_to_latex
display(array_to_latex(M1 @ ket1))
display(array_to_latex(M1 @ M2))
display(array_to_latex(M @ M))
状態、測定、および演算
Qiskitには、状態・測定・演算を作成・操作するためのクラスがいくつか含まれています。そのため、量子状態・測定・演算をシミュレートするためのプログラムをすべてゼロから書く必要はありません。 以下に、入門として役立つサンプルをいくつか示します。
状態ベクトルの定義と表示
QiskitのStatevectorクラスは、量子状態ベクトルを定義・操作するための機能を提供します。
以下のコードでは、Statevectorクラスをインポートし、いくつかのベクトルを定義します。
(NumPyライブラリからsqrt関数もインポートして平方根の計算に使用します。
この関数は、NumPyがすでにインポートされていれば np.sqrt として呼び出すこともできます。これは同じ関数を別の方法でインポートして使用しているだけです。)
from qiskit.quantum_info import Statevector
from numpy import sqrt
u = Statevector([1 / sqrt(2), 1 / sqrt(2)])
v = Statevector([(1 + 2.0j) / 3, -2 / 3])
w = Statevector([1 / 3, 2 / 3])
Statevectorクラスには、状態ベクトルをさまざまな形式で表示するためのdrawメソッドが含まれています。例えば、プレーンテキスト形式のtext、レンダリングされたLaTeX形式のlatex、そして文書への貼り付けに便利なLaTeXコード形式のlatex_sourceなどがあります。
(LaTeXコードを表示するには、displayではなくprintを使うと最良の結果が得られます。)
display(u.draw("text"))
display(u.draw("latex"))
print(u.draw("latex_source"))
[0.70710678+0.j,0.70710678+0.j]
\frac{\sqrt{2}}{2} |0\rangle+\frac{\sqrt{2}}{2} |1\rangle
Statevectorクラスにはis_validメソッドもあり、指定されたベクトルが有効な量子状態ベクトルかどうか(つまり、ユークリッドノルムが1に等しいかどうか)を確認します。
display(u.is_valid())
display(w.is_valid())
True
False
Statevectorを使った測定のシミュレーション
次に、Statevectorクラスのmeasureメソッドを使って、Qiskitで量子状態の測定をシミュレートする方法を見ていきます。
先ほど定義した量子ビットの状態ベクトルvを使いましょう。
display(v.draw("latex"))
measureメソッドを実行すると、標準基底測定がシミュレートされます。
このメソッドは、測定の結果と、測定後のシステムの新しい量子状態ベクトルを返します。
(以下のコードでは、埋め込み式を使ったフォーマット付き出力のために、fプレフィックス付きのPythonのprint関数を使用しています。)
outcome, state = v.measure()
print(f"Measured: {outcome}\nPost-measurement state:")
display(state.draw("latex"))
Measured: 1
Post-measurement state:
測定結果は確率的であるため、このメソッドは複数回実行すると異なる結果を返すことがあります。
上で定義したベクトルvの具体例では、measureメソッドは測定後の量子状態ベクトルを以下のように定義します。
( ではなく)または
( ではなく)のどちらかになり、これは測定結果に依存します。 どちらの場合も、 と の代替表現は、実際にはこれらの状態ベクトルと等価です。一方が他方に単位円上の複素数を掛けたものに等しいため、これらはグローバル位相まで同値であると言われます。 この問題については、量子回路レッスンでより詳しく説明されており、今のところは無視しても構いません。
Statevectorは、measureメソッドが無効な量子状態ベクトルに適用された場合にエラーをスローします。
Statevectorにはsample_countsメソッドもあり、毎回状態の新しいコピーを使って、任意の回数の測定シミュレーションを行うことができます。例えば、以下のコードはベクトルvを 回測定した結果を示しており、(高い確率で)結果 が約 回中 回(つまり 試行中約 回)、結果 が約 回中 回(つまり 試行中約 回)現れます。
また、以下のコードでは、結果を可視化するためのqiskit.visualizationモジュールのplot_histogram関数の使い方も示しています。
from qiskit.visualization import plot_histogram
statistics = v.sample_counts(1000)
plot_histogram(statistics)
の代わりに異なるサンプル数を使ってこのコードを何度か実行してみると、試行回数が各結果の出現回数にどのように影響するかについての直感を養うのに役立ちます。 サンプル数が増えるにつれて、各可能性のサンプルの割合は対応する確率に近づく傾向があります。 この現象は、確率論においてより一般的に大数の法則として知られています。
OperatorとStatevectorを使った演算の実行
ユニタリー演算は、以下の例のようにOperatorクラスを使ってQiskitで定義できます。
このクラスには、Statevectorと同様の引数を持つdrawメソッドが含まれています。
latexオプションはarray_to_latexと同等の結果を生成することに注意してください。
from qiskit.quantum_info import Operator
Y = Operator([[0, -1.0j], [1.0j, 0]])
H = Operator([[1 / sqrt(2), 1 / sqrt(2)], [1 / sqrt(2), -1 / sqrt(2)]])
S = Operator([[1, 0], [0, 1.0j]])
T = Operator([[1, 0], [0, (1 + 1.0j) / sqrt(2)]])
display(T.draw("latex"))
evolveメソッドを使って、状態ベクトルにユニタリー演算を適用できます。
v = Statevector([1, 0])
v = v.evolve(H)
v = v.evolve(T)
v = v.evolve(H)
v = v.evolve(S)
v = v.evolve(Y)
display(v.draw("latex"))
量子回路のプレビュー
量子回路は、本コースの第3レッスンである量子回路レッスンで正式に紹介されますが、QiskitのQuantumCircuitクラスを使って量子ビットのユニタリー演算の合成を実験することは今からでも可能です。
特に、以下のように量子回路(この場合は単一の量子ビットに対して実行されるユニタリー演算のシーケンス)を定義することができます。
from qiskit import QuantumCircuit
circuit = QuantumCircuit(1)
circuit.h(0)
circuit.t(0)
circuit.h(0)
circuit.s(0)
circuit.y(0)
display(circuit.draw(output="mpl"))
ここでは、QuantumCircuitクラスのdrawメソッドをmplレンダラー(Pythonの可視化ライブラリMatplotlibの略)と共に使用しています。
本コースでは量子回路の可視化にこのレンダラーのみを使用しますが、テキストベースやLaTeXベースのレンダラーなど他のオプションも存在します。
演算は図中で左から右へと順番に適用されます。
この回路に対応するユニタリー行列を取得する便利な方法として、Operatorクラスのfrom_circuitメソッドを使う方法があります。
display(Operator.from_circuit(circuit).draw("latex"))
初期量子状態ベクトルを設定し、回路で記述された演算のシーケンスに従ってその状態を発展させることもできます。
ket0 = Statevector([1, 0])
v = ket0.evolve(circuit)
display(v.draw("latex"))
以下のコードは、上の回路から得られた状態を標準基底測定で4000回測定する実験をシミュレートします(毎回状態の新しいコピーを使用)。
statistics = v.sample_counts(4000)
display(plot_histogram(statistics))