Skip to main content
IBM Quantum Platform

フェルミオン格子モデルのサンプル・ベースのKrylov量子対角化

推定使用時間:Heron r2 プロセッサーで9秒(注:これは推定値です。 ランタイムは異なるかもしれない)。


学習成果

このチュートリアルを終えた後、ユーザーは以下の点を理解できるようになります:

  • SQD Qiskit アドオンを使用して、量子処理ユニット(QPU)からサンプリングしたビット列を用いて格子モデルの基底状態エネルギーを近似する方法。
  • ffsim を使用して、フェルミオンシミュレーション用の時間発展回路を構築する方法。
  • サンプルベースのクリロフ対角化(SKQD)アルゴリズムを用いた後処理のために、複数の回路からのサンプルを結合する方法。

前提条件

このチュートリアルを進める前に、以下の内容についてあらかじめ理解しておいていただくことをお勧めします:


背景

このチュートリアルでは、サンプルベースの量子対角化(SQD)を用いて、フェルミオン格子モデルの基底状態エネルギーを推定する方法を紹介します。 具体的には、金属中に埋め込まれた磁性不純物を記述するために用いられる一次元単一不純物アンダーソンモデル(SIAM)を研究する。

このチュートリアルは、関連するチュートリアル「 Sample-based quantum diagonalization of chemistry Hamiltonian 」と同様のワークフローに従います。 しかし、重要な違いは量子回路の作り方にある。 もう1つのチュートリアルでは、発見的変分アナザッツを使用しており、数百万の相互作用項を持つ可能性のある化学ハミルトニアンに対して魅力的である。 一方、このチュートリアルでは、ハミルトニアンによって時間発展を近似する回路を使う。 このような回路は深くすることができるので、このアプローチは格子モデルへの応用に適している。 これらの回路によって準備された状態ベクトルはクリロフ部分空間の基底を形成し、その結果、適切な仮定の下で、アルゴリズムは基底状態に証明可能かつ効率的に収束する。

このチュートリアルで使用されるアプローチは、SQDとクリロフ量子対角化(KQD )で使用されるテクニックの組み合わせと見なすことができる。 この組み合わせは、サンプルベースクリロフ量子対角化(SQKD)と呼ばれることもある。 KQD法のチュートリアルについては、 格子ハミルトニアンのクリロフ量子対角化を参照してください。

このチュートリアルは、 "Quantum-Centric Algorithm for Sample-Based Krylov Diagonalization " をベースにしています。

単一不純物アンダーソンモデル(SIAM)

一次元のSIAMハミルトニアンは3つの項の和である:

H=Himp+Hbath+Hhyb,H = H_{\textrm{imp}}+ H_\textrm{bath} + H_\textrm{hyb},

ここで

Himp=ε(n^d+n^d)+Un^dn^d,Hbath=tj=0σ{,}L1(c^j,σc^j+1,σ+c^j+1,σc^j,σ),Hhyb=Vσ{,}(d^σc^0,σ+c^0,σd^σ).\begin{align*} H_\textrm{imp} &= \varepsilon \left( \hat{n}_{d\uparrow} + \hat{n}_{d\downarrow} \right) + U \hat{n}_{d\uparrow}\hat{n}_{d\downarrow}, \\ H_\textrm{bath} &= -t \sum_{\substack{\mathbf{j} = 0\\ \sigma\in \{\uparrow, \downarrow\}}}^{L-1} \left(\hat{c}^\dagger_{\mathbf{j}, \sigma}\hat{c}_{\mathbf{j}+1, \sigma} + \hat{c}^\dagger_{\mathbf{j}+1, \sigma}\hat{c}_{\mathbf{j}, \sigma} \right), \\ H_\textrm{hyb} &= V\sum_{\sigma \in \{\uparrow, \downarrow \}} \left(\hat{d}^\dagger_\sigma \hat{c}_{0, \sigma} + \hat{c}^\dagger_{0, \sigma} \hat{d}_{\sigma} \right). \end{align*}

ここで、 cj,σ/cj,σc^\dagger_{\mathbf{j},\sigma}/c_{\mathbf{j},\sigma} はスピンを持つ jth\mathbf{j}^{\textrm{th}} 浴サイトのフェルミオンの生成/消滅演算子であり、 σ\sigmad^σ/d^σ\hat{d}^\dagger_{\sigma}/\hat{d}_{\sigma} は不純物モードの生成/消滅演算子であり、 n^dσ=d^σd^σ\hat{n}_{d\sigma} = \hat{d}^\dagger_{\sigma} \hat{d}_{\sigma}ttUUVV はホッピング、オンサイト、混成相互作用を記述する実数であり、 ε\varepsilon は化学ポテンシャルを指定する実数である。

ハミルトニアンは、一般的な相互作用-電子ハミルトニアンの特定のインスタンスであることに注意してください、

H=p,qσhpqa^pσa^qσ+p,q,r,sστhpqrs2a^pσa^qτa^sτa^rσ=H1+H2,\begin{align*} H &= \sum_{\substack{p, q \\ \sigma}} h_{pq} \hat{a}^\dagger_{p\sigma} \hat{a}_{q\sigma} + \sum_{\substack{p, q, r, s \\ \sigma \tau}} \frac{h_{pqrs}}{2} \hat{a}^\dagger_{p\sigma} \hat{a}^\dagger_{q\tau} \hat{a}_{s\tau} \hat{a}_{r\sigma} \\ &= H_1 + H_2, \end{align*}

ここで、 H1H_1 は1体項で構成され、フェルミオンの生成・消滅作用素の2次式であり、 H2H_2 は2体項で構成され、4次式である。 SIAMのために、

H2=Un^dn^dH_2 = U \hat{n}_{d\uparrow}\hat{n}_{d\downarrow}

そして H1H_1、ハミルトニアンの残りの項が含まれる。 ハミルトニアンをプログラムで表現するために、行列 hpqh_{pq} とテンソル hpqrsh_{pqrs} を格納する。

位置と運動量の基底

HbathH_\textrm{bath} における近似的な並進対称性のため、基底状態が位置基底(上でハミルトニアンを指定した軌道基底)において疎になることは期待できない。 SQDの性能が保証されるのは、基底状態がスパースである場合、つまり、少数の計算基底状態のみに大きな重みがある場合のみである。 基底状態のスパース性を改善するために、 HbathH_\textrm{bath} が対角である軌道基底でシミュレーションを行った。 この基底を運動量基底と呼ぶ。 HbathH_\textrm{bath} は二次フェルミオンのハミルトニアンなので、軌道回転によって効率的に対角化できる。

ハミルトニアンによる近似的な時間発展

ハミルトニアンによる時間発展を近似するために、2次のTrotter-Suzuki分解を用いる、

eiΔtHeiΔt2H2eiΔtH1eiΔt2H2. e^{-i \Delta t H} \approx e^{-i\frac{\Delta t}{2} H_2} e^{-i\Delta t H_1} e^{-i\frac{\Delta t}{2} H_2}.

Jordan-Wigner変換の下では、 H2H_2 による時間発展は、不純物サイトのスピンアップ軌道とスピンダウン軌道の間の単一の CPhase ゲートに相当する。 H1H_1 は二次フェルミオンのハミルトニアンなので、 H1H_1 による時間発展は軌道回転に相当する。

クリロフ基底状態 {ψk}k=0D1\{ |\psi_k\rangle \}_{k=0}^{D-1}、ここで DD はクリロフ部分空間の次元であり、1回のトロッターステップの繰り返し適用によって形成される

ψk[eiΔt2H2eiΔtH1eiΔt2H2]kψ0. |\psi_k\rangle \approx \left[e^{-i\frac{\Delta t}{2} H_2} e^{-i\Delta t H_1} e^{-i\frac{\Delta t}{2} H_2} \right]^k\ket{\psi_0}.

以下のSQDベースのワークフローでは、この回路セットからサンプリングし、組み合わせたビット列のセットをSQDで後処理する。 このアプローチは、関連するチュートリアル「 Sample-based quantum diagonalization of a chemistry Hamiltonian(化学ハミルトニアンのサンプルに基づく量子対角化 )」で用いられたアプローチとは対照的で、サンプルは単一の発見的変分回路から引き出された。


要件

このチュートリアルを始める前に、以下のものがインストールされていることを確認してください:

  • Qiskit SDK v1.0 またはそれ以降、 可視化サポート付き
  • Qiskit Runtime v0.22 またはそれ以降 (pip install qiskit-ibm-runtime)
  • SQD Qiskit アドオン v0.11 以降 (pip install qiskit-addon-sqd)
  • ffsim v0.0.72 以降 (pip install ffsim)

小規模シミュレータの例

ステップ1:問題を量子回路にマッピングする

まず、位置基準でSIAMハミルトニアンを生成する。 ハミルトニアンは行列 hpqh_{pq} とテンソル hpqrsh_{pqrs} で表される。次に、これを運動量基底に回転させます。 ポジションベースでは、不純物を最初の部位に置く。 しかし、運動量基底に回転すると、他の軌道との相互作用を促進するために、不純物を中心サイトに移動させる。

import numpy as np
import pyscf.fci


def siam_hamiltonian(
    norb: int,
    hopping: float,
    onsite: float,
    hybridization: float,
    chemical_potential: float,
) -> tuple[np.ndarray, np.ndarray]:
    """Hamiltonian for the single-impurity Anderson model."""
    # Place the impurity on the first site
    impurity_orb = 0

    # One body matrix elements in the "position" basis
    h1e = np.zeros((norb, norb))
    np.fill_diagonal(h1e[:, 1:], -hopping)
    np.fill_diagonal(h1e[1:, :], -hopping)
    h1e[impurity_orb, impurity_orb + 1] = -hybridization
    h1e[impurity_orb + 1, impurity_orb] = -hybridization
    h1e[impurity_orb, impurity_orb] = chemical_potential

    # Two body matrix elements in the "position" basis
    h2e = np.zeros((norb, norb, norb, norb))
    h2e[impurity_orb, impurity_orb, impurity_orb, impurity_orb] = onsite

    return h1e, h2e


def momentum_basis(norb: int) -> np.ndarray:
    """Get the orbital rotation to change from the position to the momentum basis."""
    n_bath = norb - 1

    # Orbital rotation that diagonalizes the bath (non-interacting system)
    hopping_matrix = np.zeros((n_bath, n_bath))
    np.fill_diagonal(hopping_matrix[:, 1:], -1)
    np.fill_diagonal(hopping_matrix[1:, :], -1)
    _, vecs = np.linalg.eigh(hopping_matrix)

    # Expand to include impurity
    orbital_rotation = np.zeros((norb, norb))
    # Impurity is on the first site
    orbital_rotation[0, 0] = 1
    orbital_rotation[1:, 1:] = vecs

    # Move the impurity to the center
    new_index = n_bath // 2
    perm = np.r_[1 : (new_index + 1), 0, (new_index + 1) : norb]
    orbital_rotation = orbital_rotation[:, perm]

    return orbital_rotation


def rotated(
    h1e: np.ndarray, h2e: np.ndarray, orbital_rotation: np.ndarray
) -> tuple[np.ndarray, np.ndarray]:
    """Rotate the orbital basis of a Hamiltonian."""
    h1e_rotated = np.einsum(
        "ab,Aa,Bb->AB",
        h1e,
        orbital_rotation,
        orbital_rotation.conj(),
        optimize="greedy",
    )
    h2e_rotated = np.einsum(
        "abcd,Aa,Bb,Cc,Dd->ABCD",
        h2e,
        orbital_rotation,
        orbital_rotation.conj(),
        orbital_rotation,
        orbital_rotation.conj(),
        optimize="greedy",
    )
    return h1e_rotated, h2e_rotated


# Total number of spatial orbitals, including the bath sites and the impurity
# This should be an even number
norb = 8

# System is half-filled
nelec = (norb // 2, norb // 2)
# One orbital is the impurity, the rest are bath sites
n_bath = norb - 1

# Hamiltonian parameters
hybridization = 1.0
hopping = 1.0
onsite = 10.0
chemical_potential = -0.5 * onsite

# Generate Hamiltonian in position basis
h1e, h2e = siam_hamiltonian(
    norb=norb,
    hopping=hopping,
    onsite=onsite,
    hybridization=hybridization,
    chemical_potential=chemical_potential,
)

# Rotate to momentum basis
orbital_rotation = momentum_basis(norb)
h1e_momentum, h2e_momentum = rotated(h1e, h2e, orbital_rotation.T.conj())
# In the momentum basis, the impurity is placed in the center
impurity_index = n_bath // 2

# Use PySCF to compute the exact ground state energy
reference_energy, _ = pyscf.fci.direct_spin1.kernel(h1e, h2e, norb, nelec)

次に、クリロフ基底状態を生成する回路を生成する。 各スピン種について、初期状態 ψ0\ket{\psi_0} は、フェルミ準位に最も近い3つの電子の、状態 00001111|00\cdots 0011 \cdots 11\rangle から始まる最も近い4つの空モードへのすべての可能な励起の重ね合わせによって与えられ、7つの XXPlusYYGatesの適用によって実現される。 時間発展した状態は、2次のトロッターステップの連続適用によって生成される。

このモデルの詳細と回路の設計方法については、 "Quantum-Centric Algorithm for Sample-Based Krylov Diagonalization " を参照されたい。

from typing import Sequence

import ffsim
import scipy
from qiskit import QuantumCircuit, QuantumRegister
from qiskit.circuit import CircuitInstruction, Qubit
from qiskit.circuit.library import CPhaseGate, XGate, XXPlusYYGate


def prepare_initial_state(qubits: Sequence[Qubit], norb: int, nocc: int):
    """Prepare initial state."""
    assert norb >= 8
    x_gate = XGate()
    rot = XXPlusYYGate(0.5 * np.pi, -0.5 * np.pi)
    for i in range(nocc):
        yield CircuitInstruction(x_gate, [qubits[i]])
        yield CircuitInstruction(x_gate, [qubits[norb + i]])
    for i in range(3):
        for j in range(nocc - i - 1, nocc + i, 2):
            yield CircuitInstruction(rot, [qubits[j], qubits[j + 1]])
            yield CircuitInstruction(
                rot, [qubits[norb + j], qubits[norb + j + 1]]
            )
    yield CircuitInstruction(rot, [qubits[j + 1], qubits[j + 2]])
    yield CircuitInstruction(
        rot, [qubits[norb + j + 1], qubits[norb + j + 2]]
    )


def trotter_step(
    qubits: Sequence[Qubit],
    time_step: float,
    one_body_evolution: np.ndarray,
    h2e: np.ndarray,
    impurity_index: int,
    norb: int,
):
    """A Trotter step."""
    # Assume the two-body interaction is just the on-site interaction of the impurity
    onsite = h2e[
        impurity_index, impurity_index, impurity_index, impurity_index
    ]
    # Two-body evolution for half the time
    yield CircuitInstruction(
        CPhaseGate(-0.5 * time_step * onsite),
        [qubits[impurity_index], qubits[norb + impurity_index]],
    )
    # One-body evolution for the full time
    yield CircuitInstruction(
        ffsim.qiskit.OrbitalRotationJW(norb, one_body_evolution), qubits
    )
    # Two-body evolution for half the time
    yield CircuitInstruction(
        CPhaseGate(-0.5 * time_step * onsite),
        [qubits[impurity_index], qubits[norb + impurity_index]],
    )


# Time step
time_step = 0.2
# Number of Krylov basis states
krylov_dim = 8

# Initialize circuit
qubits = QuantumRegister(2 * norb, name="q")
circuit = QuantumCircuit(qubits)

# Generate initial state
for instruction in prepare_initial_state(qubits, norb=norb, nocc=norb // 2):
    circuit.append(instruction)
circuit.measure_all()

# Create list of circuits, starting with the initial state circuit
circuits = [circuit.copy()]

# Add time evolution circuits to the list
one_body_evolution = scipy.linalg.expm(-1j * time_step * h1e_momentum)
for i in range(krylov_dim - 1):
    # Remove measurements
    circuit.remove_final_measurements()
    # Append another Trotter step
    for instruction in trotter_step(
        qubits,
        time_step,
        one_body_evolution,
        h2e_momentum,
        impurity_index,
        norb,
    ):
        circuit.append(instruction)
    # Measure qubits
    circuit.measure_all()
    # Add a copy of the circuit to the list
    circuits.append(circuit.copy())
circuits[0].draw("mpl", scale=0.4, fold=-1)

Output:

Output of the previous code cell
circuits[-1].draw("mpl", scale=0.4, fold=-1)

Output:

Output of the previous code cell

ステップ2:量子実行のための問題最適化

次に、対象のハードウェアに合わせて回路を最適化します。 とりあえず、指定された数の量子ビットと、時間発展回路が自然に分解されるゲートセットを持つ汎用的なバックエンドを作成します。

from qiskit.providers.fake_provider import GenericBackendV2

backend = GenericBackendV2(
    2 * norb, basis_gates=["cp", "xx_plus_yy", "p", "x"]
)

さて、Qiskitを使って回路をターゲット・バックエンドにトランスパイルする。

from qiskit.transpiler import generate_preset_pass_manager

pass_manager = generate_preset_pass_manager(
    optimization_level=3, backend=backend
)
isa_circuits = pass_manager.run(circuits)

ステップ 3: Qiskit primitives を使用して実行する

ハードウェア実行用に回路を最適化した後、ターゲット・ハードウェア上で回路を実行し、基底状態のエネルギー推定用のサンプルを収集する準備が整った。 Samplerプリミティブを使用して各回路からビット列をサンプリングした後、すべての結果を1つのカウント辞書にまとめ、最もよくサンプリングされた上位20個のビット列をプロットする。

from qiskit.visualization import plot_histogram
from qiskit.primitives import StatevectorSampler

# Sample from the circuits
sampler = StatevectorSampler()
job = sampler.run(isa_circuits, shots=500)
from qiskit.primitives import BitArray

# Combine the shots from the individual Trotter circuits
bit_array = BitArray.concatenate_shots(
    [result.data.meas for result in job.result()]
)

plot_histogram(bit_array.get_counts(), number_to_keep=20)

Output:

Output of the previous code cell

ステップ4:後処理を行い、結果を所望の古典形式で返す

ここで、 diagonalize_fermionic_hamiltonian 関数を使用して SQD アルゴリズムを実行する。 この関数の引数の説明については、 APIドキュメントを参照のこと。

from qiskit_addon_sqd.fermion import (
    SCIResult,
    diagonalize_fermionic_hamiltonian,
)

# List to capture intermediate results
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):
        print(f"\tSubsample {i}")
        print(f"\t\tEnergy: {result.energy}")
        print(
            f"\t\tSubspace dimension: {np.prod(result.sci_state.amplitudes.shape)}"
        )


rng = np.random.default_rng(24)
result = diagonalize_fermionic_hamiltonian(
    h1e_momentum,
    h2e_momentum,
    bit_array,
    samples_per_batch=100,
    norb=norb,
    nelec=nelec,
    num_batches=3,
    max_iterations=5,
    symmetrize_spin=True,
    callback=callback,
    seed=rng,
)

Output:

Iteration 1
	Subsample 0
		Energy: -13.4222953188441
		Subspace dimension: 529
	Subsample 1
		Energy: -13.42237556285828
		Subspace dimension: 784
	Subsample 2
		Energy: -13.422045397387413
		Subspace dimension: 529
Iteration 2
	Subsample 0
		Energy: -13.422379583305478
		Subspace dimension: 900
	Subsample 1
		Energy: -13.422376197704326
		Subspace dimension: 841
	Subsample 2
		Energy: -13.422421162849295
		Subspace dimension: 1089
Iteration 3
	Subsample 0
		Energy: -13.422421164670345
		Subspace dimension: 1156
	Subsample 1
		Energy: -13.422421492737689
		Subspace dimension: 1156
	Subsample 2
		Energy: -13.422421205869572
		Subspace dimension: 1156
Iteration 4
	Subsample 0
		Energy: -13.422421494558726
		Subspace dimension: 1225
	Subsample 1
		Energy: -13.422421492737689
		Subspace dimension: 1156
	Subsample 2
		Energy: -13.422421492737689
		Subspace dimension: 1156

次のコードセルは、その結果をプロットします。 最初のグラフは、構成の復元反復回数に対する計算されたエネルギーを示しており、2番目のグラフは、最終反復後の各空間軌道における平均占有度を示しています。 これはごく小さな問題であるため、最初の反復計算ですでに正確なエネルギー値に非常に近づいています(Y軸の目盛りにご注目ください)。

import matplotlib.pyplot as plt

min_es = [
    min(result, key=lambda res: res.energy).energy
    for result in result_history
]
min_id, min_e = min(enumerate(min_es), key=lambda x: x[1])

# Data for energies plot
x1 = range(len(result_history))

# Data for avg spatial orbital occupancy
y2 = np.sum(result.orbital_occupancies, axis=0)
x2 = range(len(y2))

fig, axs = plt.subplots(1, 2, figsize=(12, 6))

# Plot energies
axs[0].plot(x1, min_es, label="energy", marker="o")
axs[0].set_xticks(x1)
axs[0].set_xticklabels(x1)
axs[0].axhline(
    y=reference_energy,
    color="#BF5700",
    linestyle="--",
    label="reference energy",
)
axs[0].set_title("Approximated Ground State Energy vs SQD Iterations")
axs[0].set_xlabel("Iteration Index", fontdict={"fontsize": 12})
axs[0].set_ylabel("Energy", fontdict={"fontsize": 12})
axs[0].legend()

# Plot orbital occupancy
axs[1].bar(x2, y2, width=0.8)
axs[1].set_xticks(x2)
axs[1].set_xticklabels(x2)
axs[1].set_title("Avg Occupancy per Spatial Orbital")
axs[1].set_xlabel("Orbital Index", fontdict={"fontsize": 12})
axs[1].set_ylabel("Avg Occupancy", fontdict={"fontsize": 12})

print(f"Reference energy: {reference_energy:.5f}")
print(f"SQD energy: {min_e:.5f}")
print(f"Absolute error: {abs(min_e - reference_energy):.5f}")
plt.tight_layout()
plt.show()

Output:

Reference energy: -13.42249
SQD energy: -13.42242
Absolute error: 0.00007
Output of the previous code cell

エネルギーを確認する

SQDによって返されるエネルギーは、真の基底状態のエネルギーの上限であることが保証されています。 SQDは基底状態を近似する状態ベクトルの係数も返すため、エネルギーの値を確認することができます。 以下のコードセルで示されているように、1粒子および2粒子の縮約密度行列を用いて、状態ベクトルからエネルギーを計算することができます。

rdm1 = result.sci_state.rdm(rank=1, spin_summed=True)
rdm2 = result.sci_state.rdm(rank=2, spin_summed=True)

energy = np.sum(h1e_momentum * rdm1) + 0.5 * np.sum(h2e_momentum * rdm2)

print(f"Recomputed energy: {energy:.5f}")

Output:

Recomputed energy: -13.42242

大規模なハードウェアの例

それでは、実際のQPUを使って、より大規模な例を実行してみましょう。 基準エネルギーについては、別途実施した DMRG 計算の結果を用いる。

from qiskit_ibm_runtime import SamplerV2 as Sampler
from qiskit_ibm_runtime import QiskitRuntimeService

# Model parameters
norb = 20
nelec = (norb // 2, norb // 2)
n_bath = norb - 1
hybridization = 1.0
hopping = 1.0
onsite = 10.0
chemical_potential = -0.5 * onsite

# Generate Hamiltonian and orbital rotation
h1e, h2e = siam_hamiltonian(
    norb=norb,
    hopping=hopping,
    onsite=onsite,
    hybridization=hybridization,
    chemical_potential=chemical_potential,
)
orbital_rotation = momentum_basis(norb)
h1e_momentum, h2e_momentum = rotated(h1e, h2e, orbital_rotation.T.conj())
impurity_index = n_bath // 2

# Set reference energy to DMRG value computed separately
reference_energy = -28.70659686

# Algorithm parameters
time_step = 0.2
krylov_dim = 8

# Construct circuits
qubits = QuantumRegister(2 * norb, name="q")
circuit = QuantumCircuit(qubits)
for instruction in prepare_initial_state(qubits, norb=norb, nocc=norb // 2):
    circuit.append(instruction)
circuit.measure_all()
circuits = [circuit.copy()]
one_body_evolution = scipy.linalg.expm(-1j * time_step * h1e_momentum)
for i in range(krylov_dim - 1):
    circuit.remove_final_measurements()
    for instruction in trotter_step(
        qubits,
        time_step,
        one_body_evolution,
        h2e_momentum,
        impurity_index,
        norb,
    ):
        circuit.append(instruction)
    circuit.measure_all()
    circuits.append(circuit.copy())

# Initialize hardware backend
service = QiskitRuntimeService()
backend = service.least_busy(
    operational=True, simulator=False, min_num_qubits=127
)
print(f"Using backend {backend.name}")

# Transpile to backend
pass_manager = generate_preset_pass_manager(
    optimization_level=3, backend=backend
)
isa_circuits = pass_manager.run(circuits)

# Sample from the circuits
sampler = Sampler(backend)
sampler.options.environment.job_tags = ["TUT_SKQD"]
job = sampler.run(isa_circuits, shots=500)

# Combine the shots from the individual Trotter circuits
bit_array = BitArray.concatenate_shots(
    [result.data.meas for result in job.result()]
)

# Run configuration recovery and diagonalization
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):
        print(f"\tSubsample {i}")
        print(f"\t\tEnergy: {result.energy}")
        print(
            f"\t\tSubspace dimension: {np.prod(result.sci_state.amplitudes.shape)}"
        )


rng = np.random.default_rng(24)
result = diagonalize_fermionic_hamiltonian(
    h1e_momentum,
    h2e_momentum,
    bit_array,
    samples_per_batch=100,
    norb=norb,
    nelec=nelec,
    num_batches=3,
    max_iterations=5,
    symmetrize_spin=True,
    callback=callback,
    seed=rng,
)


# Plot results
min_es = [
    min(result, key=lambda res: res.energy).energy
    for result in result_history
]
min_id, min_e = min(enumerate(min_es), key=lambda x: x[1])
x1 = range(len(result_history))
y2 = np.sum(result.orbital_occupancies, axis=0)
x2 = range(len(y2))
fig, axs = plt.subplots(1, 2, figsize=(12, 6))
axs[0].plot(x1, min_es, label="energy", marker="o")
axs[0].set_xticks(x1)
axs[0].set_xticklabels(x1)
axs[0].axhline(
    y=reference_energy,
    color="#BF5700",
    linestyle="--",
    label="reference energy",
)
axs[0].set_title("Approximated Ground State Energy vs SQD Iterations")
axs[0].set_xlabel("Iteration Index", fontdict={"fontsize": 12})
axs[0].set_ylabel("Energy", fontdict={"fontsize": 12})
axs[0].legend()
axs[1].bar(x2, y2, width=0.8)
axs[1].set_xticks(x2)
axs[1].set_xticklabels(x2)
axs[1].set_title("Avg Occupancy per Spatial Orbital")
axs[1].set_xlabel("Orbital Index", fontdict={"fontsize": 12})
axs[1].set_ylabel("Avg Occupancy", fontdict={"fontsize": 12})
print(f"Reference energy: {reference_energy:.5f}")
print(f"SQD energy: {min_e:.5f}")
print(f"Absolute error: {abs(min_e - reference_energy):.5f}")
plt.tight_layout()
plt.show()

Output:

Using backend ibm_boston
Iteration 1
	Subsample 0
		Energy: -28.63965951544449
		Subspace dimension: 9801
	Subsample 1
		Energy: -28.625588929202006
		Subspace dimension: 9409
	Subsample 2
		Energy: -28.647371834135498
		Subspace dimension: 8281
Iteration 2
	Subsample 0
		Energy: -28.67213260849567
		Subspace dimension: 29584
	Subsample 1
		Energy: -28.670340686158816
		Subspace dimension: 27225
	Subsample 2
		Energy: -28.669976379525988
		Subspace dimension: 31329
Iteration 3
	Subsample 0
		Energy: -28.68622875601382
		Subspace dimension: 36100
	Subsample 1
		Energy: -28.698569623143126
		Subspace dimension: 34225
	Subsample 2
		Energy: -28.694848533971882
		Subspace dimension: 33856
Iteration 4
	Subsample 0
		Energy: -28.69883392844593
		Subspace dimension: 42025
	Subsample 1
		Energy: -28.701289495200996
		Subspace dimension: 38025
	Subsample 2
		Energy: -28.699319594978245
		Subspace dimension: 45369
Iteration 5
	Subsample 0
		Energy: -28.701936886834154
		Subspace dimension: 51076
	Subsample 1
		Energy: -28.702468711812013
		Subspace dimension: 53824
	Subsample 2
		Energy: -28.702298147575938
		Subspace dimension: 52900
Reference energy: -28.70660
SQD energy: -28.70247
Absolute error: 0.00413
Output of the previous code cell

次のステップ

推奨事項

この作品が興味深かったなら、以下の資料も気に入るかもしれません:

このページは役に立ちましたか?
バグや誤字の報告、またはコンテンツの要求はGitHubで行ってください。