物理系を表現する#
量子コンピュータの並列性を利用した計算の代表例として、量子系のダイナミクスシミュレーションについて学びます。
量子系のダイナミクスとは#
量子力学について少しでも聞いたことのある方は、量子力学の根幹にシュレーディンガー方程式というものが存在することを知っているかと思います。この方程式は
などと表現され、時刻
と書いておきます。
のみを考えます。量子状態に対する演算子(線形演算子)の指数関数もまた演算子なので、積分形のシュレーディンガー方程式は「
ユニタリ演算子は量子計算の言葉で言えばゲートにあたります。したがって、ある量子系に関して、その初期状態を量子レジスタで表現でき、時間発展演算子を量子コンピュータの基本ゲートの組み合わせで実装できれば、その系のダイナミクス(=時間発展)シミュレーションを量子コンピュータで行うことができます。
例:核磁気の歳差運動#
シミュレーションの詳しい話をする前に、これまで量子力学と疎遠だった方のために、ハミルトニアンや時間発展とは具体的にどういうことか、簡単な例を使って説明します。
空間中に固定されたスピン
時刻
の
この原子核に
量子力学では
と作用します。時間発展演算子
ですが(
と書けます。したがって、
です。任意の時刻
このように、系のエネルギーの表式からハミルトニアンが決まり、その指数関数を初期状態に作用させることで時間発展後の系の状態が求まります。
ちなみに、
量子コンピュータ上での表現#
すでに触れましたが、上の例で核のスピンは量子ビットのように2つの基底ケットを持ちます(2次元量子系です)。さらに、お気づきの方も多いと思いますが、
実際には、時間発展演算子は
という変換を行います。上の核スピン系を量子コンピュータでシミュレートするには、1量子ビットで

ハミルトニアンの対角化#
再び量子コンピュータを離れて、量子・古典に関わらずデジタル計算機で量子ダイナミクスのシミュレーションをする際の一般論をします。
上の核スピンの例ではハミルトニアンが単純だったので、式(11)のように厳密解が求まりました。特に、導出において
累乗して恒等演算子にならないようなハミルトニアンであっても、系の次元が小さい場合は「対角化」という作業で厳密解を得られます。ハミルトニアンの対角化とは、ハミルトニアンの作用が実数をかけることと等しくなるようなケットを探してくること、つまり
が成り立つような
例えば上の例では
という2つの状態を考えると
なので、これらが固有値
固有値
したがって、系の初期状態
であれば、時刻
つまり、各固有ベクトルの振幅に、対応する位相因子をかけるだけで求まります。
再び核スピンの例を見ると、初期状態
となり、式(11)が再導出できます。
このように、ハミルトニアンの対角化さえできれば、量子ダイナミクスのシミュレーションは位相をかけて足し算をするだけの問題に帰着します。しかし、上で言及したように、計算量の問題から、ハミルトニアンが対角化できるのは主に系の次元が小さいときに限ります。「対角化」という言葉が示唆するように、この操作は行列演算(対角化)を伴い、その際の行列の大きさは
鈴木・トロッター分解#
ハミルトニアンが対角化できない場合、ダイナミクスシミュレーションをするには、結局式(11)のように初期状態に時間発展演算子を愚直にかけていくことになります。これは、式(10)のように
に分割し、
例えば、通常
とすれば、まず
しかし、このスキームは量子コンピュータでの実装に向いていません。上で述べたように量子コンピュータのゲートはユニタリ演算子に対応するのに対して、
鈴木・トロッター分解が使えるケースとは、
は量子回路として実装が難しい。ハミルトニアンが
のように複数の部分ハミルトニアン の和に分解できる。個々の
に対しては が簡単に実装できる。
のような場合です。もしも
なので、そのような簡単な関係は成り立ちません。しかし、
という、Baker-Campbell-Hausdorfの公式の応用式は成り立ちます。これによると、時間分割の極限では、
つまり、
で近似すると、
鈴木・トロッター分解とは、このように全体の時間発展
なぜ量子コンピュータが量子ダイナミクスシミュレーションに向いているか#
鈴木・トロッター分解がダイナミクスシミュレーションに適用できるには、ハミルトニアンが都合よくゲートで実装できる
まず、
しかし、
そもそも量子コンピュータで量子ダイナミクスシミュレーションを行う利点は、その計算効率にあります。
シミュレートする量子系の次元を
一方、同じ計算に
したがって、逆に、ハミルトニアンが
幸いなことに、通常我々がシミュレートしたいと思うような物理系では、
実習:ハイゼンベルグモデルの時間発展#
モデルのハミルトニアン#
ハミルトニアンの分解と言われてもピンと来ない方もいるかもしれませんし、ここからはダイナミクスシミュレーションの具体例をQiskitで実装してみましょう。
ハイゼンベルグモデルという、磁性体のトイモデルを考えます。空間中一列に固定された多数のスピンを持つ粒子(電子)の系で、隣接スピンの向きによってエネルギーが決まるような問題です。
例えば、
です。ここで、
ただし、式(13)の和の記法には実は若干の省略があります。例えば第
です。ここで
この系では、隣接スピン間の向きが揃っている(内積が正)のときにエネルギーが低くなります[5]。少し考えるとわかりますが、すべてのスピンが完全に同じ方向を向いている状態が最もエネルギーの低いエネルギー固有状態です。そこで、最低エネルギー状態から少しだけずらして、スピンが一つだけ直角方向を向いている状態を始状態としたときのダイナミクスをシミュレートしてみましょう。
核スピンのケースと同様に、それぞれのスピンについて+
時間発展演算子は
ですが、ハミルトニアンの各項が互いに可換でないので、シミュレーションでは鈴木・トロッター分解を用いて近似します。各時間ステップ
です。
量子ゲートでの表現#
これを回転ゲートと制御ゲートで表します。まず
です。つまり、2つのスピンの「パリティ」(同一かどうか)に応じて、かかる位相の符号が違います。
パリティに関する演算をするにはCNOTを使います。例えば以下の回路

によって、計算基底
と変換するので(確認してください)、まさに
残りの2つの演算子も同様にパリティに対する回転で表せますが、CNOTで表現できるのは
で、式(12)から、次の回路が対応する変換を引き起こすことがわかります(これも確認してください)。

最後に、

です[6]。
回路実装#
やっと準備が整ったので、シミュレーションを実装しましょう。実機で走らせられるように、
# まずは全てインポート
import numpy as np
from qiskit import QuantumCircuit, transpile
from qiskit_aer import AerSimulator
from qiskit_aer.primitives import SamplerV2 as AerSampler
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as RuntimeSampler
from qiskit_ibm_runtime.accounts import AccountNotFoundError
# このワークブック独自のモジュール
from qc_workbook.dynamics import plot_heisenberg_spins
from qc_workbook.utils import operational_backend
n_spins = 5
M = 10
omegadt = 0.1
circuits = []
circuit = QuantumCircuit(n_spins)
# 第0ビットを 1/√2 (|0> + |1>) にする
circuit.h(0)
# Δtでの時間発展をM回繰り返すループ
for istep in range(M):
# ハミルトニアンのn-1個の項への分解に関するループ
for jspin in range(n_spins - 1):
# ZZ
circuit.cx(jspin, jspin + 1)
circuit.rz(-omegadt, jspin + 1)
circuit.cx(jspin, jspin + 1)
# XX
circuit.h(jspin)
circuit.h(jspin + 1)
circuit.cx(jspin, jspin + 1)
circuit.rz(-omegadt, jspin + 1)
circuit.cx(jspin, jspin + 1)
circuit.h(jspin)
circuit.h(jspin + 1)
# YY
circuit.p(-np.pi / 2., jspin)
circuit.p(-np.pi / 2., jspin + 1)
circuit.h(jspin)
circuit.h(jspin + 1)
circuit.cx(jspin, jspin + 1)
circuit.rz(-omegadt, jspin + 1)
circuit.cx(jspin, jspin + 1)
circuit.h(jspin)
circuit.h(jspin + 1)
circuit.p(np.pi / 2., jspin)
circuit.p(np.pi / 2., jspin + 1)
# この時点での回路のコピーをリストに保存
# measure_all(inplace=False) はここまでの回路のコピーに測定を足したものを返す
circuits.append(circuit.measure_all(inplace=False))
print(f'{len(circuits)} circuits created')
10 circuits created
量子回路シミュレーターで実行し、各ビットにおける
# 初期状態 |0> x |0> x |0> x |0> x 1/√2(|0>+|1>) は配列では [1/√2 1/√2 0 0 ...]
initial_state = np.zeros(2 ** n_spins, dtype=np.complex128)
initial_state[0:2] = np.sqrt(0.5)
shots = 100000
simulator = AerSimulator()
# 今回はシミュレータと実機両方のSamplerを使うので、シミュレータベースのものをAerSamplerと呼んでいる(上のimport文を参照)
sampler = AerSampler()
circuits_sim = transpile(circuits, backend=simulator)
sim_job = sampler.run(circuits_sim, shots=shots)
sim_results = sim_job.result()
sim_counts_list = [result.data.meas.get_counts() for result in sim_results]
plot_heisenberg_spins(sim_counts_list, n_spins, initial_state, omegadt, add_theory_curve=True)

ビット0でのスピンの不整合が徐々に他のビットに伝搬していく様子が観察できました。
また、上のように関数plot_heisenberg_spins
にadd_theory_curve=True
という引数を渡すと、ハミルトニアンを対角化して計算した厳密解のカーブも同時にプロットします。トロッター分解による解が、厳密解から少しずつずれていっている様子も観察できます。興味があれば
実機でも同様の結果が得られるか確認してみましょう。
# 利用できるインスタンスが複数ある場合(Premium accessなど)はここで指定する
# instance = 'hub-x/group-y/project-z'
instance = None
try:
service = QiskitRuntimeService(channel='ibm_quantum', instance=instance)
except AccountNotFoundError:
service = QiskitRuntimeService(channel='ibm_quantum', token='__paste_your_token_here__', instance=instance)
backend = service.least_busy(filters=operational_backend())
print(f'Job will run on {backend.name}')
sampler = RuntimeSampler(backend)
circuits_ibmq = transpile(circuits, backend=backend)
job = sampler.run(circuits_ibmq, shots=2000)
results = job.result()
counts_list = [result.data.meas.get_counts() for result in results]
plot_heisenberg_spins(counts_list, n_spins, initial_state, omegadt)