Diagonalisasi kuantum Krylov untuk Hamiltonian kisi
Estimasi penggunaan: 70 menit di prosesor Heron atau Nighthawk (CATATAN: Ini hanya estimasi. Waktu aktual bisa berbeda.)
Hasil pembelajaran
-
Cara menafsirkan Krylov quantum diagonalization (KQD) sebagai pembelajaran fungsi Hamiltonian terbatas yang bertindak sebagai filter spektral.
-
Cara membangun matriks Hamiltonian dan overlap yang diproyeksikan dengan pengukuran extended swap-test.
-
Cara menyelesaikan generalized eigenvalue problem (GEVP) yang dihasilkan dan memperoleh estimasi energi keadaan dasar untuk suatu Hamiltonian kisi.
Prasyarat
Latar Belakang
Tutorial ini mendemonstrasikan cara mengimplementasikan diagonalisasi kuantum Krylov (KQD) dalam konteks pola Qiskit. Kamu akan mempelajari teori di balik algoritma ini terlebih dahulu, lalu melihat demonstrasi eksekusinya di QPU.
Mengestimasi sifat energi rendah dari Hamiltonian banyak-benda adalah tugas utama dalam simulasi kuantum. Sebagai contoh, energi keadaan dasar dan eksitasi tingkat rendah berkaitan langsung dengan stabilitas kimia, tata magnetik, transisi fase kuantum, dan respons material. Pada komputer klasik, dimensi ruang Hilbert tumbuh secara eksponensial seiring bertambahnya jumlah orbital atau spin, sehingga diagonalisasi langsung dengan cepat menjadi tidak praktis.
Ada beberapa pendekatan komputasi kuantum untuk masalah ini. Metode variasional jangka pendek, seperti variational quantum eigensolver (VQE), menggunakan circuit berparameter yang relatif dangkal, tetapi memerlukan loop optimasi klasik nonlinear dengan banyak evaluasi quantum circuit. Di sisi lain, quantum phase estimation (QPE) memberikan rute yang lebih langsung untuk estimasi eigenvalue dengan jaminan yang ketat, tetapi QPE standar memerlukan circuit koheren yang panjang dan terutama cocok untuk komputer kuantum fault-tolerant. KQD berada di antara kedua pendekatan ini: ia menggunakan evolusi Hamiltonian real-time, seperti pada algoritma berbasis phase-estimation, tetapi menggantikan phase estimation penuh dengan masalah eigenvalue terproyeksi yang ringkas yang bisa diselesaikan secara klasik.
Perhatikan Hamiltonian -qubit dan keadaan referensi . Metode KQD membangun subruang Krylov dari keadaan yang berevolusi secara real-time,
di mana adalah dimensi Krylov dan adalah langkah waktu. Setiap keadaan dalam subruang Krylov kemudian direpresentasikan sebagai kombinasi linear dari keadaan basis ini,
where the denominator normalizes the state.
Dengan aljabar sederhana, kita bisa melihat bahwa energi yang bersesuaian ditulis sebagai Rayleigh quotient,
Di sini, matriks dan ,
mendefinisikan matriks overlap dan Hamiltonian yang diproyeksikan. Entrinya diestimasi menggunakan pengukuran quantum circuit.
Kita bertujuan menemukan koefisien yang menghasilkan minimum:
Menurut teorema Rayleigh-Ritz, minimisasi ini setara dengan menyelesaikan generalized eigenvalue problem (GEVP),
Perhatikan bahwa dimensi bisa cukup kecil sehingga komputer klasik bisa menyelesaikan GEVP.
Ini adalah prinsip variasional yang sama yang digunakan dalam diagonalisasi subruang klasik, tetapi di sini keadaan basisnya dihasilkan oleh evolusi waktu kuantum. Dibandingkan dengan VQE, KQD biasanya memerlukan circuit yang lebih dalam karena bergantung pada evolusi real-time. Sebagai imbalannya, KQD menghindari optimasi parameter nonlinear dan eksekusi perangkat keras kuantum iteratif, dan secara sistematis meningkat seiring diperbesarnya subruang yang diproyeksikan. Algoritma ini telah didemonstrasikan dalam skala besar pada perangkat keras kuantum yang ada [2], dan kinerjanya bisa dianalisis dengan jaminan yang bisa dibuktikan [1].
Persyaratan
Sebelum memulai tutorial ini, pastikan kamu sudah menginstal yang berikut:
-
Qiskit SDK v2.3 atau lebih baru dengan dukungan visualisasi
-
Qiskit Runtime v0.22 atau lebih baru (
pip install qiskit-ibm-runtime) -
SciPy (
pip install scipy) -
Matplotlib (
pip install matplotlib) -
Pandas (
pip install pandas)
Eksekusi perangkat keras memerlukan qiskit-ibm-runtime dan akses ke akun IBM Quantum®.
Pengaturan
Sel pengaturan mengimpor modul yang diperlukan dan mendefinisikan fungsi helper untuk alur kerja ini:
-
membangun Hamiltonian Heisenberg;
-
menyelesaikan GEVP yang di-threshold;
-
evaluasi filter Krylov yang telah dipelajari;
-
konversi nilai-nilai filter menjadi bobot spektral;
-
plot distribusi energi referensi dan yang telah difilter.
# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy pandas qiskit qiskit-ibm-runtime scipy
from __future__ import annotations
import warnings
import numpy as np
import pandas as pd
import scipy.linalg as la
import matplotlib.pyplot as plt
from qiskit import QuantumCircuit, transpile
from qiskit.circuit import Parameter
from qiskit.circuit.library import PauliEvolutionGate
from qiskit.primitives import StatevectorEstimator
from qiskit.quantum_info import Operator, SparsePauliOp
from qiskit.synthesis import LieTrotter, SuzukiTrotter
from qiskit.transpiler import PassManager, Layout
from qiskit.transpiler.passes import CommutativeOptimization
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService, EstimatorV2, Batch
from qiskit_ibm_runtime.fake_provider import FakeMarrakesh
warnings.filterwarnings("ignore")
def make_heisenberg_hamiltonian(
num_qubits: int,
coupling: float = 1.0,
) -> SparsePauliOp:
"""Make a Heisenberg Hamiltonian for a 1D chain of qubits with nearest-neighbor interactions."""
terms: list[tuple[str, complex]] = []
def append_term(q0: int, q1: int, pauli: str):
label = ["I"] * num_qubits
label[num_qubits - 1 - q0] = pauli[0]
label[num_qubits - 1 - q1] = pauli[1]
terms.append(("".join(label), coupling))
for pauli in ("XX", "YY", "ZZ"):
for q in range(num_qubits - 1):
append_term(q, q + 1, pauli)
return SparsePauliOp.from_list(terms).simplify()
def _basis_state_transition_amplitude_sparse(
hamiltonian: SparsePauliOp,
bra_state: int,
ket_state: int,
) -> complex:
"""Evaluate <bra_state|H|ket_state> for computational-basis states."""
num_qubits = hamiltonian.num_qubits
amplitude = 0.0 + 0.0j
for pauli, coeff in zip(hamiltonian.paulis, hamiltonian.coeffs):
new_state = ket_state
phase = 1.0 + 0.0j
for q in range(num_qubits):
x = bool(pauli.x[q])
z = bool(pauli.z[q])
if not x and not z:
continue
bit = (new_state >> q) & 1
if x and z:
# Y|0> = i|1>, Y|1> = -i|0>
phase *= 1j if bit == 0 else -1j
new_state ^= 1 << q
elif x:
new_state ^= 1 << q
else:
# Z|0> = |0>, Z|1> = -|1>
if bit:
phase *= -1
if new_state == bra_state:
amplitude += coeff * phase
return amplitude
def basis_state_expectation_sparse(
hamiltonian: SparsePauliOp,
bitstring: str,
) -> complex:
"""Evaluate <bitstring|H|bitstring>."""
state = int(bitstring, 2)
return _basis_state_transition_amplitude_sparse(
hamiltonian,
bra_state=state,
ket_state=state,
)
def diagonalize_single_1_subspace(
hamiltonian: SparsePauliOp,
) -> np.ndarray:
"""Diagonalize the Hamiltonian projected onto the single-excitation subspace."""
num_qubits = hamiltonian.num_qubits
# Integer basis states |...010...>, with the excitation at qubit k.
basis = [1 << k for k in range(num_qubits)]
h_single = np.empty((num_qubits, num_qubits), dtype=complex)
for row, bra_state in enumerate(basis):
for col, ket_state in enumerate(basis):
h_single[row, col] = _basis_state_transition_amplitude_sparse(
hamiltonian,
bra_state=bra_state,
ket_state=ket_state,
)
# Remove floating-point-level asymmetry.
h_single = 0.5 * (h_single + h_single.conj().T)
evals, _ = np.linalg.eigh(h_single)
return np.real(evals)
def simple_transpilation(circuit: QuantumCircuit) -> QuantumCircuit:
"""Transpilation to simplify the circuit"""
pm = PassManager(
[
CommutativeOptimization(),
]
)
circuit = transpile(circuit, optimization_level=3)
circuit = pm.run(circuit)
return circuit
def summarize_circuit(circuit: QuantumCircuit) -> dict[str, int | str]:
"""Summarize the circuit with depth, size, and 2-qubit gate information."""
two_qubit_total = sum(
inst.operation.num_qubits == 2 for inst in circuit.data
)
two_qubit_depth = circuit.depth(lambda x: x[0].num_qubits == 2)
return {
"depth": circuit.depth(),
"size": circuit.size(),
"2q gates": two_qubit_total,
"2q depth": two_qubit_depth,
}
def solve_thresholded_gevp(
h_matrix: np.ndarray,
s_matrix: np.ndarray,
threshold: float = 1e-10,
) -> tuple[float, np.ndarray, int]:
"""Solve H c = E S c using canonical orthogonalization of S."""
s_vals, s_vecs = la.eigh(s_matrix)
valid = s_vals > threshold
if not np.any(valid):
raise ValueError(
"All overlap eigenvalues were removed by thresholding."
)
keep = valid
orthogonalizer = s_vecs[:, keep] @ np.diag(1.0 / np.sqrt(s_vals[keep]))
h_orth = orthogonalizer.conj().T @ h_matrix @ orthogonalizer
h_orth = 0.5 * (h_orth + h_orth.conj().T)
eigvals, eigvecs = la.eigh(h_orth)
coeffs = orthogonalizer @ eigvecs[:, 0]
normalization = np.sqrt(np.real(coeffs.conj().T @ s_matrix @ coeffs))
coeffs /= normalization
return float(np.real(eigvals[0])), coeffs, int(np.sum(keep))
Di bagian pertama tutorial ini, kita mendemonstrasikan metode KQD dengan menggunakan simulator statevector lokal. Selanjutnya, kita menggunakan backend kuantum nyata untuk mengatasi masalah berskala utilitas.
Kita juga mendefinisikan fake backend untuk mendemonstrasikan transpilasi khusus backend dan memeriksa circuit yang dihasilkan.
try:
service = QiskitRuntimeService()
except Exception:
QiskitRuntimeService.save_account(
token="<api_token>", instance="<instance>", overwrite=True
)
service = QiskitRuntimeService()
backend = FakeMarrakesh()
Contoh simulator skala kecil
Langkah 1: Petakan input klasik ke masalah kuantum
Hamiltonian dan keadaan referensi
Contoh ini menggunakan rantai Heisenberg batas terbuka 12-qubit (),
dengan keadaan produk eksitasi tunggal
sebagai keadaan referensi. Karena Hamiltonian Heisenberg yang didefinisikan di atas mengonservasi jumlah total eksitasi, keadaan referensi tetap berada dalam subruang eksitasi tunggal, yang dimensinya hanya bertumbuh secara linear terhadap jumlah qubit. Oleh karena itu, kita dapat menghitung energi keadaan dasar yang eksak secara efisien, dengan mendiagonalisasi Hamiltonian yang dibatasi pada subruang tersebut, dan menggunakannya murni sebagai benchmark diagnostik untuk estimasi KQD. Alur kerja KQD sendiri mengestimasi elemen matriks terproyeksi dengan menggunakan Qiskit primitives dan menyelesaikan masalah terproyeksi yang dihasilkan secara klasik.
# Problem definition for the simulator example
num_qubits = 12
hamiltonian = make_heisenberg_hamiltonian(num_qubits=num_qubits, coupling=1.0)
ref_bitstring = "000001000000"
ref_energy = basis_state_expectation_sparse(hamiltonian, ref_bitstring)
print("Hamiltonian:")
print(hamiltonian)
print(f"Reference state: |{ref_bitstring}>")
print("Reference energy: ", ref_energy)
Hamiltonian:
SparsePauliOp(['IIIIIIIIIIXX', 'IIIIIIIIIXXI', 'IIIIIIIIXXII', 'IIIIIIIXXIII', 'IIIIIIXXIIII', 'IIIIIXXIIIII', 'IIIIXXIIIIII', 'IIIXXIIIIIII', 'IIXXIIIIIIII', 'IXXIIIIIIIII', 'XXIIIIIIIIII', 'IIIIIIIIIIYY', 'IIIIIIIIIYYI', 'IIIIIIIIYYII', 'IIIIIIIYYIII', 'IIIIIIYYIIII', 'IIIIIYYIIIII', 'IIIIYYIIIIII', 'IIIYYIIIIIII', 'IIYYIIIIIIII', 'IYYIIIIIIIII', 'YYIIIIIIIIII', 'IIIIIIIIIIZZ', 'IIIIIIIIIZZI', 'IIIIIIIIZZII', 'IIIIIIIZZIII', 'IIIIIIZZIIII', 'IIIIIZZIIIII', 'IIIIZZIIIIII', 'IIIZZIIIIIII', 'IIZZIIIIIIII', 'IZZIIIIIIIII', 'ZZIIIIIIIIII'],
coeffs=[1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j,
1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j,
1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j,
1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j])
Reference state: |000001000000>
Reference energy: (7+0j)
Atur parameter untuk algoritma
Berdasarkan batas atas pada norma Hamiltonian, Ref. [1] secara heuristik menyarankan langkah waktu sebagai . Karena norma spektral sulit dihitung, kita malah menggunakan batas atasnya:
Kita menetapkan dimensi Krylov ke dan jumlah langkah Trotter per langkah waktu ke : ruang Krylov yang cukup besar untuk mengurai spektrum berenergi rendah sambil menjaga circuit terdalam () tetap terjangkau, dan langkah Trotter yang cukup untuk menjaga error diskretisasi tetap kecil pada circuit terdalam tersebut.
dt = np.pi / (3 * (num_qubits - 1))
print("dt in Krylov basis: ", dt)
krylov_dim = 10
num_trotter_steps = 5
dt in Krylov basis: 0.09519977738150888
Membangun circuit
Di sini, kita membangun circuit untuk mengestimasi elemen matriks dan . Karena semua pangkat dari berkomutasi, kita memperoleh
Matriks yang elemennya bergantung pada selisih indeks seperti ini disebut Toeplitz dan dapat direkonstruksi dari elemen-elemen baris pertama yang diindeks oleh .
Di sini, kita menyajikan circuit, yang disebut extended-swap-test, yang menyiapkan
di mana .
Keadaan referensi
Kita menyiapkan keadaan referensi .
qc_ref = QuantumCircuit(num_qubits)
for i, b in enumerate(reversed(ref_bitstring)):
if b == "1":
qc_ref.x(i)
display(qc_ref.draw("mpl", scale=0.5))
Evolusi waktu
Realisasikan operator evolusi waktu yang dihasilkan oleh Hamiltonian, diaproksimasi dengan Lie-Trotterization sederhana.
t = Parameter("t")
evol_gate = PauliEvolutionGate(
hamiltonian,
time=t,
synthesis=LieTrotter(reps=num_trotter_steps),
label="U(t)",
)
# Synthesize U(t) first and then control the synthesized circuit.
# This makes the controlled structure visible in the circuit drawer.
evolution_circuit = QuantumCircuit(num_qubits, name="U(t)")
evolution_circuit.append(evol_gate, range(num_qubits))
evolution_circuit = simple_transpilation(evolution_circuit)
# Make a controlled version of the evolution circuit.
controlled_evolution_gate = evolution_circuit.to_gate(label="U(t)").control(
1, label="C-U(t)"
)
display(
evolution_circuit.assign_parameters({t: 1.5}).draw(
"mpl", scale=0.5, fold=-1
)
)

Circuit extended swap test [3]
Circuit pertama-tama menyiapkan keadaan referensi pada register sistem sementara ancilla tetap berada di :
Kemudian, menerapkan gate Hadamard pada ancilla menciptakan superposisi koheren dari dua cabang:
Akhirnya, gate evolusi waktu terkontrol diterapkan:
hanya ketika ancilla berada di cabang . Oleh karena itu,
Pada blok kode berikutnya, kita mengimplementasikan:
yang akan ditetapkan sebagai untuk pada langkah eksekusi.
ancilla = 0
system_qubits = list(range(1, num_qubits + 1))
extended_swap_test = QuantumCircuit(num_qubits + 1)
# Append state preparation part
extended_swap_test = extended_swap_test.compose(qc_ref, system_qubits)
# Prepare the coherent branch label, (|0> + |1>) / sqrt(2).
extended_swap_test.h(ancilla)
# Apply U(t) only to the |1> branch of the ancilla.
extended_swap_test.append(
controlled_evolution_gate, [ancilla] + system_qubits
)
# Decompose once more for visualization so that control bullets are visible.
display(extended_swap_test.draw("mpl", fold=-1))
Observable
Untuk observable sistem Hermitian apa pun, di sini kita menetapkan observable yang akan dihitung:
Ini karena memberikan elemen overlap , sementara memberikan elemen Hamiltonian .
Dengan menggunakan , kita memperoleh
Demikian pula, dengan menggunakan ,
Oleh karena itu, kita memperoleh
Akhirnya, untuk setiap keadaan , kita perlu mengukur:
n_qubits = hamiltonian.num_qubits
observable_labels = [
"Re S_0d",
"Im S_0d",
"Re H_0d",
"Im H_0d",
]
# X ⊗ I and Y ⊗ I.
# Qiskit's Pauli-label convention places qubit 0 on the rightmost character,
# so the ancilla Pauli is appended to the right.
obs_x_identity = SparsePauliOp("I" * n_qubits + "X")
obs_y_identity = SparsePauliOp("I" * n_qubits + "Y")
# X ⊗ H and Y ⊗ H.
obs_x_hamiltonian = SparsePauliOp.from_list(
[
(label + "X", coeff)
for label, coeff in zip(
hamiltonian.paulis.to_labels(),
hamiltonian.coeffs,
)
]
)
obs_y_hamiltonian = SparsePauliOp.from_list(
[
(label + "Y", coeff)
for label, coeff in zip(
hamiltonian.paulis.to_labels(),
hamiltonian.coeffs,
)
]
)
observables = [
obs_x_identity,
obs_y_identity,
obs_x_hamiltonian,
obs_y_hamiltonian,
]
for obs, label in zip(observables, observable_labels):
print(f"Observable: {label}")
print(obs)
print()
Observable: Re S_0d
SparsePauliOp(['IIIIIIIIIIIIX'],
coeffs=[1.+0.j])
Observable: Im S_0d
SparsePauliOp(['IIIIIIIIIIIIY'],
coeffs=[1.+0.j])
Observable: Re H_0d
SparsePauliOp(['IIIIIIIIIIXXX', 'IIIIIIIIIXXIX', 'IIIIIIIIXXIIX', 'IIIIIIIXXIIIX', 'IIIIIIXXIIIIX', 'IIIIIXXIIIIIX', 'IIIIXXIIIIIIX', 'IIIXXIIIIIIIX', 'IIXXIIIIIIIIX', 'IXXIIIIIIIIIX', 'XXIIIIIIIIIIX', 'IIIIIIIIIIYYX', 'IIIIIIIIIYYIX', 'IIIIIIIIYYIIX', 'IIIIIIIYYIIIX', 'IIIIIIYYIIIIX', 'IIIIIYYIIIIIX', 'IIIIYYIIIIIIX', 'IIIYYIIIIIIIX', 'IIYYIIIIIIIIX', 'IYYIIIIIIIIIX', 'YYIIIIIIIIIIX', 'IIIIIIIIIIZZX', 'IIIIIIIIIZZIX', 'IIIIIIIIZZIIX', 'IIIIIIIZZIIIX', 'IIIIIIZZIIIIX', 'IIIIIZZIIIIIX', 'IIIIZZIIIIIIX', 'IIIZZIIIIIIIX', 'IIZZIIIIIIIIX', 'IZZIIIIIIIIIX', 'ZZIIIIIIIIIIX'],
coeffs=[1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j,
1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j,
1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j,
1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j])
Observable: Im H_0d
SparsePauliOp(['IIIIIIIIIIXXY', 'IIIIIIIIIXXIY', 'IIIIIIIIXXIIY', 'IIIIIIIXXIIIY', 'IIIIIIXXIIIIY', 'IIIIIXXIIIIIY', 'IIIIXXIIIIIIY', 'IIIXXIIIIIIIY', 'IIXXIIIIIIIIY', 'IXXIIIIIIIIIY', 'XXIIIIIIIIIIY', 'IIIIIIIIIIYYY', 'IIIIIIIIIYYIY', 'IIIIIIIIYYIIY', 'IIIIIIIYYIIIY', 'IIIIIIYYIIIIY', 'IIIIIYYIIIIIY', 'IIIIYYIIIIIIY', 'IIIYYIIIIIIIY', 'IIYYIIIIIIIIY', 'IYYIIIIIIIIIY', 'YYIIIIIIIIIIY', 'IIIIIIIIIIZZY', 'IIIIIIIIIZZIY', 'IIIIIIIIZZIIY', 'IIIIIIIZZIIIY', 'IIIIIIZZIIIIY', 'IIIIIZZIIIIIY', 'IIIIZZIIIIIIY', 'IIIZZIIIIIIIY', 'IIZZIIIIIIIIY', 'IZZIIIIIIIIIY', 'ZZIIIIIIIIIIY'],
coeffs=[1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j,
1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j,
1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j,
1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j])
Untuk estimasi elemen matriks , jumlah suku Pauli jauh lebih besar dibandingkan untuk elemen matriks .
Kita sekarang dapat mengurangi jumlah suku Hamiltonian yang diukur dengan menggunakan teknik pergeseran [4]. Kita memisahkan Hamiltonian sebagai:
di mana dipilih sedemikian rupa sehingga keadaan referensi merupakan eigenstate-nya,
Then,
Here,
adalah elemen matriks Hamiltonian yang telah digeser. Oleh karena itu, kita hanya perlu mengukur dan . Kontribusi dari direkonstruksi secara klasik dengan menggunakan elemen matriks overlap yang telah diukur.
Dalam contoh ini, pilihan yang alami adalah bagian diagonal dari Hamiltonian Heisenberg,
Karena keadaan referensi adalah keadaan basis komputasi, ia merupakan eigenstate dari setiap suku .
Namun, pilihan yang lebih menguntungkan adalah menyertakan tidak hanya suku diagonal , tetapi juga suku yang menganihilasi keadaan referensi.
Untuk setiap pasangan tetangga, operator memenuhi
and
Oleh karena itu, suku berkontribusi hanya ketika dua qubit tetangga memiliki okupansi yang berbeda pada bitstring referensi. Jika kedua qubit sama-sama atau sama-sama , suku tersebut menganihilasi keadaan referensi dan juga dapat digeser keluar.
Misalkan , di mana . Dengan demikian, kita dapat memilih
Operator ini masih memenuhi
karena suku bekerja secara diagonal pada , sementara suku yang telah digeser memberikan nol. Eigenvalue yang bersesuaian oleh karena itu ditentukan hanya oleh suku ,
Dengan pilihan ini, Hamiltonian yang telah digeser menjadi:
Sebagai hasilnya, hanya edge dengan okupansi yang berbeda pada keadaan referensi yang perlu diukur. Semua suku dan semua suku yang tidak aktif direkonstruksi melalui kontribusi overlap , atau memberikan kontribusi nol berdasarkan konstruksinya.
Ini menghasilkan observable yang lebih kecil dibandingkan hanya menggeser bagian diagonal. Secara khusus, untuk keadaan referensi basis komputasi dengan eksitasi yang terlokalisasi, hanya edge yang berdekatan dengan eksitasi yang tersisa dalam . Oleh karena itu, jumlah suku Pauli dalam dan dapat dikurangi secara substansial, sementara elemen matriks yang direkonstruksi
tetap sama persis.
def make_reduced_heisenberg_observables(
ref_bitstring: str,
coupling: float = 1.0,
) -> tuple[SparsePauliOp, SparsePauliOp, float]:
"""Build X⊗(H-T), Y⊗(H-T), and tau."""
n_qubits = len(ref_bitstring)
shifted_terms: list[tuple[str, complex]] = []
tau = 0.0
def bit(q: int) -> str:
return ref_bitstring[n_qubits - 1 - q]
def append_term(q0: int, q1: int, pauli: str):
label = ["I"] * n_qubits
label[n_qubits - 1 - q0] = pauli[0]
label[n_qubits - 1 - q1] = pauli[1]
shifted_terms.append(("".join(label), coupling))
for q in range(n_qubits - 1):
same_occupation = bit(q) == bit(q + 1)
# ZZ contribution to tau
tau += coupling * (1.0 if same_occupation else -1.0)
# XX + YY survives only for opposite occupations.
if not same_occupation:
append_term(q, q + 1, "XX")
append_term(q, q + 1, "YY")
if shifted_terms:
obs_x_shifted_hamiltonian = SparsePauliOp.from_list(
[(label + "X", coeff) for label, coeff in shifted_terms]
)
obs_y_shifted_hamiltonian = SparsePauliOp.from_list(
[(label + "Y", coeff) for label, coeff in shifted_terms]
)
else:
obs_x_shifted_hamiltonian = SparsePauliOp(
"I" * n_qubits + "X", coeffs=[0.0]
)
obs_y_shifted_hamiltonian = SparsePauliOp(
"I" * n_qubits + "Y", coeffs=[0.0]
)
return obs_x_shifted_hamiltonian, obs_y_shifted_hamiltonian, tau
obs_x_shifted_hamiltonian, obs_y_shifted_hamiltonian, shift_tau = (
make_reduced_heisenberg_observables(ref_bitstring)
)
print("Observable: Re shifted H_0d")
print(obs_x_shifted_hamiltonian)
print()
print("Observable: Im shifted H_0d")
print(obs_y_shifted_hamiltonian)
print()
print("tau =", shift_tau)
observables = [
obs_x_identity,
obs_y_identity,
obs_x_shifted_hamiltonian,
obs_y_shifted_hamiltonian,
]
observable_labels = [
"Re S_0d",
"Im S_0d",
"Re shifted H_0d",
"Im shifted H_0d",
]
Observable: Re shifted H_0d
SparsePauliOp(['IIIIIXXIIIIIX', 'IIIIIYYIIIIIX', 'IIIIXXIIIIIIX', 'IIIIYYIIIIIIX'],
coeffs=[1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j])
Observable: Im shifted H_0d
SparsePauliOp(['IIIIIXXIIIIIY', 'IIIIIYYIIIIIY', 'IIIIXXIIIIIIY', 'IIIIYYIIIIIIY'],
coeffs=[1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j])
tau = 7.0
Langkah 2: Optimalkan masalah untuk eksekusi perangkat keras kuantum
Sekarang kita mengubah circuit extended-swap-test yang abstrak menjadi template yang berorientasi hardware. Sebelum itu, kita terlebih dahulu mengoptimalkan lebih lanjut circuit tersebut pada level abstrak.
Membandingkan urutan suku Hamiltonian
Pertama, kita membandingkan berbagai urutan suku Pauli dalam Hamiltonian Heisenberg untuk simulasi Hamiltonian. Hamiltonian itu sendiri tidak berubah, tetapi urutan tersebut memengaruhi bagaimana circuit product-formula dihasilkan dan seberapa besar strukturnya dapat diparalelkan dalam circuit. Sebagai contoh, urutan naif mendaftarkan semua suku tetangga terdekat, kemudian semua suku , lalu semua suku . Hal ini menempatkan edge yang berdekatan seperti dan bersebelahan satu sama lain, sehingga tidak dapat dieksekusi secara paralel. Urutan even-then-odd mengunjungi edge genap yang saling lepas terlebih dahulu, diikuti oleh edge ganjil, yang menyingkap layer dua-qubit paralel. Urutan even-odd edge-grouped melangkah lebih jauh lagi: untuk setiap edge, ia menjaga suku , , dan lokal tetap bersama, sambil tetap mengunjungi edge genap sebelum edge ganjil. Kita mengharapkan urutan even-then-odd dan even-odd edge-grouped mengurangi kedalaman circuit dengan menyingkap layer dua-qubit paralel, dan urutan edge-grouped juga mengurangi error Trotter karena interaksi dua-qubit lokal pada edge yang sama ditangani sebagai blok yang kompak.

Urutan Hamiltonian juga memengaruhi error Trotter. Jika kita menempatkan suku-suku yang tidak berkomutasi bersebelahan, transisi basis terjadi lebih sering, yang menyebabkan lebih banyak error Trotter. Dengan mengelompokkan suku-suku yang memerlukan transformasi basis-Pauli yang sama, perubahan basis yang redundan dapat dihindari.
Di sini, perbandingan menggunakan waktu evolusi terbesar yang muncul dalam estimasi Krylov baris pertama, dengan menggunakan kondisi transpilasi yang sama.
Untuk mengukur error Trotter, kita menggunakan process infidelity antara circuit yang di-Trotterisasi dan evolusi Hamiltonian yang eksak,
where is the dimension of the Hilbert space.
Diagnostik ini menggunakan matriks padat, sehingga cocok untuk contoh 12-qubit kecil ini tetapi tidak dimaksudkan sebagai subrutin yang scalable.
# The comparison uses the largest time that appears in the first-row Krylov estimates.
# Circuit depth does not depend on this numeric value, but the Trotter error does.
comparison_time = (krylov_dim - 1) * dt
def make_heisenberg_hamiltonian_ordered(
num_qubits: int,
ordering: str,
coupling: float = 1.0,
) -> SparsePauliOp:
"""Return the same Heisenberg Hamiltonian with a specified term ordering."""
terms: list[tuple[str, complex]] = []
even_edges = [(q, q + 1) for q in range(0, num_qubits - 1, 2)]
odd_edges = [(q, q + 1) for q in range(1, num_qubits - 1, 2)]
def append_term(q0: int, q1: int, pauli: str):
label = ["I"] * num_qubits
label[num_qubits - 1 - q0] = pauli[0]
label[num_qubits - 1 - q1] = pauli[1]
terms.append(("".join(label), coupling))
if ordering == "naive":
for pauli in ("XX", "YY", "ZZ"):
for q in range(num_qubits - 1):
append_term(q, q + 1, pauli)
elif ordering == "even-then-odd":
for pauli in ("XX", "YY", "ZZ"):
for q0, q1 in even_edges + odd_edges:
append_term(q0, q1, pauli)
elif ordering == "even-odd edge-grouped":
for q0, q1 in even_edges + odd_edges:
for pauli in ("XX", "YY", "ZZ"):
append_term(q0, q1, pauli)
else:
raise ValueError(f"Unknown ordering: {ordering}")
return SparsePauliOp.from_list(terms).simplify()
def build_numeric_evolution_circuit(
hamiltonian: SparsePauliOp,
synthesis,
time_value: float,
**synthesis_kwargs,
) -> QuantumCircuit:
"""Build a numeric circuit for exp(-i H t) with a chosen synthesis rule."""
evolution_gate = PauliEvolutionGate(
hamiltonian,
time=time_value,
synthesis=synthesis(**synthesis_kwargs),
)
circuit = QuantumCircuit(hamiltonian.num_qubits)
circuit.append(evolution_gate, range(hamiltonian.num_qubits))
return circuit
def process_infidelity(
circuit: QuantumCircuit,
exact_matrix: np.ndarray,
) -> float:
"""Return 1 - |Tr(U_circuit† U_exact) / d|²."""
circuit_matrix = np.asarray(Operator(circuit).data)
dim = circuit_matrix.shape[0]
normalized_trace = np.vdot(circuit_matrix, exact_matrix) / dim
fidelity = np.abs(normalized_trace) ** 2
return float(np.clip(1.0 - fidelity, 0.0, 1.0))
hamiltonians_by_ordering = {
ordering: make_heisenberg_hamiltonian_ordered(
num_qubits, ordering, coupling=1.0
)
for ordering in ["naive", "even-then-odd", "even-odd edge-grouped"]
}
print(f"Comparison time: {comparison_time}\n")
for order_name, ham_ordered in hamiltonians_by_ordering.items():
print(f"{order_name}:")
print([op for op, _ in ham_ordered.to_list()])
print()
print("Precomputing the exact evolution operator... ", end="")
exact_matrix = la.expm(-1j * comparison_time * hamiltonian.to_matrix())
print("Done")
Comparison time: 0.8567979964335799
naive:
['IIIIIIIIIIXX', 'IIIIIIIIIXXI', 'IIIIIIIIXXII', 'IIIIIIIXXIII', 'IIIIIIXXIIII', 'IIIIIXXIIIII', 'IIIIXXIIIIII', 'IIIXXIIIIIII', 'IIXXIIIIIIII', 'IXXIIIIIIIII', 'XXIIIIIIIIII', 'IIIIIIIIIIYY', 'IIIIIIIIIYYI', 'IIIIIIIIYYII', 'IIIIIIIYYIII', 'IIIIIIYYIIII', 'IIIIIYYIIIII', 'IIIIYYIIIIII', 'IIIYYIIIIIII', 'IIYYIIIIIIII', 'IYYIIIIIIIII', 'YYIIIIIIIIII', 'IIIIIIIIIIZZ', 'IIIIIIIIIZZI', 'IIIIIIIIZZII', 'IIIIIIIZZIII', 'IIIIIIZZIIII', 'IIIIIZZIIIII', 'IIIIZZIIIIII', 'IIIZZIIIIIII', 'IIZZIIIIIIII', 'IZZIIIIIIIII', 'ZZIIIIIIIIII']
even-then-odd:
['IIIIIIIIIIXX', 'IIIIIIIIXXII', 'IIIIIIXXIIII', 'IIIIXXIIIIII', 'IIXXIIIIIIII', 'XXIIIIIIIIII', 'IIIIIIIIIXXI', 'IIIIIIIXXIII', 'IIIIIXXIIIII', 'IIIXXIIIIIII', 'IXXIIIIIIIII', 'IIIIIIIIIIYY', 'IIIIIIIIYYII', 'IIIIIIYYIIII', 'IIIIYYIIIIII', 'IIYYIIIIIIII', 'YYIIIIIIIIII', 'IIIIIIIIIYYI', 'IIIIIIIYYIII', 'IIIIIYYIIIII', 'IIIYYIIIIIII', 'IYYIIIIIIIII', 'IIIIIIIIIIZZ', 'IIIIIIIIZZII', 'IIIIIIZZIIII', 'IIIIZZIIIIII', 'IIZZIIIIIIII', 'ZZIIIIIIIIII', 'IIIIIIIIIZZI', 'IIIIIIIZZIII', 'IIIIIZZIIIII', 'IIIZZIIIIIII', 'IZZIIIIIIIII']
even-odd edge-grouped:
['IIIIIIIIIIXX', 'IIIIIIIIIIYY', 'IIIIIIIIIIZZ', 'IIIIIIIIXXII', 'IIIIIIIIYYII', 'IIIIIIIIZZII', 'IIIIIIXXIIII', 'IIIIIIYYIIII', 'IIIIIIZZIIII', 'IIIIXXIIIIII', 'IIIIYYIIIIII', 'IIIIZZIIIIII', 'IIXXIIIIIIII', 'IIYYIIIIIIII', 'IIZZIIIIIIII', 'XXIIIIIIIIII', 'YYIIIIIIIIII', 'ZZIIIIIIIIII', 'IIIIIIIIIXXI', 'IIIIIIIIIYYI', 'IIIIIIIIIZZI', 'IIIIIIIXXIII', 'IIIIIIIYYIII', 'IIIIIIIZZIII', 'IIIIIXXIIIII', 'IIIIIYYIIIII', 'IIIIIZZIIIII', 'IIIXXIIIIIII', 'IIIYYIIIIIII', 'IIIZZIIIIIII', 'IXXIIIIIIIII', 'IYYIIIIIIIII', 'IZZIIIIIIIII']
Precomputing the exact evolution operator... Done
Kita terlebih dahulu menjaga aturan sintesis tetap pada satu langkah Trotter orde pertama dan hanya memvariasikan urutan suku Pauli. Tujuan dari perbandingan ini terutama untuk melihat seberapa besar kedalaman circuit dan biaya dua-qubit dapat dikurangi dengan menyingkap edge tetangga terdekat yang saling lepas kepada transpiler.
ordering_comparison_rows = []
infidelity_reps = [1, 2, 4, 8]
for ordering, ham_ordered in hamiltonians_by_ordering.items():
for reps in infidelity_reps:
circuit = build_numeric_evolution_circuit(
ham_ordered,
LieTrotter,
comparison_time,
reps=reps,
)
decomposed_circuit = simple_transpilation(circuit)
infidelity = process_infidelity(decomposed_circuit, exact_matrix)
ordering_comparison_rows.append(
{
"ordering": ordering,
"synthesis": f"LieTrotter(reps={reps})",
"infidelity": infidelity,
**summarize_circuit(decomposed_circuit),
}
)
if reps == 1:
print(f"Circuit for {ordering} ordering:")
print([op for op, _ in ham_ordered.to_list()])
display(decomposed_circuit.draw("mpl", fold=-1, scale=0.6))
ordering_comparison_df = pd.DataFrame(ordering_comparison_rows)
display(ordering_comparison_df)
hamiltonian_for_synthesis = hamiltonians_by_ordering["even-odd edge-grouped"]
Circuit for naive ordering:
['IIIIIIIIIIXX', 'IIIIIIIIIXXI', 'IIIIIIIIXXII', 'IIIIIIIXXIII', 'IIIIIIXXIIII', 'IIIIIXXIIIII', 'IIIIXXIIIIII', 'IIIXXIIIIIII', 'IIXXIIIIIIII', 'IXXIIIIIIIII', 'XXIIIIIIIIII', 'IIIIIIIIIIYY', 'IIIIIIIIIYYI', 'IIIIIIIIYYII', 'IIIIIIIYYIII', 'IIIIIIYYIIII', 'IIIIIYYIIIII', 'IIIIYYIIIIII', 'IIIYYIIIIIII', 'IIYYIIIIIIII', 'IYYIIIIIIIII', 'YYIIIIIIIIII', 'IIIIIIIIIIZZ', 'IIIIIIIIIZZI', 'IIIIIIIIZZII', 'IIIIIIIZZIII', 'IIIIIIZZIIII', 'IIIIIZZIIIII', 'IIIIZZIIIIII', 'IIIZZIIIIIII', 'IIZZIIIIIIII', 'IZZIIIIIIIII', 'ZZIIIIIIIIII']
Circuit for even-then-odd ordering:
['IIIIIIIIIIXX', 'IIIIIIIIXXII', 'IIIIIIXXIIII', 'IIIIXXIIIIII', 'IIXXIIIIIIII', 'XXIIIIIIIIII', 'IIIIIIIIIXXI', 'IIIIIIIXXIII', 'IIIIIXXIIIII', 'IIIXXIIIIIII', 'IXXIIIIIIIII', 'IIIIIIIIIIYY', 'IIIIIIIIYYII', 'IIIIIIYYIIII', 'IIIIYYIIIIII', 'IIYYIIIIIIII', 'YYIIIIIIIIII', 'IIIIIIIIIYYI', 'IIIIIIIYYIII', 'IIIIIYYIIIII', 'IIIYYIIIIIII', 'IYYIIIIIIIII', 'IIIIIIIIIIZZ', 'IIIIIIIIZZII', 'IIIIIIZZIIII', 'IIIIZZIIIIII', 'IIZZIIIIIIII', 'ZZIIIIIIIIII', 'IIIIIIIIIZZI', 'IIIIIIIZZIII', 'IIIIIZZIIIII', 'IIIZZIIIIIII', 'IZZIIIIIIIII']
Circuit for even-odd edge-grouped ordering:
['IIIIIIIIIIXX', 'IIIIIIIIIIYY', 'IIIIIIIIIIZZ', 'IIIIIIIIXXII', 'IIIIIIIIYYII', 'IIIIIIIIZZII', 'IIIIIIXXIIII', 'IIIIIIYYIIII', 'IIIIIIZZIIII', 'IIIIXXIIIIII', 'IIIIYYIIIIII', 'IIIIZZIIIIII', 'IIXXIIIIIIII', 'IIYYIIIIIIII', 'IIZZIIIIIIII', 'XXIIIIIIIIII', 'YYIIIIIIIIII', 'ZZIIIIIIIIII', 'IIIIIIIIIXXI', 'IIIIIIIIIYYI', 'IIIIIIIIIZZI', 'IIIIIIIXXIII', 'IIIIIIIYYIII', 'IIIIIIIZZIII', 'IIIIIXXIIIII', 'IIIIIYYIIIII', 'IIIIIZZIIIII', 'IIIXXIIIIIII', 'IIIYYIIIIIII', 'IIIZZIIIIIII', 'IXXIIIIIIIII', 'IYYIIIIIIIII', 'IZZIIIIIIIII']
ordering synthesis infidelity depth size \
0 naive LieTrotter(reps=1) 0.999917 15 33
1 naive LieTrotter(reps=2) 0.805998 21 66
2 naive LieTrotter(reps=4) 0.271389 33 132
3 naive LieTrotter(reps=8) 0.074590 57 264
4 even-then-odd LieTrotter(reps=1) 0.999917 6 33
5 even-then-odd LieTrotter(reps=2) 0.805998 12 66
6 even-then-odd LieTrotter(reps=4) 0.271389 24 132
7 even-then-odd LieTrotter(reps=8) 0.074590 48 264
8 even-odd edge-grouped LieTrotter(reps=1) 0.998432 6 33
9 even-odd edge-grouped LieTrotter(reps=2) 0.653843 12 66
10 even-odd edge-grouped LieTrotter(reps=4) 0.181529 24 132
11 even-odd edge-grouped LieTrotter(reps=8) 0.045244 48 264
2q gates 2q depth
0 33 15
1 66 21
2 132 33
3 264 57
4 33 6
5 66 12
6 132 24
7 264 48
8 33 6
9 66 12
10 132 24
11 264 48
Pada reps=1, kita mengamati bahwa urutan even-then-odd dan even-odd edge-grouped keduanya menurunkan kedalaman dari 15 menjadi 6 dengan menyingkap layer dua-qubit paralel, sementara jumlah gate dua-qubit tetap sama di ketiga urutan tersebut.
Namun, ketiga urutan tersebut memiliki infidelity mendekati 1, sehingga kita memvariasikan jumlah repetisi Trotter untuk memisahkan urutan-urutan tersebut secara lebih jelas.
Seiring bertambahnya jumlah repetisi, infidelity dari urutan even-odd edge-grouped turun lebih cepat dibandingkan dua urutan lainnya, mencapai 0.045 pada reps=8 dibandingkan 0.075 untuk urutan naif dan even-then-odd.
Membandingkan sintesis product-formula
Selanjutnya, kita mengeksplorasi berbagai pengaturan lanjutan dari Trotterization, dengan urutan Hamiltonian tetap pada urutan even-odd edge-grouped. Kita mempertimbangkan Lie-Trotter orde pertama, Suzuki-Trotter orde kedua, dan Suzuki-Trotter orde keempat.
synthesis_comparison_rows = []
for num_trotter_steps in [1, 2, 3, 4, 5]:
synthesis_cases = [
("LieTrotter", LieTrotter, {"reps": num_trotter_steps}),
(
"SuzukiTrotter(order=2)",
SuzukiTrotter,
{"order": 2, "reps": num_trotter_steps},
),
(
"SuzukiTrotter(order=4)",
SuzukiTrotter,
{"order": 4, "reps": num_trotter_steps},
),
]
for label, synthesis, kwargs in synthesis_cases:
circuit = build_numeric_evolution_circuit(
hamiltonian_for_synthesis,
synthesis,
comparison_time,
**kwargs,
)
decomposed_circuit = simple_transpilation(circuit)
synthesis_comparison_rows.append(
{
"synthesis": label,
"reps": kwargs["reps"],
"infidelity": process_infidelity(
decomposed_circuit, exact_matrix
),
**summarize_circuit(decomposed_circuit),
}
)
synthesis_comparison_df = pd.DataFrame(synthesis_comparison_rows)
display(synthesis_comparison_df.sort_values(["2q gates", "2q depth"]))
# For memory free
exact_matrix = None
synthesis reps infidelity depth size 2q gates \
0 LieTrotter 1 9.984324e-01 6 33 33
1 SuzukiTrotter(order=2) 1 9.733399e-01 9 51 51
3 LieTrotter 2 6.538427e-01 12 66 66
4 SuzukiTrotter(order=2) 2 2.522533e-01 15 84 84
6 LieTrotter 3 3.197242e-01 18 99 99
7 SuzukiTrotter(order=2) 3 4.804050e-02 21 117 117
9 LieTrotter 4 1.815291e-01 24 132 132
10 SuzukiTrotter(order=2) 4 1.453103e-02 27 150 150
12 LieTrotter 5 1.161770e-01 30 165 165
2 SuzukiTrotter(order=4) 1 2.884402e-01 33 183 183
13 SuzukiTrotter(order=2) 5 5.803455e-03 33 183 183
5 SuzukiTrotter(order=4) 2 1.641162e-03 63 348 348
8 SuzukiTrotter(order=4) 3 2.907076e-05 93 513 513
11 SuzukiTrotter(order=4) 4 2.791061e-06 123 678 678
14 SuzukiTrotter(order=4) 5 4.736685e-07 153 843 843
2q depth
0 6
1 9
3 12
4 15
6 18
7 21
9 24
10 27
12 30
2 33
13 33
5 63
8 93
11 123
14 153
Lie-Trotter memberikan circuit yang paling dangkal tetapi memiliki error terbesar, sementara Suzuki-Trotter orde keempat lebih akurat tetapi meningkatkan kedalaman circuit. Untuk sisa tutorial ini, kita memilih Suzuki-Trotter orde kedua karena memberikan circuit berkedalaman kecil sambil secara substansial mengurangi error Trotter relatif terhadap formula orde pertama.
Menghapus gate evolusi waktu terkontrol
Dalam extended swap test, mengontrol evolusi waktu dengan satu qubit ancilla mengharuskan ancilla untuk mengontrol banyak gate di seluruh sistem. Hal ini dapat menimbulkan overhead routing yang substansial dan, dalam kasus terburuk, secara efektif memerlukan konektivitas all-to-one. Untuk menghindari hal ini, optimasi lebih lanjut dimungkinkan dengan menggantikan gate evolusi waktu terkontrol dengan versi bebas-kontrol, memanfaatkan simetri Hamiltonian. Mari kita amati circuit berikut.

Di sini, menyiapkan keadaan referensi, .
Alih-alih terlebih dahulu menyiapkan dan kemudian menerapkan hanya pada cabang , circuit secara langsung menyiapkan kedua cabang sebagai
where
Circuit pertama-tama menerapkan gate Hadamard pada ancilla dan menyiapkan keadaan referensi hanya pada cabang :
Kemudian operator evolusi waktu yang tidak terkontrol diterapkan pada kedua cabang:
Karena Hamiltonian mempertahankan jumlah eksitasi, kita dapat melihat bahwa adalah eigenstate-nya, dan dengan demikian operator evolusi hanya mengakumulasi fase di bawah Hamiltonian:
Therefore,
Selanjutnya, diterapkan hanya pada cabang .
Pada titik ini, kedua cabang memiliki fase relatif tambahan. Untuk menghilangkannya, kita menerapkan gate fase ancilla
Ini mentransformasi keadaan sebagai
Dengan demikian, hingga fase global yang tidak relevan, kita akhirnya menyiapkan
Pada blok kode berikutnya, kita mengimplementasikan circuit bebas-kontrol ini.
controlled_extended_swap_test = extended_swap_test
# Reuse the ordering and synthesis rule selected by the comparison above.
evol_gate_optimized = PauliEvolutionGate(
hamiltonian_for_synthesis,
time=t,
synthesis=SuzukiTrotter(order=2, reps=num_trotter_steps),
)
uncontrolled_evolution = QuantumCircuit(num_qubits, name="U_ST2(t)")
uncontrolled_evolution.append(evol_gate_optimized, range(num_qubits))
controlled_state_prep = QuantumCircuit(num_qubits + 1, name="C-Prep")
for q, bit in enumerate(reversed(ref_bitstring)):
if bit == "1":
controlled_state_prep.cx(ancilla, system_qubits[q])
vacuum_bitstring = "0" * num_qubits
vacuum_energy = basis_state_expectation_sparse(hamiltonian, "0" * num_qubits)
optimized_extended_swap_test = QuantumCircuit(num_qubits + 1)
optimized_extended_swap_test.h(ancilla)
# Prepare |psi_ref> only on the |1> branch.
optimized_extended_swap_test.compose(controlled_state_prep, inplace=True)
optimized_extended_swap_test.barrier()
# Apply the Trotterized time evolution without control.
optimized_extended_swap_test.compose(
uncontrolled_evolution,
qubits=system_qubits,
inplace=True,
)
optimized_extended_swap_test.barrier()
# Map |0>|0...0> to |0>|psi_ref>, leaving the |1> branch unchanged.
optimized_extended_swap_test.x(ancilla)
optimized_extended_swap_test.compose(controlled_state_prep, inplace=True)
optimized_extended_swap_test.x(ancilla)
# Cancel the known vacuum phase so that the same X/Y observables can be used.
optimized_extended_swap_test.p(-vacuum_energy * t, ancilla)
optimized_extended_swap_test = simple_transpilation(
optimized_extended_swap_test
)
print(f"Vacuum energy E_vac = {vacuum_energy:.1f}")
display(
optimized_extended_swap_test.assign_parameters({t: 1.0}).draw(
"mpl", scale=0.5, fold=26
)
)
Vacuum energy E_vac = 11.0+0.0j

Transpilasi
Sekarang, kita mentranspilasi circuit terkontrol dan bebas-kontrol agar dapat dieksekusi pada hardware. Mari kita bandingkan hasil dari circuit yang telah ditranspilasi.
pass_manager = generate_preset_pass_manager(
backend=backend,
optimization_level=3,
)
isa_controlled_extended_swap_test = pass_manager.run(
controlled_extended_swap_test
)
pass_manager = generate_preset_pass_manager(
backend=backend, optimization_level=3, routing_method="none"
)
isa_optimized_extended_swap_test = pass_manager.run(
optimized_extended_swap_test
)
transpilation_result = [
{
"label": "abstract controlled U(t)",
**summarize_circuit(isa_controlled_extended_swap_test),
},
{
"label": "optimized non-controlled U(t)",
**summarize_circuit(isa_optimized_extended_swap_test),
},
]
display(pd.DataFrame(transpilation_result))
def filter_qubits_from_layout(layout):
q_layout = Layout(
{
physical: virtual
for physical, virtual in layout.get_physical_bits().items()
if virtual._register.name == "q"
}
)
return q_layout
print(
filter_qubits_from_layout(
isa_optimized_extended_swap_test.layout.initial_layout
)
)
isa_observables = [
op.apply_layout(isa_optimized_extended_swap_test.layout)
for op in observables
]
label depth size 2q gates 2q depth
0 abstract controlled U(t) 15457 23786 4686 4580
1 optimized non-controlled U(t) 261 1716 307 57
Layout({
18: <Qubit register=(13, "q"), index=0>,
5: <Qubit register=(13, "q"), index=1>,
6: <Qubit register=(13, "q"), index=2>,
7: <Qubit register=(13, "q"), index=3>,
8: <Qubit register=(13, "q"), index=4>,
9: <Qubit register=(13, "q"), index=5>,
10: <Qubit register=(13, "q"), index=6>,
11: <Qubit register=(13, "q"), index=7>,
12: <Qubit register=(13, "q"), index=8>,
13: <Qubit register=(13, "q"), index=9>,
14: <Qubit register=(13, "q"), index=10>,
15: <Qubit register=(13, "q"), index=11>,
19: <Qubit register=(13, "q"), index=12>
})
Langkah 3: Eksekusi menggunakan Qiskit primitives
Langkah selanjutnya adalah mengirimkan circuit terparameterisasi yang sama untuk beberapa nilai . Untuk setiap , kita mengestimasi empat nilai ekspektasi: , , , dan . Keempat angka ini kemudian digabungkan menjadi elemen baris pertama kompleks dan .
Di sini, dan dapat dihitung secara klasik karena bersifat sparse, sehingga kita melewatkan kasus .
pub_list = []
d_values = list(range(1, krylov_dim))
# Exact local statevector estimator.
estimator = StatevectorEstimator()
# We use the circuit before the transpilation for the local simulator,
# but we will use the transpiled circuit for the real backend.
for d in d_values:
parameter_values = [d * dt]
for ob in observables:
pub_list.append(
(
optimized_extended_swap_test,
ob,
parameter_values,
)
)
job = estimator.run(pub_list)
# Local PrimitiveJob does not provide Runtime-style job inputs,
# so preserve the inputs directly.
inputs = pub_list
result = job.result()
print(f"Number of Krylov basis states: r = {len(d_values)}")
print(f"Number of PUBs: {len(pub_list)}")
print(f"Each d uses observables: {observable_labels}")
Number of Krylov basis states: r = 9
Number of PUBs: 36
Each d uses observables: ['Re S_0d', 'Im S_0d', 'Re shifted H_0d', 'Im shifted H_0d']
Langkah 4: Pasca-proses dan kembalikan hasil dalam format klasik yang diinginkan
Setelah mengestimasi matriks yang terproyeksi, kita meregularisasi dan menyelesaikan GEVP
Eigenvalue tergeneralisasi terkecil memberikan estimasi KQD dari energi keadaan dasar.
h_shifted_row_est = np.zeros(krylov_dim, dtype=complex)
s_row_est = np.zeros(krylov_dim, dtype=complex)
h_shifted_row_est[0] = ref_energy - shift_tau
s_row_est[0] = 1.0
for idx, (pub_input, pub_result) in enumerate(zip(inputs, result)):
d_index, obs_index = divmod(idx, len(observables))
ev = np.asarray(pub_result.data.evs).reshape(-1)[0]
std = np.asarray(pub_result.data.stds).reshape(-1)[0]
if obs_index == 0:
s_row_est[d_index + 1] = ev
elif obs_index == 1:
s_row_est[d_index + 1] += 1j * ev
elif obs_index == 2:
h_shifted_row_est[d_index + 1] = ev
elif obs_index == 3:
h_shifted_row_est[d_index + 1] += 1j * ev
# H_0d = shifted_H_0d + tau * S_0d.
h_row_est = h_shifted_row_est + shift_tau * s_row_est
h_matrix_est = la.toeplitz(h_row_est.conj(), h_row_est)
s_matrix_est = la.toeplitz(s_row_est.conj(), s_row_est)
s_eigvals = la.eigvalsh(0.5 * (s_matrix_est + s_matrix_est.conj().T))
positive_s_eigvals = s_eigvals[s_eigvals > 1e-12]
s_condition_number = (
positive_s_eigvals[-1] / positive_s_eigvals[0]
if len(positive_s_eigvals) > 0
else np.inf
)
with np.printoptions(precision=3, suppress=True):
print("Estimated first row of S:")
print(s_row_est)
print()
print("Estimated first row of H:")
print(h_row_est)
print()
print("Eigenvalues of the estimated overlap matrix S:")
print(s_eigvals)
print(f"Condition number above 1e-12: {s_condition_number:.3e}")
print()
Estimated first row of S:
[ 1. +0.j 0.758-0.596j 0.203-0.836j -0.291-0.636j -0.444-0.229j
-0.276+0.053j -0.044+0.051j 0.006-0.121j -0.155-0.218j -0.345-0.101j]
Estimated first row of H:
[ 7. +0.j 4.842-4.76j 0.044-6.185j -3.791-3.653j -4.137+0.39j
-1.495+2.654j 1.331+1.777j 1.85 -0.763j -0.032-2.278j -2.222-1.372j]
Eigenvalues of the estimated overlap matrix S:
[-0. 0. 0. 0. 0. 0. 0.01 0.355 3.526 6.109]
Condition number above 1e-12: 1.904e+12
Sekarang kita menyelesaikan masalah eigenvalue tergeneralisasi dengan menggunakan matriks yang direkonstruksi dari estimasi circuit. Dalam perhitungan statevector yang ideal dengan evolusi waktu-nyata yang eksak, ini seharusnya mereproduksi hasil terproyeksi yang eksak. Dalam praktiknya, deviasi dapat berasal dari Trotterization, error sampling, dan ketidakstabilan numerik dari matriks overlap.
Kita mengamati bagaimana energi konvergen seiring kita meningkatkan dimensi subruang Krylov.
exact_evals = diagonalize_single_1_subspace(hamiltonian)
exact_ground = min(exact_evals)
print("exact ground state energy: ", exact_ground)
threshold = 1e-12
energy_convergence = []
for r in range(1, krylov_dim + 1):
energy_est_kqd, coeffs_est, retained_est = solve_thresholded_gevp(
h_matrix_est[:r, :r],
s_matrix_est[:r, :r],
threshold=threshold,
)
energy_convergence.append(energy_est_kqd)
print(
f"Krylov ground state energy (dim={r}, retained={retained_est}): ",
energy_est_kqd,
)
exact ground state energy: 3.136296694843727
Krylov ground state energy (dim=1, retained=1): 7.0
Krylov ground state energy (dim=2, retained=2): 4.184510657551266
Krylov ground state energy (dim=3, retained=3): 3.5539074630394136
Krylov ground state energy (dim=4, retained=4): 3.3366270761341044
Krylov ground state energy (dim=5, retained=5): 3.252017453225087
Krylov ground state energy (dim=6, retained=6): 3.2300275138879186
Krylov ground state energy (dim=7, retained=7): 3.2299154099085685
Krylov ground state energy (dim=8, retained=7): 3.2298063744216776
Krylov ground state energy (dim=9, retained=7): 3.2296778282872456
Krylov ground state energy (dim=10, retained=8): 3.223647515867734
def plot_energy_convergence(energy_convergence, exact_ground, krylov_dim):
fig, ax = plt.subplots(figsize=(7, 4.5))
ax.plot(
range(1, krylov_dim + 1),
energy_convergence,
marker="o",
label="KQD estimate",
)
ax.axhline(
exact_ground,
linestyle="--",
label=f"Exact ground energy = {exact_ground:.6f}",
)
ax.set_xlabel("Krylov dimension")
ax.set_ylabel("Ground-state energy")
ax.grid(True, alpha=0.3)
ax.legend()
plt.tight_layout()
plt.show()
plot_energy_convergence(energy_convergence, exact_ground, krylov_dim)
Contoh hardware skala besar
Bagian sebelumnya menggunakan model 12-qubit sehingga simulasi statevector dapat digunakan sebagai diagnostik. Sekarang kita menskalakan alur kerja KQD yang sama ke rantai Heisenberg 30-qubit dan menyiapkan workload untuk eksekusi pada hardware IBM Quantum.
Langkah 1-4 dipadatkan menjadi satu blok kode
Di sini kita sekarang menggabungkan semua detail ini menjadi satu alur kerja tunggal pada skala yang lebih besar, yang kemudian dijalankan pada hardware kuantum nyata kita. Pada bagian ini, kita menerapkan pengaturan mitigasi error yang realistis untuk meningkatkan keandalan hasil. Karena elemen matriks yang bersesuaian dengan nilai yang berbeda dapat dievaluasi secara paralel, kita menggunakan mode Batch untuk mengeksekusinya secara efisien.
# -------------------------Step 1-------------------------
# Map the classical problem to quantum circuits and observables.
# Problem and KQD parameters.
large_num_qubits = 30
large_krylov_dim = 7
large_num_trotter_steps = 3
large_dt = np.pi / (3 * (large_num_qubits - 1))
large_t = Parameter("t_large")
# Use a single excitation near the center of the chain.
large_excitation_qubit = large_num_qubits // 2
large_ref_label = ["0"] * large_num_qubits
large_ref_label[large_num_qubits - 1 - large_excitation_qubit] = "1"
large_ref_bitstring = "".join(large_ref_label)
# Use the ordering and product formula selected in the preceding section.
large_hamiltonian = make_heisenberg_hamiltonian_ordered(
large_num_qubits,
ordering="even-odd edge-grouped",
coupling=1.0,
)
large_ref_energy = float(
np.real(
basis_state_expectation_sparse(large_hamiltonian, large_ref_bitstring)
)
)
large_vacuum_energy = float(
np.real(
basis_state_expectation_sparse(
large_hamiltonian, "0" * large_num_qubits
)
)
)
large_evolution_gate = PauliEvolutionGate(
large_hamiltonian,
time=large_t,
synthesis=SuzukiTrotter(order=2, reps=large_num_trotter_steps),
)
large_uncontrolled_evolution = QuantumCircuit(
large_num_qubits,
name="U_ST2_large(t)",
)
large_uncontrolled_evolution.append(
large_evolution_gate,
range(large_num_qubits),
)
# Build the control-free extended-swap-test circuit.
large_ancilla = 0
large_system_qubits = list(range(1, large_num_qubits + 1))
large_controlled_state_prep = QuantumCircuit(
large_num_qubits + 1,
name="C-Prep-large",
)
large_controlled_state_prep.cx(
large_ancilla,
large_system_qubits[large_excitation_qubit],
)
large_extended_swap_test = QuantumCircuit(large_num_qubits + 1)
large_extended_swap_test.h(large_ancilla)
large_extended_swap_test.compose(large_controlled_state_prep, inplace=True)
large_extended_swap_test.compose(
large_uncontrolled_evolution,
qubits=large_system_qubits,
inplace=True,
)
large_extended_swap_test.x(large_ancilla)
large_extended_swap_test.compose(
large_controlled_state_prep.inverse(),
inplace=True,
)
large_extended_swap_test.x(large_ancilla)
large_extended_swap_test.p(
-large_vacuum_energy * large_t,
large_ancilla,
)
# Reuse the Hamiltonian-shifting construction from the preceding section.
(
large_obs_x_shifted_hamiltonian,
large_obs_y_shifted_hamiltonian,
large_shift_tau,
) = make_reduced_heisenberg_observables(large_ref_bitstring)
large_observables = [
SparsePauliOp("I" * large_num_qubits + "X"),
SparsePauliOp("I" * large_num_qubits + "Y"),
large_obs_x_shifted_hamiltonian,
large_obs_y_shifted_hamiltonian,
]
large_observable_labels = [
"Re S_0d",
"Im S_0d",
"Re shifted H_0d",
"Im shifted H_0d",
]
# -------------------------Step 2-------------------------
# Optimize the problem for quantum execution.
# Select a real backend and transpile the parameterized circuit to ISA form.
large_backend = service.backend("ibm_boston")
large_pass_manager = generate_preset_pass_manager(
backend=large_backend, optimization_level=3, routing_method="none"
)
large_isa_circuit = large_pass_manager.run(large_extended_swap_test)
large_isa_observables = [
observable.apply_layout(large_isa_circuit.layout)
for observable in large_observables
]
large_two_qubit_gate_count = sum(
instruction.operation.num_qubits == 2
for instruction in large_isa_circuit.data
)
print(f"Backend: {large_backend.name}")
print(f"System qubits: {large_num_qubits}")
print(f"Total circuit qubits: {large_isa_circuit.num_qubits}")
print(f"Krylov dimension: {large_krylov_dim}")
print(f"Time step: {large_dt:.6f}")
print(f"Shift tau: {large_shift_tau}")
print(f"ISA circuit depth: {large_isa_circuit.depth()}")
print(f"ISA two-qubit gates: {large_two_qubit_gate_count}")
print(
f"ISA two-qubit depth: {large_isa_circuit.depth(lambda x: x[0].num_qubits == 2)}"
)
# -------------------------Step 3-------------------------
# Execute on quantum hardware with Qiskit Runtime primitives.
# Submit one job per d, with all four observables in that job.
large_d_values = list(range(1, large_krylov_dim))
retrieve_batch_id = None
large_jobs = []
if retrieve_batch_id is None:
large_estimator_options = {
"default_shots": 8192,
"dynamical_decoupling": {
"enable": True,
"sequence_type": "XpXm",
},
"resilience": {
"measure_mitigation": True,
"measure_noise_learning": {
"num_randomizations": 32,
"shots_per_randomization": 256,
},
"layer_noise_learning": {
"max_layers_to_learn": 4,
"layer_pair_depths": [0, 1, 2, 4, 16, 32],
"num_randomizations": 32,
"shots_per_randomization": 128,
},
"zne_mitigation": True,
"zne": {
"amplifier": "pea",
"noise_factors": [1.0, 1.5, 2.0],
"extrapolator": ("exponential", "linear"),
},
},
"twirling": {
"enable_gates": True,
"enable_measure": True,
"num_randomizations": 32,
"shots_per_randomization": 256,
"strategy": "active-accum",
},
}
with Batch(backend=large_backend) as large_batch:
large_batch_id = large_batch.session_id
large_estimator = EstimatorV2(
mode=large_batch,
options=large_estimator_options,
)
# Krylov quantum diagonalization of lattice Hamiltonians -> TUT_KQDOLH.
large_estimator.options.environment.job_tags = ["TUT_KQDOLH"]
for d in large_d_values:
parameter_values = [d * large_dt]
pubs_for_d = [
(large_isa_circuit, observable, parameter_values)
for observable in large_isa_observables
]
job = large_estimator.run(pubs_for_d)
large_jobs.append(job)
print(
f"Submitted d={d}: job_id={job.job_id()}, "
f"PUBs={len(pubs_for_d)}"
)
print(f"Batch ID: {large_batch_id}")
else:
large_batch_id = retrieve_batch_id
large_jobs = service.jobs(
session_id=large_batch_id,
limit=None,
descending=False,
)
large_job_ids_by_d = {
d: job.job_id() for d, job in zip(large_d_values, large_jobs)
}
print(f"Job IDs by d: {large_job_ids_by_d}")
# -------------------------Step 4-------------------------
# Post-process the quantum results and solve the classical GEVP.
# Reconstruct the first rows of S and the shifted Hamiltonian matrix.
large_s_row_est = np.zeros(large_krylov_dim, dtype=complex)
large_h_shifted_row_est = np.zeros(large_krylov_dim, dtype=complex)
large_s_row_est[0] = 1.0
large_h_shifted_row_est[0] = large_ref_energy - large_shift_tau
for d, job in zip(large_d_values, large_jobs):
job_result = job.result()
if len(job_result) != len(large_observables):
raise RuntimeError(
f"Expected {len(large_observables)} PUB results for d={d}, "
f"but received {len(job_result)}."
)
expectation_values = [
np.asarray(pub_result.data.evs).reshape(-1)[0]
for pub_result in job_result
]
large_s_row_est[d] = expectation_values[0] + 1j * expectation_values[1]
large_h_shifted_row_est[d] = (
expectation_values[2] + 1j * expectation_values[3]
)
# H_0d = shifted_H_0d + tau * S_0d.
large_h_row_est = large_h_shifted_row_est + large_shift_tau * large_s_row_est
large_s_matrix_est = la.toeplitz(
large_s_row_est.conj(),
large_s_row_est,
)
large_h_matrix_est = la.toeplitz(
large_h_row_est.conj(),
large_h_row_est,
)
large_exact_gnd = min(diagonalize_single_1_subspace(large_hamiltonian))
large_energy_convergence = []
with np.printoptions(precision=5, suppress=True):
print("Estimated first row of S:")
print(large_s_row_est)
print("Estimated first row of H:")
print(large_h_row_est)
print("exact ground state energy: ", large_exact_gnd)
for r in range(1, large_krylov_dim + 1):
energy_est_kqd, coeffs_est, retained_est = solve_thresholded_gevp(
large_h_matrix_est[:r, :r],
large_s_matrix_est[:r, :r],
threshold=5e-2,
)
large_energy_convergence.append(energy_est_kqd)
print(
f"Krylov ground state energy (dim={r}, retained={retained_est}): ",
energy_est_kqd,
)
plot_energy_convergence(
large_energy_convergence, large_exact_gnd, large_krylov_dim
)
Backend: ibm_boston
System qubits: 30
Total circuit qubits: 156
Krylov dimension: 7
Time step: 0.036110
Shift tau: 25.0
ISA circuit depth: 219
ISA two-qubit gates: 728
ISA two-qubit depth: 52
Job IDs by d: {1: 'd9fl4p4jeosc73fk4dg0', 2: 'd9fl4pineu4c739poecg', 3: 'd9fl4q2neu4c739poedg', 4: 'd9fl4qhhtsac739fjgn0', 5: 'd9fl4r4jeosc73fk4dhg', 6: 'd9fl4rkjeosc73fk4dj0'}
Estimated first row of S:
[ 1. +0.j 0.42055-0.65466j -0.17883-0.73125j -0.64523-0.31601j
-0.70781+0.55574j -0.02467+0.70525j 0.59301+0.55602j]
Estimated first row of H:
[ 25. +0.j 10.3419 -16.5586j -5.017 -18.03933j
-16.44797 -6.98173j -17.17081+14.84502j 0.62722+17.81196j
15.68886+12.74875j]
exact ground state energy: 21.021912418526902
Krylov ground state energy (dim=1, retained=1): 25.0
Krylov ground state energy (dim=2, retained=2): 24.432409110686205
Krylov ground state energy (dim=3, retained=3): 24.256856893278556
Krylov ground state energy (dim=4, retained=4): 23.727409826799715
Krylov ground state energy (dim=5, retained=4): 23.324720470780864
Krylov ground state energy (dim=6, retained=5): 21.91957579005085
Krylov ground state energy (dim=7, retained=5): 21.548331214122644

Lampiran: Sudut pandang fungsi-Hamiltonian (filter-spektral)
Alur kerja utama menyajikan KQD secara operasional: membangun basis Krylov dari keadaan yang dievolusikan secara waktu-nyata, mengestimasi matriks terproyeksi dan , dan menyelesaikan GEVP. Lampiran ini meninjau kembali perhitungan yang sama dari sudut pandang komplementer yang menjelaskan mengapa KQD bekerja: sudut pandang fungsi-Hamiltonian, atau filter-spektral [3], [5]. Lampiran ini menggunakan kembali model 12-qubit, langkah waktu , dan solusi Krylov yang sudah diperoleh di atas; tidak diperlukan eksekusi circuit baru.
Keadaan referensi sebagai distribusi energi
Misalkan Hamiltonian memiliki dekomposisi eigen
dengan eigenstate energi . Keadaan referensi apa pun dapat diekspansikan dalam basis eigen ini,
sehingga ia membawa bobot spektral pada setiap energi . Energi referensi adalah rata-rata dari distribusi ini, .
Dekomposisi eigen dari Hamiltonian -qubit yang umum berbiaya eksponensial, sehingga gambaran ini hanya merupakan diagnostik, yang bukan bagian dari algoritma. Di sini, bagaimanapun, kita dapat menghitungnya dengan murah untuk masalah yang sama: Hamiltonian Heisenberg mengonservasi jumlah total eksitasi, dan referensi membawa satu eksitasi, sehingga seluruh konten spektralnya berada dalam subruang eksitasi tunggal yang dimensinya hanya bertumbuh secara linear terhadap . Oleh karena itu, kita menggunakan kembali blok eksitasi tunggal yang eksak (sudah digunakan di atas sebagai benchmark) dan membaca distribusi referensi di dalam subruang tersebut.
# Exact single-excitation-subspace decomposition of the reference state.
# This reuses make_heisenberg_hamiltonian / _basis_state_transition_amplitude_sparse
# and the n=12 `hamiltonian` and `ref_bitstring` defined in the small-scale example.
single_excitation_states = [1 << k for k in range(num_qubits)]
h_single = np.array(
[
[
_basis_state_transition_amplitude_sparse(hamiltonian, bra, ket)
for ket in single_excitation_states
]
for bra in single_excitation_states
]
)
h_single = 0.5 * (h_single + h_single.conj().T)
subspace_evals, subspace_evecs = la.eigh(h_single)
subspace_evals = np.real(subspace_evals)
# Reference-state coordinates inside the single-excitation subspace.
ref_position = single_excitation_states.index(int(ref_bitstring, 2))
ref_in_subspace = np.zeros(num_qubits, dtype=complex)
ref_in_subspace[ref_position] = 1.0
# Amplitudes and spectral weights of the reference in the energy eigenbasis.
ref_eigen_amplitudes = subspace_evecs.conj().T @ ref_in_subspace
ref_spectral_weights = np.abs(ref_eigen_amplitudes) ** 2
print(f"Single-excitation subspace dimension: {num_qubits}")
print(f"Subspace ground-state energy: {subspace_evals[0]:.6f}")
print(
f"Reference energy (sum p_m E_m): {np.sum(ref_spectral_weights * subspace_evals):.6f}"
)
print(f"Reference weight on subspace ground: {ref_spectral_weights[0]:.6f}")
Single-excitation subspace dimension: 12
Subspace ground-state energy: 3.136297
Reference energy (sum p_m E_m): 7.000000
Reference weight on subspace ground: 0.163827
KQD mempelajari filter yang membentuk ulang distribusi ini
Sebuah fungsi-Hamiltonian didefinisikan melalui kalkulus spektral,
atau dengan kata lain, jumlah berbobot dari eigenprojector. Menerapkannya pada referensi membentuk ulang setiap amplitudo spektral, :
Jika memiliki puncak yang tajam pada energi terendah ( dan selainnya) maka akan bertindak sebagai proyektor keadaan-dasar, dan output yang dinormalisasi akan (mendekati) keadaan dasar. Oleh karena itu, filter spektral low-pass yang baik dalam energi adalah tepat apa yang kita inginkan.
KQD tidak menentukan terlebih dahulu. Sebaliknya, ia mengekspansikan filter dalam basis evolusi waktu-nyata,
sebuah fungsi trigonometrik dari energi yang koefisiennya tepatnya merupakan eigenvector GEVP yang diselesaikan di atas. Meminimalkan quotient Rayleigh oleh karena itu sama dengan mempelajari filter yang paling baik menekan bobot keadaan-tereksitasi dari referensi. Dimensi Krylov yang lebih besar memberikan filter lebih banyak derajat kebebasan dan puncak yang lebih tajam pada energi keadaan-dasar.
Helper di bawah ini mengevaluasi filter yang telah dipelajari ini pada sumbu energi; kita kemudian menerapkannya pada distribusi referensi yang diperoleh di atas.
def trigonometric_krylov_filter(
coeffs: np.ndarray,
energies: np.ndarray,
time_step: float,
) -> np.ndarray:
"""Evaluate the learned Krylov filter f(E) = sum_l c_l exp(-i l dt E)."""
values = np.zeros_like(energies, dtype=complex)
for ell, coeff in enumerate(coeffs):
values += coeff * np.exp(-1j * ell * time_step * energies)
return values
def filtered_spectral_weights(
weights: np.ndarray,
filter_values: np.ndarray,
) -> np.ndarray:
"""Reshape spectral weights by |f(E)|^2 and renormalize."""
reshaped = weights * np.abs(filter_values) ** 2
return reshaped / np.sum(reshaped)
# Recover the KQD coefficients from the already-estimated projected matrices.
# The shift only moves H by tau * S, so it does not change the GEVP eigenvector;
# we solve at the full Krylov dimension used in the small-scale example.
_, kqd_coeffs, _ = solve_thresholded_gevp(
h_matrix_est,
s_matrix_est,
threshold=1e-12,
)
filter_on_spectrum = trigonometric_krylov_filter(
kqd_coeffs, subspace_evals, dt
)
filtered_weights = filtered_spectral_weights(
ref_spectral_weights, filter_on_spectrum
)
print(f"Ground-state overlap (reference): {ref_spectral_weights[0]:.4f}")
print(f"Ground-state overlap (filtered): {filtered_weights[0]:.4f}")
print(
f"Mean energy (reference): {np.sum(ref_spectral_weights * subspace_evals):.6f}"
)
print(
f"Mean energy (filtered): {np.sum(filtered_weights * subspace_evals):.6f}"
)
Ground-state overlap (reference): 0.1638
Ground-state overlap (filtered): 0.9638
Mean energy (reference): 7.000000
Mean energy (filtered): 3.164503
Memvisualisasikan filter dan fleksibilitasnya
Kita pertama-tama menunjukkan filter yang telah dipelajari pada dimensi Krylov penuh yang digunakan di atas, kemudian melacak bagaimana filter tersebut menajam seiring bertambahnya dimensi .
Batang menunjukkan bobot spektral referensi (sebelum) dan bobot yang telah difilter (sesudah), bersama dengan intensitas filter yang telah dipelajari pada sumbu energi kontinu. Filter memusatkan bobot pada energi terendah dari subruang eksitasi tunggal — energi yang sama dengan tempat estimasi KQD berkonvergensi pada contoh skala kecil. Perhatikan bahwa ini adalah keadaan dasar dalam sektor eksitasi tunggal, yang merupakan target yang relevan untuk keadaan referensi yang mengonservasi eksitasi ini, bukan keadaan dasar global.
fig, ax = plt.subplots(figsize=(8, 4))
visible = (ref_spectral_weights > 1e-4) | (filtered_weights > 1e-4)
ax.bar(
subspace_evals[visible],
ref_spectral_weights[visible],
width=0.18,
alpha=0.45,
label="reference $p_m$",
)
ax.bar(
subspace_evals[visible],
filtered_weights[visible],
width=0.14,
alpha=0.9,
label=r"filtered $p_m\,|f_{\rm KQD}(E_m)|^2$",
)
energy_grid = np.linspace(
subspace_evals.min() - 0.5, subspace_evals.max() + 0.5, 800
)
filter_intensity = (
np.abs(trigonometric_krylov_filter(kqd_coeffs, energy_grid, dt)) ** 2
)
filter_intensity /= filter_intensity.max()
ax.plot(
energy_grid,
filter_intensity,
color="k",
linewidth=2,
label=r"$|f_{\rm KQD}(E)|^2$ (normalized)",
)
ax.axvline(
subspace_evals[0],
color="C3",
linestyle="--",
linewidth=1,
label="subspace ground energy",
)
ax.set_xlabel("Energy eigenvalue $E_m$")
ax.set_ylabel("Spectral weight")
ax.set_ylim(0, 1)
ax.legend(loc="upper right")
plt.tight_layout()
plt.show()
Meningkatkan dimensi Krylov: fleksibilitas dari fungsi yang telah dipelajari
Ingat kembali bahwa filter yang telah dipelajari adalah polinomial trigonometrik dalam energi dengan koefisien,
Dimensi Krylov persis merupakan jumlah koefisien bebas, sehingga ia mengontrol fleksibilitas dari fungsi tersebut. yang kecil hanya dapat menghasilkan filter yang lebar dan bervariasi secara perlahan yang membocorkan bobot ke keadaan-keadaan tereksitasi berenergi rendah; ketika meningkat, filter dapat membentuk puncak yang lebih sempit pada energi target dan menekan sisa bobot keadaan-tereksitasi secara lebih agresif. Ini adalah rekan filter-spektral dari konvergensi energi yang diamati pada contoh skala kecil: seiring bertambahnya , distribusi yang telah difilter mengempis menuju keadaan dasar subruang dan energi yang diestimasi menurun mendekatinya.
Kita menggunakan kembali matriks terproyeksi yang sudah diestimasi di atas dan cukup menyelesaikan GEVP pada setiap blok terdepan, kemudian mengevaluasi dan memplot filter yang bersesuaian.
# Sweep the Krylov dimension using the leading r x r blocks of the estimated matrices.
sweep_dims = [r for r in (2, 4, 6, 8, krylov_dim) if r <= krylov_dim]
sweep_dims = sorted(set(sweep_dims))
energy_grid = np.linspace(
subspace_evals.min() - 0.5, subspace_evals.max() + 0.5, 800
)
sweep_cases = []
print(" r retained ground overlap filtered energy")
print("-- -------- -------------- ---------------")
for r in sweep_dims:
_, coeffs_r, retained_r = solve_thresholded_gevp(
h_matrix_est[:r, :r],
s_matrix_est[:r, :r],
threshold=1e-12,
)
filter_on_spectrum_r = trigonometric_krylov_filter(
coeffs_r, subspace_evals, dt
)
filtered_weights_r = filtered_spectral_weights(
ref_spectral_weights, filter_on_spectrum_r
)
filtered_energy_r = float(np.sum(filtered_weights_r * subspace_evals))
sweep_cases.append((r, coeffs_r, filtered_weights_r))
print(
f"{r:2d} {retained_r:8d} {filtered_weights_r[0]:14.4f} {filtered_energy_r:15.6f}"
)
print(f"\nSubspace ground-state energy (target): {subspace_evals[0]:.6f}")
# One panel per Krylov dimension: filtered spectrum (bars) + filter intensity (curve).
fig, axes = plt.subplots(
len(sweep_cases),
1,
figsize=(8, 2.1 * len(sweep_cases)),
sharex=True,
)
axes = np.atleast_1d(axes)
for idx, (ax, (r, coeffs_r, filtered_weights_r)) in enumerate(
zip(axes, sweep_cases)
):
visible = (ref_spectral_weights > 1e-4) | (filtered_weights_r > 1e-4)
ax.bar(
subspace_evals[visible],
ref_spectral_weights[visible],
width=0.18,
alpha=0.35,
color="C0",
label="reference $p_m$" if idx == 0 else None,
)
ax.bar(
subspace_evals[visible],
filtered_weights_r[visible],
width=0.14,
alpha=0.9,
color="C1",
label="filtered weights" if idx == 0 else None,
)
filter_intensity_r = (
np.abs(trigonometric_krylov_filter(coeffs_r, energy_grid, dt)) ** 2
)
filter_intensity_r /= filter_intensity_r.max()
ax.plot(energy_grid, filter_intensity_r, color="k", linewidth=2)
ax.axvline(subspace_evals[0], color="C3", linestyle="--", linewidth=1)
ax.set_ylim(0, 1)
ax.set_ylabel("weight")
ax.legend(loc="upper right", title=f"$r={r}$")
axes[-1].set_xlabel("Energy eigenvalue $E_m$")
fig.suptitle(
r"KQD-learned filter $|f_{\rm KQD}(E)|^2$ sharpening with Krylov dimension $r$",
y=1.0,
)
fig.tight_layout()
plt.show()
r retained ground overlap filtered energy
-- -------- -------------- ---------------
2 2 0.4549 4.184510
4 4 0.8373 3.310168
6 6 0.9706 3.158100
8 7 0.9701 3.158725
10 8 0.9638 3.164503
Subspace ground-state energy (target): 3.136297

Langkah selanjutnya
Jika kamu merasa pekerjaan ini menarik, kamu mungkin tertarik dengan materi berikut:
Referensi
[1] E. N. Epperly, L. Lin, dan Y. Nakatsukasa, A theory of quantum subspace diagonalization, SIAM Journal on Matrix Analysis and Applications 43, 1263-1290 (2022).
[2] N. Yoshioka, M. Amico, W. Kirby, et al., Diagonalization of large many-body Hamiltonians on a quantum processor, arXiv:2407.14431 (2024).
[3] R. M. Parrish and P. L. McMahon, Quantum filter diagonalization: quantum eigendecomposition without full quantum phase estimation, Physical Review Letters 122, 230401 (2019).
[4] G. Lee, S. Choi, J. Huh, and A. F. Izmaylov, Efficient strategies for reducing sampling error in quantum Krylov subspace diagonalization, Digital Discovery 4, 954-969 (2025).
[5] G. Lee, M. Kang, J. Hong, S. Fomichev and J. Huh, Filtered Quantum Phase Estimation, arXiv:2510.04294 (2025).