Skip to main content
IBM Quantum Platform

페르미온 격자 모델의 샘플 기반 Krylov 양자 대각화

사용량 추정치: Heron r2 프로세서에서 9초(참고: 이는 추정치일 뿐입니다. 런타임은 다를 수 있습니다.)


학습 성과

  • SQD Qiskit 애드온 을 사용하여 양자 처리 장치(QPU)에서 샘플링한 비트열을 바탕으로 격자 모델의 기저 상태 에너지를 근사하는 방법.
  • ffsim을 사용하여 페르미온 시뮬레이션을 위한 시간 발전 회로를 구축하는 방법.
  • 샘플 기반 크릴로프 대각화(SKQD) 알고리즘을 사용하여 후처리를 위해 여러 회로의 샘플을 결합하는 방법.

전제조건


배경

이 튜토리얼에서는 샘플 기반 양자 대각선화(SQD)를 사용하여 페르미온 격자 모델의 기저 상태 에너지를 추정하는 방법을 설명합니다. 특히 금속에 포함된 자성 불순물을 설명하는 데 사용되는 1차원 단일 불순물 앤더슨 모델(SIAM)을 연구합니다.

이 튜토리얼은 관련 튜토리얼인 화학 해밀턴의 샘플 기반 양자 대각선 화 와 유사한 워크플로우를 따릅니다. 그러나 중요한 차이점은 양자 회로가 구축되는 방식에 있습니다. 다른 튜토리얼은 잠재적으로 수백만 개의 상호 작용 조건이 있는 화학 해밀턴에게 매력적인 휴리스틱 변형 안사츠를 사용합니다. 반면에 이 튜토리얼에서는 해밀턴의 시간 진화에 근사한 회로를 사용합니다. 이러한 회로는 깊이가 깊을 수 있으므로 격자 모델에 적용하는 데 이 접근 방식이 더 적합합니다. 이러한 회로에 의해 준비된 상태 벡터는 크릴로프 부분 공간의 기초를 형성하며, 결과적으로 알고리즘은 적절한 가정 하에서 증명 가능하고 효율적으로 기저 상태로 수렴합니다.

이 튜토리얼에서 사용된 접근 방식은 SQD와 크릴로프 양자 대각선화(KQD) 에 사용된 기술을 조합한 것으로 볼 수 있습니다. 이 결합된 접근 방식을 샘플 기반 크릴로프 양자 대각선화(SQKD)라고도 합니다. KQD 방법에 대한 튜토리얼은 격자 해밀턴의 크릴로프 양자 대각선화를 참조하세요.

이 튜토리얼은 "샘플 기반 크릴로프 대각선화를 위한 양자 중심 알고리즘" 을 기반으로 하며, 자세한 내용은 이 튜토리얼을 참조할 수 있습니다.

단일 불순물 앤더슨 모델 (SIAM)

1차원 SIAM 해밀턴은 세 개의 항의 합입니다:

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}} 배스 사이트의 페르미온 생성/소멸 연산자 σ\sigma, d^σ/d^σ\hat{d}^\dagger_{\sigma}/\hat{d}_{\sigma} 은 불순물 모드의 생성/소멸 연산자이며, n^dσ=d^σd^σ\hat{n}_{d\sigma} = \hat{d}^\dagger_{\sigma} \hat{d}_{\sigma}, tt, UU, VV 은 호핑, 온사이트, 혼성화 상호작용을 나타내는 실수이고, ε\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 은 페르미온 생성 및 소멸 연산자에서 2진법인 1진법으로 구성되고 H2H_2 은 2진법인 2진법으로 구성됩니다. 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 차 트로터-스즈키 분해를 사용합니다,

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}.

조던-위그너 변환에서 H2H_2 에 의한 시간 진화는 불순물 부위에서 스핀업 궤도와 스핀다운 궤도 사이의 단일 CPhase 게이트에 해당합니다. H1H_1 은 이차 페르미온 해밀턴이므로 H1H_1 에 의한 시간 진화는 궤도 회전에 해당합니다.

크릴로프 기저는 {ψk}k=0D1\{ |\psi_k\rangle \}_{k=0}^{D-1}, 여기서 DD 은 크릴로프 부분 공간의 차원이며, 단일 트로터 단계의 반복 적용으로 형성됩니다

ψ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로 후처리합니다. 이 접근법은 관련 튜토리얼인 화학 해밀턴의 샘플 기반 양자 대각선화에서 사용된 것과는 대조적으로, 단일 휴리스틱 변형 회로에서 샘플을 도출했습니다.


요구사항

이 튜토리얼을 시작하기 전에 다음이 설치되어 있는지 확인하세요:

  • 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} 는 페르미 레벨에 가장 가까운 세 전자의 가능한 모든 여기들을 00001111|00\cdots 0011 \cdots 11\rangle 상태에서 시작하여 가장 가까운 4개의 빈 모드로 중첩하여 주어지며, 7개의 XXPlusYY게이트를 적용하여 실현됩니다. 시간 진화 상태는 2차 트로터 단계를 연속적으로 적용하여 생성됩니다.

이 모델과 회로 설계 방식에 대한 자세한 설명은 "샘플 기반 크릴로프 대각선화를 위한 양자 중심 알고리즘" 을 참조하세요.

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"]
)

이제 키스킷을 사용하여 대상 백엔드로 회로를 트랜스파일합니다.

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를 사용하여 실행합니다

하드웨어 실행을 위해 회로를 최적화한 후, 대상 하드웨어에서 실행하고 접지 상태 에너지 추정을 위한 샘플을 수집할 준비가 되었습니다. 샘플러 프리미티브를 사용하여 각 회로에서 비트스트링을 샘플링한 후, 모든 결과를 단일 카운트 사전으로 결합하고 가장 일반적으로 샘플링된 상위 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

다음 코드 셀은 결과를 그래프로 표시합니다. 첫 번째 그래프는 구성 복원 반복 횟수에 따른 계산된 에너지를 나타내며, 두 번째 그래프는 최종 반복 후 각 공간 궤도의 평균 점유율을 보여줍니다. 이 문제가 매우 간단하기 때문에, 첫 번째 반복만으로도 정확한 에너지 값에 매우 근접하게 됩니다(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에서 버그, 오타를 보고하거나 컨텐츠를 요청하십시오.