格子ハミルトニアンのクリロフ量子対角化
使用時間の目安: Heron r2で20分(注: これは目安です。実行時間は異なる場合があります。)
# This cell is hidden from users – it disables some lint rules
# ruff: noqa: E402 E722 F601
背景
このチュートリアルでは、Qiskitパターンの文脈でクリロフ量子対角化アルゴリズム(KQD)を実装する方法を示します。まずアルゴリズムの背後にある理論を学び、その後QPU上での実行のデモンストレーションを行います。
さまざまな分野において、量子系の基底状態特性を知ることに関心があります。例えば、粒子と力の基本的な性質の理解、複雑な物質の振る舞いの予測と理解、生化学的相互作用と反応の理解などが挙げられます。ヒルベルト空間の指数関数的な成長とエンタングルした系に生じる相関のため、古典的アルゴリズムはサイズが増大する量子系に対してこの問題を解くことが困難です。一方の端には、量子ハードウェアを活用する既存のアプローチとして変分量子手法(例えば、変分量子固有値ソルバー)があります。これらの手法は、最適化プロセスで必 要とされる関数呼び出しの回数が多いため現在のデバイスでは課題を抱えており、高度なエラー緩和技術を導入すると大きなリソースオーバーヘッドが加わるため、その有効性は小規模なシステムに限定されます。もう一方の端には、性能保証を持つフォールトトレラント量子手法(例えば、量子位相推定)がありますが、これらはフォールトトレラントデバイス上でのみ実行可能な深い回路を必要とします。これらの理由から、ここでは部分空間法(このレビュー論文に記載)に基づく量子アルゴリズムであるクリロフ量子対角化(KQD)アルゴリズムを紹介します。このアルゴリズムは、既存の量子ハードウェア上で大規模に良好な性能を示し[1]、位相推定と同様の性能保証を共有し、高度なエラー緩和技術と互換性があり、古典的に到達不可能な結果を提供できる可能性があります。
要件
このチュートリアルを始める前に、以下がインストールされていることを確認してください:
- Qiskit SDK v2.0以降、可視化サポート付き
- Qiskit Runtime v0.22以降(
pip install qiskit-ibm-runtime)
セットアップ
import numpy as np
import scipy as sp
import matplotlib.pylab as plt
from typing import Union, List
import itertools as it
import copy
from sympy import Matrix
import warnings
warnings.filterwarnings("ignore")
from qiskit.quantum_info import SparsePauliOp, Pauli, StabilizerState
from qiskit.circuit import Parameter, IfElseOp
from qiskit import QuantumCircuit, QuantumRegister
from qiskit.circuit.library import PauliEvolutionGate
from qiskit.synthesis import LieTrotter
from qiskit.transpiler import Target, CouplingMap
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import (
QiskitRuntimeService,
EstimatorV2 as Estimator,
)
def solve_regularized_gen_eig(
h: np.ndarray,
s: np.ndarray,
threshold: float,
k: int = 1,
return_dimn: bool = False,
) -> Union[float, List[float]]:
"""
Method for solving the generalized eigenvalue problem with regularization
Args:
h (numpy.ndarray):
The effective representation of the matrix in the Krylov subspace
s (numpy.ndarray):
The matrix of overlaps between vectors of the Krylov subspace
threshold (float):
Cut-off value for the eigenvalue of s
k (int):
Number of eigenvalues to return
return_dimn (bool):
Whether to return the size of the regularized subspace
Returns:
lowest k-eigenvalue(s) that are the solution of the regularized generalized eigenvalue problem
"""
s_vals, s_vecs = sp.linalg.eigh(s)
s_vecs = s_vecs.T
good_vecs = np.array(
[vec for val, vec in zip(s_vals, s_vecs) if val > threshold]
)
h_reg = good_vecs.conj() @ h @ good_vecs.T
s_reg = good_vecs.conj() @ s @ good_vecs.T
if k == 1:
if return_dimn:
return sp.linalg.eigh(h_reg, s_reg)[0][0], len(good_vecs)
else:
return sp.linalg.eigh(h_reg, s_reg)[0][0]
else:
if return_dimn:
return sp.linalg.eigh(h_reg, s_reg)[0][:k], len(good_vecs)
else:
return sp.linalg.eigh(h_reg, s_reg)[0][:k]
def single_particle_gs(H_op, n_qubits):
"""
Find the ground state of the single particle(excitation) sector
"""
H_x = []
for p, coeff in H_op.to_list():
H_x.append(set([i for i, v in enumerate(Pauli(p).x) if v]))
H_z = []
for p, coeff in H_op.to_list():
H_z.append(set([i for i, v in enumerate(Pauli(p).z) if v]))
H_c = H_op.coeffs
print("n_sys_qubits", n_qubits)
n_exc = 1
sub_dimn = int(sp.special.comb(n_qubits + 1, n_exc))
print("n_exc", n_exc, ", subspace dimension", sub_dimn)
few_particle_H = np.zeros((sub_dimn, sub_dimn), dtype=complex)
sparse_vecs = [
set(vec) for vec in it.combinations(range(n_qubits + 1), r=n_exc)
] # list all of the possible sets of n_exc indices of 1s in n_exc-particle states
m = 0
for i, i_set in enumerate(sparse_vecs):
for j, j_set in enumerate(sparse_vecs):
m += 1
if len(i_set.symmetric_difference(j_set)) <= 2:
for p_x, p_z, coeff in zip(H_x, H_z, H_c):
if i_set.symmetric_difference(j_set) == p_x:
sgn = ((-1j) ** len(p_x.intersection(p_z))) * (
(-1) ** len(i_set.intersection(p_z))
)
else:
sgn = 0
few_particle_H[i, j] += sgn * coeff
gs_en = min(np.linalg.eigvalsh(few_particle_H))
print("single particle ground state energy: ", gs_en)
return gs_en
ステップ1: 古典的な入力を量子問題にマッピングする
クリロフ空間
次数 のクリロフ空間 は、行列 の までの高次べき乗を参照ベクトル に乗じて得られるベクトルが張る空間です。
行列 がハミルトニアン である場合、対応する空間をべきクリロフ空間 と呼びます。 がハミルトニアンによって生成される時間発展演算子 である場合、この空間をユニタリクリロフ空間 と呼びます。古典的に使用されるべきクリロフ部分空間は、 がユニタリ演算子ではないため、量子コンピュータ上で直接生成することはできません。代わりに、べき乗法と同様の収束保証を与えることが示されている時間発展演算子 を使用することができます。 のべき乗は異なる時間ステップ となります。
ユニタリクリロフ空間が低エネルギー固有状態を正確に表現できる仕組みの詳細な導出については、付録を参照してください。
クリロフ量子対角化アルゴリズム
対角化したいハミルトニアン が与えられた場合、まず対応するユニタリクリロフ空間 を考えます。目標は におけるハミルトニアンのコンパクトな表現を見つけることであり、これを と呼びます。 の行列要素、すなわちクリロフ空間へのハミルトニアンの射影は、以下の期待値を計算することで求められます。
ここで はユニタリクリロフ空間のベクトルであり、 は選択された時間ステップ の倍数です。量子コンピュータ上では、各行列要素の計算は量子状態間の重なりを取得できる任意のアルゴリズムで行うことができます。このチュートリアルではアダマールテストに焦点を当てます。 の次元が であることから、部分空間に射影されたハミルトニアンの次元は となります。 が十分に小さければ(一般的に で固有エネルギーの推定値の収束が得られます)、射影されたハミルトニアン を容易に対角化できます。ただし、クリロフ空間ベクトルの非直交性のため、 を直接対角化することはできません。ベクトル間の重なりを測定し、行列 を構成する必要があります。
これにより、非直交空間での固有値問題(一般化固有値問題とも呼ばれます)を解くことができます。
の固有値と固有状態を調べることで、 の固有値と固有状態の推定値を得ることができます。例えば、基底状態エネルギーの推定値は最小の固有値 を取ることで得られ、基底状態は対応する固有ベクトル から得られます。 の係数は、 を張る異なるベクトルの寄与を決定します。

この図は、異なる量子状態間の重なりを計算するために使用される手法である修正アダマールテストの回路表現を示しています。各行列要素 に対して、状態 と の間のアダマールテストが実行されます。これは図中の行列要素と対応する 、 操作のカラースキームで強調されています。したがって、射影されたハミルトニアン のすべての行列要素を計算するためには、クリロフ空間ベクトルのすべての可能な組み合わせに対するアダマールテストのセットが必要です。アダマールテスト回路の上のワイヤーはXまたはY基底で測定される補助量子ビットであり、その期待値が状態間の重なりの値を決定します。下のワイヤーはシステムハミルトニアンのすべての量子ビットを表します。 操作は、補助量子ビットの状態に制御されてシステム量子ビットを状態 に準備し( も同様)、操作 はシステムハミルトニアンのパウリ分解 を表します。アダマールテストで計算される操作のより詳細な導出を以下に示します。
ハミルトニアンの定義
線形チェーン上の 量子ビットに対するハイゼンベルクハミルトニアンを考えます:
# Define problem Hamiltonian.
n_qubits = 30
J = 1 # coupling strength for ZZ interaction
# Define the Hamiltonian:
H_int = [["I"] * n_qubits for _ in range(3 * (n_qubits - 1))]
for i in range(n_qubits - 1):
H_int[i][i] = "Z"
H_int[i][i + 1] = "Z"
for i in range(n_qubits - 1):
H_int[n_qubits - 1 + i][i] = "X"
H_int[n_qubits - 1 + i][i + 1] = "X"
for i in range(n_qubits - 1):
H_int[2 * (n_qubits - 1) + i][i] = "Y"
H_int[2 * (n_qubits - 1) + i][i + 1] = "Y"
H_int = ["".join(term) for term in H_int]
H_tot = [(term, J) if term.count("Z") == 2 else (term, 1) for term in H_int]
# Get operator
H_op = SparsePauliOp.from_list(H_tot)
print(H_tot)
[('ZZIIIIIIIIIIIIIIIIIIIIIIIIIIII', 1), ('IZZIIIIIIIIIIIIIIIIIIIIIIIIIII', 1), ('IIZZIIIIIIIIIIIIIIIIIIIIIIIIII', 1), ('IIIZZIIIIIIIIIIIIIIIIIIIIIIIII', 1), ('IIIIZZIIIIIIIIIIIIIIIIIIIIIIII', 1), ('IIIIIZZIIIIIIIIIIIIIIIIIIIIIII', 1), ('IIIIIIZZIIIIIIIIIIIIIIIIIIIIII', 1), ('IIIIIIIZZIIIIIIIIIIIIIIIIIIIII', 1), ('IIIIIIIIZZIIIIIIIIIIIIIIIIIIII', 1), ('IIIIIIIIIZZIIIIIIIIIIIIIIIIIII', 1), ('IIIIIIIIIIZZIIIIIIIIIIIIIIIIII', 1), ('IIIIIIIIIIIZZIIIIIIIIIIIIIIIII', 1), ('IIIIIIIIIIIIZZIIIIIIIIIIIIIIII', 1), ('IIIIIIIIIIIIIZZIIIIIIIIIIIIIII', 1), ('IIIIIIIIIIIIIIZZIIIIIIIIIIIIII', 1), ('IIIIIIIIIIIIIIIZZIIIIIIIIIIIII', 1), ('IIIIIIIIIIIIIIIIZZIIIIIIIIIIII', 1), ('IIIIIIIIIIIIIIIIIZZIIIIIIIIIII', 1), ('IIIIIIIIIIIIIIIIIIZZIIIIIIIIII', 1), ('IIIIIIIIIIIIIIIIIIIZZIIIIIIIII', 1), ('IIIIIIIIIIIIIIIIIIIIZZIIIIIIII', 1), ('IIIIIIIIIIIIIIIIIIIIIZZIIIIIII', 1), ('IIIIIIIIIIIIIIIIIIIIIIZZIIIIII', 1), ('IIIIIIIIIIIIIIIIIIIIIIIZZIIIII', 1), ('IIIIIIIIIIIIIIIIIIIIIIIIZZIIII', 1), ('IIIIIIIIIIIIIIIIIIIIIIIIIZZIII', 1), ('IIIIIIIIIIIIIIIIIIIIIIIIIIZZII', 1), ('IIIIIIIIIIIIIIIIIIIIIIIIIIIZZI', 1), ('IIIIIIIIIIIIIIIIIIIIIIIIIIIIZZ', 1), ('XXIIIIIIIIIIIIIIIIIIIIIIIIIIII', 1), ('IXXIIIIIIIIIIIIIIIIIIIIIIIIIII', 1), ('IIXXIIIIIIIIIIIIIIIIIIIIIIIIII', 1), ('IIIXXIIIIIIIIIIIIIIIIIIIIIIIII', 1), ('IIIIXXIIIIIIIIIIIIIIIIIIIIIIII', 1), ('IIIIIXXIIIIIIIIIIIIIIIIIIIIIII', 1), ('IIIIIIXXIIIIIIIIIIIIIIIIIIIIII', 1), ('IIIIIIIXXIIIIIIIIIIIIIIIIIIIII', 1), ('IIIIIIIIXXIIIIIIIIIIIIIIIIIIII', 1), ('IIIIIIIIIXXIIIIIIIIIIIIIIIIIII', 1), ('IIIIIIIIIIXXIIIIIIIIIIIIIIIIII', 1), ('IIIIIIIIIIIXXIIIIIIIIIIIIIIIII', 1), ('IIIIIIIIIIIIXXIIIIIIIIIIIIIIII', 1), ('IIIIIIIIIIIIIXXIIIIIIIIIIIIIII', 1), ('IIIIIIIIIIIIIIXXIIIIIIIIIIIIII', 1), ('IIIIIIIIIIIIIIIXXIIIIIIIIIIIII', 1), ('IIIIIIIIIIIIIIIIXXIIIIIIIIIIII', 1), ('IIIIIIIIIIIIIIIIIXXIIIIIIIIIII', 1), ('IIIIIIIIIIIIIIIIIIXXIIIIIIIIII', 1), ('IIIIIIIIIIIIIIIIIIIXXIIIIIIIII', 1), ('IIIIIIIIIIIIIIIIIIIIXXIIIIIIII', 1), ('IIIIIIIIIIIIIIIIIIIIIXXIIIIIII', 1), ('IIIIIIIIIIIIIIIIIIIIIIXXIIIIII', 1), ('IIIIIIIIIIIIIIIIIIIIIIIXXIIIII', 1), ('IIIIIIIIIIIIIIIIIIIIIIIIXXIIII', 1), ('IIIIIIIIIIIIIIIIIIIIIIIIIXXIII', 1), ('IIIIIIIIIIIIIIIIIIIIIIIIIIXXII', 1), ('IIIIIIIIIIIIIIIIIIIIIIIIIIIXXI', 1), ('IIIIIIIIIIIIIIIIIIIIIIIIIIIIXX', 1), ('YYIIIIIIIIIIIIIIIIIIIIIIIIIIII', 1), ('IYYIIIIIIIIIIIIIIIIIIIIIIIIIII', 1), ('IIYYIIIIIIIIIIIIIIIIIIIIIIIIII', 1), ('IIIYYIIIIIIIIIIIIIIIIIIIIIIIII', 1), ('IIIIYYIIIIIIIIIIIIIIIIIIIIIIII', 1), ('IIIIIYYIIIIIIIIIIIIIIIIIIIIIII', 1), ('IIIIIIYYIIIIIIIIIIIIIIIIIIIIII', 1), ('IIIIIIIYYIIIIIIIIIIIIIIIIIIIII', 1), ('IIIIIIIIYYIIIIIIIIIIIIIIIIIIII', 1), ('IIIIIIIIIYYIIIIIIIIIIIIIIIIIII', 1), ('IIIIIIIIIIYYIIIIIIIIIIIIIIIIII', 1), ('IIIIIIIIIIIYYIIIIIIIIIIIIIIIII', 1), ('IIIIIIIIIIIIYYIIIIIIIIIIIIIIII', 1), ('IIIIIIIIIIIIIYYIIIIIIIIIIIIIII', 1), ('IIIIIIIIIIIIIIYYIIIIIIIIIIIIII', 1), ('IIIIIIIIIIIIIIIYYIIIIIIIIIIIII', 1), ('IIIIIIIIIIIIIIIIYYIIIIIIIIIIII', 1), ('IIIIIIIIIIIIIIIIIYYIIIIIIIIIII', 1), ('IIIIIIIIIIIIIIIIIIYYIIIIIIIIII', 1), ('IIIIIIIIIIIIIIIIIIIYYIIIIIIIII', 1), ('IIIIIIIIIIIIIIIIIIIIYYIIIIIIII', 1), ('IIIIIIIIIIIIIIIIIIIIIYYIIIIIII', 1), ('IIIIIIIIIIIIIIIIIIIIIIYYIIIIII', 1), ('IIIIIIIIIIIIIIIIIIIIIIIYYIIIII', 1), ('IIIIIIIIIIIIIIIIIIIIIIIIYYIIII', 1), ('IIIIIIIIIIIIIIIIIIIIIIIIIYYIII', 1), ('IIIIIIIIIIIIIIIIIIIIIIIIIIYYII', 1), ('IIIIIIIIIIIIIIIIIIIIIIIIIIIYYI', 1), ('IIIIIIIIIIIIIIIIIIIIIIIIIIIIYY', 1)]
アルゴリズムのパラメータ設定
ここでは、時間ステップ dt の値を(ハミルトニアンノルムの上限に基づいて)ヒューリスティックに選択します。参考文献[2]では、十分に小さい時間ステップは であり、過大評価するよりも過小評価する方がある程度までは望ましいことが示されています。これは、過大評価すると高エネルギー状態からの寄与がクリロフ空間内の最適な状態をも劣化させる可能性があるためです。一方、 を小さくしすぎるとクリロフ部分空間の条件付けが悪化します。これは、時間ステップごとのクリロフ基底ベクトルの差異が小さくなるためです。
# Get Hamiltonian restricted to single-particle states
single_particle_H = np.zeros((n_qubits, n_qubits))
for i in range(n_qubits):
for j in range(i + 1):
for p, coeff in H_op.to_list():
p_x = Pauli(p).x
p_z = Pauli(p).z
if all(
p_x[k] == ((i == k) + (j == k)) % 2 for k in range(n_qubits)
):
sgn = (
(-1j) ** sum(p_z[k] and p_x[k] for k in range(n_qubits))
) * ((-1) ** p_z[i])
else:
sgn = 0
single_particle_H[i, j] += sgn * coeff
for i in range(n_qubits):
for j in range(i + 1, n_qubits):
single_particle_H[i, j] = np.conj(single_particle_H[j, i])
# Set dt according to spectral norm
dt = np.pi / np.linalg.norm(single_particle_H, ord=2)
dt
np.float64(0.10833078115826875)
次に、アルゴリズムのその他のパラメータを設定します。このチュートリアルでは、5次元のクリロフ空間のみを使用するように制限しますが、これはかなり制約があります。
# Set parameters for quantum Krylov algorithm
krylov_dim = 5 # size of Krylov subspace
num_trotter_steps = 6
dt_circ = dt / num_trotter_steps
状態準備
基底状態とある程度の重なりを持つ参照状態 を選びます。このハミルトニアンでは、中央の量子ビットに励起を持つ状態 を参照状態として使用します。
qc_state_prep = QuantumCircuit(n_qubits)
qc_state_prep.x(int(n_qubits / 2) + 1)
qc_state_prep.draw("mpl", scale=0.5)
時間発展
与えられたハミルトニアンによって生成される時間発展演算子 は、リー・トロッター近似を用いて実現できます。
t = Parameter("t")
## Create the time-evo op circuit
evol_gate = PauliEvolutionGate(
H_op, time=t, synthesis=LieTrotter(reps=num_trotter_steps)
)
qr = QuantumRegister(n_qubits)
qc_evol = QuantumCircuit(qr)
qc_evol.append(evol_gate, qargs=qr)
<qiskit.circuit.instructionset.InstructionSet at 0x11eef9be0>
アダマールテスト

ここで はハミルトニアンの分解 における項の一つであり、、 はユニタリクリロフ空間のベクトル 、 を準備する制御操作です。ここで です。 を測定するために、まず を適用します...
... その後測定します: