クイックスタート
このガイドでは、qiskit-addon-sqd パッケージの最小限の動作例を示します。サンプルベース量子対角化 (SQD) を使って、化学ハミルトニアンの基底状態を近似する量子回路から得られたノイズを含むサンプルを精緻化します。基底状態エネルギーは、精緻化されたサンプルが張る部分空間でハミルトニアンを対角化することで近似されます。
量子ハードウェア上で実行する、より詳しいエンドツーエンドのワークフローについては、次のチュートリアルをご覧ください。
-
サンプルベース量子対角化: 化学ハミルトニアンのワークフロー
-
サンプルベース・クリロフ量子対角化: フェルミオン格子模型のワークフロー
1. SQD の入力を準備する
まず、化学ハミルトニアンと、その基底状態を近似する量子回路を定義します。この例で扱う系は、平衡構造にある N₂ で、STO-3G 基底を用い、2つの内殻軌道を凍結しています。この系は 8 個の空間軌道と 10 個の電子を持つため、量子回路は 16 量子ビット上で定義され、量子ビット 0-7 がスピンアップ軌道、量子ビット 8-15 がスピンダウン軌道を表します。これは 10 個の電子を持つ閉殻系なので、物理的に妥当な各サンプルはハミング重み 10 を持つはずであり、ビット列の左半分と右半分のハミング重みはどちらも 5 になります。
# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util
_needed = {"numpy": "numpy", "pyscf": "pyscf", "qiskit_addon_sqd": "qiskit-addon-sqd"}
_missing = [pip for module, pip in _needed.items()
if importlib.util.find_spec(module) is None]
# One at a time, so a package that fails to install does not block the others
for _pip in _missing:
%pip install -q {_pip}
if not _missing:
print("\u2713 All required packages are installed")
from pyscf import ao2mo, gto, mcscf, scf
# Construct the Hamiltonian, pull out integrals and other information about the system
mol = gto.M(
atom="N 0 0 0; N 0 0 1.09768", basis="sto-3g", spin=0, charge=0, verbose=0
)
mf = scf.RHF(mol).run()
cas = mcscf.CASCI(mf, ncas=8, nelecas=10)
hcore, nuclear_repulsion_energy = cas.get_h1eff()
eri = cas.get_h2eff()
num_orbitals = cas.ncas
eri = ao2mo.restore(1, eri, num_orbitals)
n_alpha, n_beta = cas.nelecas
nelec = (n_alpha, n_beta)
print(
f"Spatial orbitals: {num_orbitals}\nQubits: {num_orbitals * 2}\nElectrons (alpha, beta): {nelec}"
)
Spatial orbitals: 8
Qubits: 16
Electrons (alpha, beta): (5, 5)
2. ノイズを含むサンプルを生成する
実際のワークフローでは、アンザッツ回路を設計し、ハードウェア向けに最適化して、QPU 上でサンプリングします。ここではそれを省略し、代わりに 10,000 個の一様ランダムなビット列を生成します。このサンプルには実際の基底状態に関する情報は含まれていませんが、これらのサンプルの精緻化、対角化、そして各イテレーションで得られたエネルギーを次のビット列精緻化ラウンドに活用するという手順を反復することで、SQD プロトコルが真の基底状態を回復できることを確認します。基底状態を含む部分空間は厳密に計算できる程度に小さいため、最終的に厳密な基底状態を回復できます。具体的には、各ビット列の各半分 (8 ビット) はハミング重み 5 を持つはずなので、この系の完全なヒルベルト部分空間のサイズは であることがわかります。
import numpy as np
from qiskit_addon_sqd.counts import generate_bit_array_uniform
# Generate some uniformly-random bitstrings to simulate QPU samples
rng = np.random.default_rng(24)
bit_array = generate_bit_array_uniform(
10_000, num_orbitals * 2, rand_seed=rng
)
print(
f"Generated {bit_array.num_shots} uniformly-random, {bit_array.num_bits}-qubit samples."
)
Generated 10000 uniformly-random, 16-qubit samples.
3. SQD で後処理する
SQD アルゴリズムの目的は、QPU から得られたノイズを含むサンプルを精緻化し、最終的にそれらの精緻化されたサンプルが張る部分空間でハミルトニアンを対角化することです。これは次のように反復的に行われます。
-
スピンアップとスピンダウンの両方の部分系で正しい粒子数を持つサンプルのみを事後選択して、サンプルを精緻化する
-
精緻化されたサンプルが張る小さな部分空間で対角化を行い、最良 (最低) のエネルギーをもたらす部分空間に関する情報を収集する。
-
各イテレーションで:
-
誤った粒子数を持つサンプルのビットを確率的に反転させて、ノイズを含むサンプルを精緻化する。確率は、直近の対角化で得られた部分空間の情報を使って計算されます。
-
精緻化されたサンプルが張る小さな部分空間で対角化する
-
最良 (最低) のエネルギーをもたらす部分空間に関する情報を収集する
-
出力から、アルゴリズムが 6 回のイテレーションの後に厳密な基底状態エネルギーへ収束し、対角化が次元 の完全なヒルベルト部分空間全体にわたって行われていることがわかります。もちろん、完全な部分空間全体での対角化は通常は現実的ではないため、基底状態の台がスパースな系が、SQD のような部分空間対角化技術の恩恵を最も受けやすいと考えられます。
diagonalize_fermionic_hamiltonian 関数の詳細については、API ドキュメントをご覧ください。
from functools import partial
from pyscf import fci
from qiskit_addon_sqd.fermion import (
SCIResult,
diagonalize_fermionic_hamiltonian,
solve_sci_batch,
)
exact_energy, _ = fci.direct_spin1.kernel(
hcore, eri, int(num_orbitals), nelec
)
exact_energy += nuclear_repulsion_energy
print(f"Exact (FCI) reference energy: {exact_energy:.6f} Ha\n")
sci_solver = partial(solve_sci_batch, spin_sq=0.0, max_cycle=200)
result_history = []
def callback(results: list[SCIResult]):
result_history.append(results)
iteration = len(result_history)
print(f"Iteration {iteration}")
for i, result in enumerate(results):
error = result.energy + nuclear_repulsion_energy - exact_energy
print(f" Subsample {i}")
print(f" Energy: {result.energy + nuclear_repulsion_energy:.6f}")
print(
f" Subspace dimension: {np.prod(result.sci_state.amplitudes.shape)}"
)
print(f" Error vs exact: {error:.6f} Ha")
samples_per_batch = 20
result = diagonalize_fermionic_hamiltonian(
hcore,
eri,
bit_array,
samples_per_batch=samples_per_batch,
norb=num_orbitals,
nelec=nelec,
occupancies_tol=1e-7,
max_iterations=30,
sci_solver=sci_solver,
symmetrize_spin=True,
callback=callback,
seed=np.random.default_rng(32),
)
Exact (FCI) reference energy: -107.652521 Ha
Iteration 1
Subsample 0
Energy: -106.908481
Subspace dimension: 784
Error vs exact: 0.744040 Ha
Iteration 2
Subsample 0
Energy: -106.934921
Subspace dimension: 1764
Error vs exact: 0.717600 Ha
Iteration 3
Subsample 0
Energy: -106.953430
Subspace dimension: 2209
Error vs exact: 0.699091 Ha
Iteration 4
Subsample 0
Energy: -107.651946
Subspace dimension: 2916
Error vs exact: 0.000575 Ha
Iteration 5
Subsample 0
Energy: -107.652003
Subspace dimension: 3025
Error vs exact: 0.000518 Ha
Iteration 6
Subsample 0
Energy: -107.652521
Subspace dimension: 3136
Error vs exact: 0.000000 Ha
Iteration 7
Subsample 0
Energy: -107.652521
Subspace dimension: 3136
Error vs exact: 0.000000 Ha