Krylov Quante-Diagonalisierung vun Gitter-Hamiltoniane
Nutzungsschätzung: 20 Minute op nem Heron r2 (HENGER: Dat es nor en Schätzung. Ding Laufzick kann anders sin.)
# This cell is hidden from users – it disables some lint rules
# ruff: noqa: E402 E722 F601
Hintergrund
Dat Tutorial zeich dir, wie mer dä Krylov Quantum Diagonalization Algorithm (KQD) em Kontext vun Qiskit-Mustere ömsetze kann. Do liers zeez övver de Theorie hinger däm Algorithmus un dann kriss de en Vörführung vun singer Usführung op enem QPU.
Övver all Diszipline hann mir Intress, Grundzustandseigenschafte vun Quantesysteme ze liere. Beispiele enthalde et Verstonn vun dä fundamentale Natur vun Partikel un Kräfte, et Vörhersare un Verstonn vum Verhalte vun komplexe Materiale un et Verstonn vun biochemische Interaktione un Reaktione. Wääje däm exponentielle Wachstum vum Hilbert-Ruum un dä Korrelation, die en verschränkte Systeme optrette, han klassische Algorithme Schwierigkeite, dat Problem för Quantesysteme vun wachsender Gröss ze löse. An enem Eng vum Spektrum es dä bestehende Ansatz, dä vun dä Quantehardware profitiert un sisch op variationelle Quantemethode konzentriert (zom Beispill variational quantum eigensolver). Die Technike stonn vör Herausforderunge met aktuelle Geräte wääje dä huhe Zahl vun Funktioneufrufe, die em Optimierungsprozess nötig sen, wat ne große Overhead an Ressource bringt, wann moderne Fehlerminderungstechnike engeföhrt wäde, un domet ehr Wirksamkeit op kleine Systeme beschränk. Am andere Eng vum Spektrum git et fehlertolerante Quantemethode met Leistungsgarantie (zom Beispill quantum phase estimation), die deef Schaltkreise bruche, die nor op enem fehlertolerante Gerät usgeföhrt wäde künne. Uss dänne Gründe stelle mir hee ne Quantealgorithmus vör, dä op Teilruummethode basiert (wie beschrevve en däm Övversechtspapier), dä Krylov Quante-Diagonalisierungsalgorithmus (KQD). Dä Algorithmus funktioneert gut bei große Maßstav [1] op bestehender Quantehardware, deilt ähnliche Leistungsgarantie wie Phase Estimation, es kompatibel met moderne Fehlerminderungstechnike un künnt Ergebnisse levere, die klassisch nit zogänglich sen.
Vöraussetzunge
Bevör de met däm Tutorial aafängs, sorg doför, dat de Folgendes installiert häs:
- Qiskit SDK v2.0 oder nöer, met Visualisierung
- Qiskit Runtime v0.22 oder nöer (
pip install qiskit-ibm-runtime)
Opbau
import numpy as np
import scipy as sp
import matplotlib.pylab as plt
from typing import Union, List
import itertools as it
import copy
from sympy import Matrix
import warnings
warnings.filterwarnings("ignore")
from qiskit.quantum_info import SparsePauliOp, Pauli, StabilizerState
from qiskit.circuit import Parameter, IfElseOp
from qiskit import QuantumCircuit, QuantumRegister
from qiskit.circuit.library import PauliEvolutionGate
from qiskit.synthesis import LieTrotter
from qiskit.transpiler import Target, CouplingMap
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import (
QiskitRuntimeService,
EstimatorV2 as Estimator,
)
def solve_regularized_gen_eig(
h: np.ndarray,
s: np.ndarray,
threshold: float,
k: int = 1,
return_dimn: bool = False,
) -> Union[float, List[float]]:
"""
Method for solving the generalized eigenvalue problem with regularization
Args:
h (numpy.ndarray):
The effective representation of the matrix in the Krylov subspace
s (numpy.ndarray):
The matrix of overlaps between vectors of the Krylov subspace
threshold (float):
Cut-off value for the eigenvalue of s
k (int):
Number of eigenvalues to return
return_dimn (bool):
Whether to return the size of the regularized subspace
Returns:
lowest k-eigenvalue(s) that are the solution of the regularized generalized eigenvalue problem
"""
s_vals, s_vecs = sp.linalg.eigh(s)
s_vecs = s_vecs.T
good_vecs = np.array(
[vec for val, vec in zip(s_vals, s_vecs) if val > threshold]
)
h_reg = good_vecs.conj() @ h @ good_vecs.T
s_reg = good_vecs.conj() @ s @ good_vecs.T
if k == 1:
if return_dimn:
return sp.linalg.eigh(h_reg, s_reg)[0][0], len(good_vecs)
else:
return sp.linalg.eigh(h_reg, s_reg)[0][0]
else:
if return_dimn:
return sp.linalg.eigh(h_reg, s_reg)[0][:k], len(good_vecs)
else:
return sp.linalg.eigh(h_reg, s_reg)[0][:k]
def single_particle_gs(H_op, n_qubits):
"""
Find the ground state of the single particle(excitation) sector
"""
H_x = []
for p, coeff in H_op.to_list():
H_x.append(set([i for i, v in enumerate(Pauli(p).x) if v]))
H_z = []
for p, coeff in H_op.to_list():
H_z.append(set([i for i, v in enumerate(Pauli(p).z) if v]))
H_c = H_op.coeffs
print("n_sys_qubits", n_qubits)
n_exc = 1
sub_dimn = int(sp.special.comb(n_qubits + 1, n_exc))
print("n_exc", n_exc, ", subspace dimension", sub_dimn)
few_particle_H = np.zeros((sub_dimn, sub_dimn), dtype=complex)
sparse_vecs = [
set(vec) for vec in it.combinations(range(n_qubits + 1), r=n_exc)
] # list all of the possible sets of n_exc indices of 1s in n_exc-particle states
m = 0
for i, i_set in enumerate(sparse_vecs):
for j, j_set in enumerate(sparse_vecs):
m += 1
if len(i_set.symmetric_difference(j_set)) <= 2:
for p_x, p_z, coeff in zip(H_x, H_z, H_c):
if i_set.symmetric_difference(j_set) == p_x:
sgn = ((-1j) ** len(p_x.intersection(p_z))) * (
(-1) ** len(i_set.intersection(p_z))
)
else:
sgn = 0
few_particle_H[i, j] += sgn * coeff
gs_en = min(np.linalg.eigvalsh(few_particle_H))
print("single particle ground state energy: ", gs_en)
return gs_en
Schritt 1: Kartier klassische Ingänge op e Quanteproblem
Dä Krylov-Ruum
Dä Krylov-Ruum vun Ordnung es dä Ruum, dä vun Vektore opgespannt weed, die mer kritt, endemm mir höhere Potenze vun ener Matrix , bes zo , met enem Referenzvekter multipliziere.
Wann de Matrix dä Hamiltonian es, wäde mir dä entsprechende Ruum als Potenz-Krylov-Ruum bezeichne. Em Fall, wann dä Zickentwicklungsoperator es, dä vum Hamiltonian erzeugt weed, wäde mir dä Ruum als unitäre Krylov-Ruum bezeichne. Dä Potenz-Krylov-Teilruum, dä mir klassisch bruche, kann nit direkt op enem Quantecomputer erzeugt wäde, weil keine unitäre Operator es. Stattdesse künne mir dä Zickentwicklungsoperator bruche, vun däm mer zeige kann, dat hä ähnliche Konvergenzgarantie wäi de Potenzmethode gitt. Potenze vun wäde dann verschiedene Zickschritte .
Luur em Aanhang f ör en detaillierte Herleitung, wie dä unitäre Krylov-Ruum et erlaubt, niederenergetische Eigenzuständ genau darzestelle.
Krylov Quante-Diagonalisierungsalgorithmus
Gevve ene Hamiltonian , dä mir diagonalisiere welle, betrachte mir zeez dä entsprechende unitäre Krylov-Ruum . Et Ziel es et, en kompakte Darstellung vum Hamiltonian en ze finge, die mir als bezeichne. De Matrixelemente vun , de Projektion vum Hamiltonian em Krylov-Ruum, künne berechnet wäde, endemm mir de folgend Erwartungswerte berechne
Woh de Vektore vum unitäre Krylov-Ruum sen un de Vielfache vum Zickschritt sin, dä gewählt wood. Op enem Quantecomputer kann de Berechnung vun jedem Matrixelement met jedem Algorithmus gemaat wäde, dä et erlaubt, Övverlappunge zwesche Quantezuständ ze erhalde. Dat Tutorial konzentriert sisch op dä Hadamard-Test. Gevve dat de Dimension hät, hätt dä Hamiltonian, projiziert en dä Teilruum, Dimensione . Met klein genuch (normalerwies langt för de Konvergenz vun Schätzunge vun Eigenenergien) künne mir dann einfach dä projizierte Hamiltonian diagonalisiere. Ävver mir künne nit direkt diagonalisiere wääje dä Nit-Orthogonalität vun dä Krylov-Ruum-Vektore. Mir müsse ehr Övverlappunge messe un en Matrix konstruiere
Dat erlaubt oss, et Eigenwäätproblem en enem nit-orthogonale Ruum ze löse (och verallgemeinert Eigenwäätproblem genannt)
Mer kann dann Schätzunge vun dä Eigenwääte un Eigenzuständ vun erhalde, endemm mer op die vun luurt. Zom Beispill kreijt mer de Schätzung vun dä Grundzustandsenergie, endemm mer dä kleenste Eigenwäät nimmp un dä Grundzustand us däm entsprechende Eigenvekter . De Koeffiziente en bestimme dä Bidrag vun dä verschiedene Vektore, die opspanne.

De Figur zeich en Schaltkreisdarstellung vum modifizierte Hadamard-Test, en Methode, die benutzt weed, öm de Övverlappung zwesche verschiedene Quantezuständ ze berechne. För jedes Matrixelement weed ne Hadamard-Test zwesche däm Zustand , dörchgeföhrt. Dat weed en dä Figur dörch et Farvschema för de Matrixelemente un de entsprechende , Operatione hervorgehove. Also es en Menge vun Hadamard-Tests för all mögliche Kombinatione vun Krylov-Ruum-Vektore nötig, öm all Matrixelemente vum projizierte Hamiltonian ze berechne. De bovverste Leitung em Hadamard-Test-Schaltkreis es e Ancilla-Qubit, dat entweder en dä X- oder Y-Basis gemesse weed, singer Erwartungswäät bestimmp dä Wäät vun dä Övverlappung zwesche dä Zuständ. De ungerste Leitung stellt all Qubits vum System-Hamiltonian dar. De Operation bereidt et System-Qubit em Zustand vör, gesteuert dörch dä Zustand vum Ancilla-Qubit (ähnlich för ) un de Operation stellt de Pauli-Zerlegung vum System-Hamiltonian dar. En detaillierter Herleitung vun dä Operatione, die vum Hadamard-Test berechnet wäde, es onge gevve.
Hamiltonian definiere
Loss oss dä Heisenberg-Hamiltonian för Qubits op ener lineare Kette betrachte:
# Define problem Hamiltonian.
n_qubits = 30
J = 1 # coupling strength for ZZ interaction
# Define the Hamiltonian:
H_int = [["I"] * n_qubits for _ in range(3 * (n_qubits - 1))]
for i in range(n_qubits - 1):
H_int[i][i] = "Z"
H_int[i][i + 1] = "Z"
for i in range(n_qubits - 1):
H_int[n_qubits - 1 + i][i] = "X"
H_int[n_qubits - 1 + i][i + 1] = "X"
for i in range(n_qubits - 1):
H_int[2 * (n_qubits - 1) + i][i] = "Y"
H_int[2 * (n_qubits - 1) + i][i + 1] = "Y"
H_int = ["".join(term) for term in H_int]
H_tot = [(term, J) if term.count("Z") == 2 else (term, 1) for term in H_int]
# Get operator
H_op = SparsePauliOp.from_list(H_tot)
print(H_tot)
[('ZZIIIIIIIIIIIIIIIIIIIIIIIIIIII', 1), ('IZZIIIIIIIIIIIIIIIIIIIIIIIIIII', 1), ('IIZZIIIIIIIIIIIIIIIIIIIIIIIIII', 1), ('IIIZZIIIIIIIIIIIIIIIIIIIIIIIII', 1), ('IIIIZZIIIIIIIIIIIIIIIIIIIIIIII', 1), ('IIIIIZZIIIIIIIIIIIIIIIIIIIIIII', 1), ('IIIIIIZZIIIIIIIIIIIIIIIIIIIIII', 1), ('IIIIIIIZZIIIIIIIIIIIIIIIIIIIII', 1), ('IIIIIIIIZZIIIIIIIIIIIIIIIIIIII', 1), ('IIIIIIIIIZZIIIIIIIIIIIIIIIIIII', 1), ('IIIIIIIIIIZZIIIIIIIIIIIIIIIIII', 1), ('IIIIIIIIIIIZZIIIIIIIIIIIIIIIII', 1), ('IIIIIIIIIIIIZZIIIIIIIIIIIIIIII', 1), ('IIIIIIIIIIIIIZZIIIIIIIIIIIIIII', 1), ('IIIIIIIIIIIIIIZZIIIIIIIIIIIIII', 1), ('IIIIIIIIIIIIIIIZZIIIIIIIIIIIII', 1), ('IIIIIIIIIIIIIIIIZZIIIIIIIIIIII', 1), ('IIIIIIIIIIIIIIIIIZZIIIIIIIIIII', 1), ('IIIIIIIIIIIIIIIIIIZZIIIIIIIIII', 1), ('IIIIIIIIIIIIIIIIIIIZZIIIIIIIII', 1), ('IIIIIIIIIIIIIIIIIIIIZZIIIIIIII', 1), ('IIIIIIIIIIIIIIIIIIIIIZZIIIIIII', 1), ('IIIIIIIIIIIIIIIIIIIIIIZZIIIIII', 1), ('IIIIIIIIIIIIIIIIIIIIIIIZZIIIII', 1), ('IIIIIIIIIIIIIIIIIIIIIIIIZZIIII', 1), ('IIIIIIIIIIIIIIIIIIIIIIIIIZZIII', 1), ('IIIIIIIIIIIIIIIIIIIIIIIIIIZZII', 1), ('IIIIIIIIIIIIIIIIIIIIIIIIIIIZZI', 1), ('IIIIIIIIIIIIIIIIIIIIIIIIIIIIZZ', 1), ('XXIIIIIIIIIIIIIIIIIIIIIIIIIIII', 1), ('IXXIIIIIIIIIIIIIIIIIIIIIIIIIII', 1), ('IIXXIIIIIIIIIIIIIIIIIIIIIIIIII', 1), ('IIIXXIIIIIIIIIIIIIIIIIIIIIIIII', 1), ('IIIIXXIIIIIIIIIIIIIIIIIIIIIIII', 1), ('IIIIIXXIIIIIIIIIIIIIIIIIIIIIII', 1), ('IIIIIIXXIIIIIIIIIIIIIIIIIIIIII', 1), ('IIIIIIIXXIIIIIIIIIIIIIIIIIIIII', 1), ('IIIIIIIIXXIIIIIIIIIIIIIIIIIIII', 1), ('IIIIIIIIIXXIIIIIIIIIIIIIIIIIII', 1), ('IIIIIIIIIIXXIIIIIIIIIIIIIIIIII', 1), ('IIIIIIIIIIIXXIIIIIIIIIIIIIIIII', 1), ('IIIIIIIIIIIIXXIIIIIIIIIIIIIIII', 1), ('IIIIIIIIIIIIIXXIIIIIIIIIIIIIII', 1), ('IIIIIIIIIIIIIIXXIIIIIIIIIIIIII', 1), ('IIIIIIIIIIIIIIIXXIIIIIIIIIIIII', 1), ('IIIIIIIIIIIIIIIIXXIIIIIIIIIIII', 1), ('IIIIIIIIIIIIIIIIIXXIIIIIIIIIII', 1), ('IIIIIIIIIIIIIIIIIIXXIIIIIIIIII', 1), ('IIIIIIIIIIIIIIIIIIIXXIIIIIIIII', 1), ('IIIIIIIIIIIIIIIIIIIIXXIIIIIIII', 1), ('IIIIIIIIIIIIIIIIIIIIIXXIIIIIII', 1), ('IIIIIIIIIIIIIIIIIIIIIIXXIIIIII', 1), ('IIIIIIIIIIIIIIIIIIIIIIIXXIIIII', 1), ('IIIIIIIIIIIIIIIIIIIIIIIIXXIIII', 1), ('IIIIIIIIIIIIIIIIIIIIIIIIIXXIII', 1), ('IIIIIIIIIIIIIIIIIIIIIIIIIIXXII', 1), ('IIIIIIIIIIIIIIIIIIIIIIIIIIIXXI', 1), ('IIIIIIIIIIIIIIIIIIIIIIIIIIIIXX', 1), ('YYIIIIIIIIIIIIIIIIIIIIIIIIIIII', 1), ('IYYIIIIIIIIIIIIIIIIIIIIIIIIIII', 1), ('IIYYIIIIIIIIIIIIIIIIIIIIIIIIII', 1), ('IIIYYIIIIIIIIIIIIIIIIIIIIIIIII', 1), ('IIIIYYIIIIIIIIIIIIIIIIIIIIIIII', 1), ('IIIIIYYIIIIIIIIIIIIIIIIIIIIIII', 1), ('IIIIIIYYIIIIIIIIIIIIIIIIIIIIII', 1), ('IIIIIIIYYIIIIIIIIIIIIIIIIIIIII', 1), ('IIIIIIIIYYIIIIIIIIIIIIIIIIIIII', 1), ('IIIIIIIIIYYIIIIIIIIIIIIIIIIIII', 1), ('IIIIIIIIIIYYIIIIIIIIIIIIIIIIII', 1), ('IIIIIIIIIIIYYIIIIIIIIIIIIIIIII', 1), ('IIIIIIIIIIIIYYIIIIIIIIIIIIIIII', 1), ('IIIIIIIIIIIIIYYIIIIIIIIIIIIIII', 1), ('IIIIIIIIIIIIIIYYIIIIIIIIIIIIII', 1), ('IIIIIIIIIIIIIIIYYIIIIIIIIIIIII', 1), ('IIIIIIIIIIIIIIIIYYIIIIIIIIIIII', 1), ('IIIIIIIIIIIIIIIIIYYIIIIIIIIIII', 1), ('IIIIIIIIIIIIIIIIIIYYIIIIIIIIII', 1), ('IIIIIIIIIIIIIIIIIIIYYIIIIIIIII', 1), ('IIIIIIIIIIIIIIIIIIIIYYIIIIIIII', 1), ('IIIIIIIIIIIIIIIIIIIIIYYIIIIIII', 1), ('IIIIIIIIIIIIIIIIIIIIIIYYIIIIII', 1), ('IIIIIIIIIIIIIIIIIIIIIIIYYIIIII', 1), ('IIIIIIIIIIIIIIIIIIIIIIIIYYIIII', 1), ('IIIIIIIIIIIIIIIIIIIIIIIIIYYIII', 1), ('IIIIIIIIIIIIIIIIIIIIIIIIIIYYII', 1), ('IIIIIIIIIIIIIIIIIIIIIIIIIIIYYI', 1), ('IIIIIIIIIIIIIIIIIIIIIIIIIIIIYY', 1)]
Parameter för dä Algorithmus festlääje
Mir wähle heuristisch ne Wäät för dä Zickschritt dt (basierend op bovverste Grenze vun dä Hamiltonian-Norm). Ref [2] hät gezeich, dat ne genuch kleine Zickschritt es, un dat et bes zo enem Punkt besser es, dä Wäät z'ungerschätze statt övverschätze, weil Övverschätze Bidräch vun huhenergetische Zuständ zolööt, die söjar dä optimale Zustand em Krylov-Ruum verdarve künne. Anderersits föhrt de Wahl vun ze klein dazu, dat de Konditionierung vum Krylov-Teilruum schlächter weed, weil de Krylov-Basisvektore sisch weniger vun Zickschritt ze Zickschritt ungerscheide.
# Get Hamiltonian restricted to single-particle states
single_particle_H = np.zeros((n_qubits, n_qubits))
for i in range(n_qubits):
for j in range(i + 1):
for p, coeff in H_op.to_list():
p_x = Pauli(p).x
p_z = Pauli(p).z
if all(
p_x[k] == ((i == k) + (j == k)) % 2 for k in range(n_qubits)
):
sgn = (
(-1j) ** sum(p_z[k] and p_x[k] for k in range(n_qubits))
) * ((-1) ** p_z[i])
else:
sgn = 0
single_particle_H[i, j] += sgn * coeff
for i in range(n_qubits):
for j in range(i + 1, n_qubits):
single_particle_H[i, j] = np.conj(single_particle_H[j, i])
# Set dt according to spectral norm
dt = np.pi / np.linalg.norm(single_particle_H, ord=2)
dt
np.float64(0.10833078115826875)
Un setz ander Parameter vum Algorithmus. För et Tutorial beschränke mir oss op de Nutzung vun enem Krylov-Ruum met nor fünf Dimensione, wat ziemlisch beschränkend es.
# Set parameters for quantum Krylov algorithm
krylov_dim = 5 # size of Krylov subspace
num_trotter_steps = 6
dt_circ = dt / num_trotter_steps
Zustandsvörbereitung
Wähl ne Referenzzustand , dä irgendwie Övverlappung met däm Grundzustand hät. För dä Hamiltonian bruche mir dä Zustand met ener Anregung em mittlere Qubit als unse Referenzzustand.
qc_state_prep = QuantumCircuit(n_qubits)
qc_state_prep.x(int(n_qubits / 2) + 1)
qc_state_prep.draw("mpl", scale=0.5)
Zickentwicklung
Mir künne dä Zickentwicklungsoperator, dä vun enem gevvene Hamiltonian erzeugt weed, realisiere: övver de Lie-Trotter-Approximation.
t = Parameter("t")
## Create the time-evo op circuit
evol_gate = PauliEvolutionGate(
H_op, time=t, synthesis=LieTrotter(reps=num_trotter_steps)
)
qr = QuantumRegister(n_qubits)
qc_evol = QuantumCircuit(qr)
qc_evol.append(evol_gate, qargs=qr)
<qiskit.circuit.instructionset.InstructionSet at 0x11eef9be0>
Hadamard-Test

Woh eine vun dä Terme en dä Zerlegung vum Hamiltonian es un , gesteuerte Operatione sen, die , Vektore vum unitäre Krylov-Ruum vörbereide, met . Öm ze messe, wende zeez aan...
... dann mess:
Us dä Identität . Ähnlich levvt de Messung vun
## Create the time-evo op circuit
evol_gate = PauliEvolutionGate(
H_op, time=dt, synthesis=LieTrotter(reps=num_trotter_steps)
)
## Create the time-evo op dagger circuit
evol_gate_d = PauliEvolutionGate(
H_op, time=dt, synthesis=LieTrotter(reps=num_trotter_steps)
)
evol_gate_d = evol_gate_d.inverse()
# Put pieces together
qc_reg = QuantumRegister(n_qubits)
qc_temp = QuantumCircuit(qc_reg)
qc_temp.compose(qc_state_prep, inplace=True)
for _ in range(num_trotter_steps):
qc_temp.append(evol_gate, qargs=qc_reg)
for _ in range(num_trotter_steps):
qc_temp.append(evol_gate_d, qargs=qc_reg)
qc_temp.compose(qc_state_prep.inverse(), inplace=True)
# Create controlled version of the circuit
controlled_U = qc_temp.to_gate().control(1)
# Create hadamard test circuit for real part
qr = QuantumRegister(n_qubits + 1)
qc_real = QuantumCircuit(qr)
qc_real.h(0)
qc_real.append(controlled_U, list(range(n_qubits + 1)))
qc_real.h(0)
print(
"Schaltkreis för de Berechnung vum reale Deel vun dä Övverlappung en S övver dä Hadamard-Test"
)
qc_real.draw("mpl", fold=-1, scale=0.5)
Schaltkreis för de Berechnung vum reale Deel vun dä Övverlappung en S övver dä Hadamard-Test
Dä Hadamard-Test-Schaltkreis kann ne deefe Schaltkreis sin, sobald mer en native Gates zerläje (wat noch mieh zunimmp, wann mer de Topologie vum Gerät berücksichtige)
print(
"Aanzahl vun Lare vun 2Q-Operatione",
qc_real.decompose(reps=2).depth(lambda x: x[0].num_qubits == 2),
)
Aanzahl vun Lare vun 2Q-Operatione 112753
Schritt 2: Optimier et Problem för de Usführung op Quantehardware
Effiziente Hadamard-Test
Mir künne de deefe Schaltkreise för dä Hadamard-Test optimiere, die mir erhalde han, endemm mir e paar Approximatione enföhre un oss op e paar Aanname övver dä Modell-Hamiltonian verlosse. Zom Beispill betrach dä folgend Schaltkreis för dä Hadamard-Test:

Nimm aan, mer künne klassisch berechne, dä Eigenwäät vun unger däm Hamiltonian . Dat es erföllt, wann dä Hamiltonian de U(1)-Symmetrie erhallt. Obvoll dat en starke Aanname schingk, git et vill Fäll, woh et sicher es aanzunämme, dat et ne Vakuumzustand git (en däm Fall kütt et zo däm Zustand), dä vun dä Wirking vum Hamiltonian unberührt bliev. Dat stemmp zom Beispill för Chemie-Hamiltoniane, die stabile Molekül beschriive (woh de Aanzahl vun Elektrone erhalte bliev). Gevve dat de Pfoote dä gewünschte Referenzzustand vörbereidt , zom Beispill öm dä HF-Zustand för Chemie vörzubereide, wör e Produkt vun Einzel-Qubit-NOTs sin, also es gesteuert- nor e Produkt vun CNOTs. Dann implementiert dä Schaltkreis dovve dä folgend Zustand vör dä Messung:
woh mir de klassisch simulierbare Phasenverschiebung en dä dritte Zeil bruche han. Doher wäde de Erwartungswääte erhalte als
Met dänne Aanname kunnte mir de Erwartungswääte vun Operatore vun Intress met winnijer gesteuerte Operatione schrieve. Tatsächlich müsse mir nor de gesteuerte Zustandsvörbereitung implementiere un nit de gesteuerte Zickentwicklunge. Unse Berechnung wie dovve neu ze formuliere erlaubt oss, de Deef vun dä resultierend Schaltkreise stark ze reduziere.
Zerlääj dä Zickentwicklungsoperator met Trotter-Zerlegung
Statt dä Zickentwicklungsoperator exakt ze implementiere, künne mir de Trotter-Zerlegung bruche, öm en Approximation dovun ze implementiere. Mehrfache Wiederholunge vun ener bestemmte Trotter-Zerlegung git oss en weitere Reduzierung vum Fehler, dä vun dä Approximation engeföhrt weed. Em Folgend baue mir de Trotter-Implementierung direkt op de effizientste Wies för dä Interaktionsgraf vum Hamiltonian, dä mir betrachte (nor Nohberinteraktione). En dä Praxis föje mir Pauli-Rotatione , , met enem parametrisierte Winkel en, die dä ungefähre Implementierung vun entspreche. Wääje däm Ungescheed en dä Definition vun dä Pauli-Rotatione un dä Zickentwicklung, die mir ze implementiere versöke, müsse mir dä Parameter bruche, öm en Zickentwicklung vun ze erreichecele. Dofür kehre mir de Reihenfolg vun dä Operatione för ungerade Aanzahle vun Wiederholunge vun dä Trotter-Schritte öm, wat funktioneell equivalent es, ävver et erlaubt, nebenenand liegende Operatione en enem einzelne unitäre z'synthetisiere. Dat git ne vill flachere Schaltkreis als wat mir met dä generische PauliEvolutionGate() Funktioneätät erhalde.
t = Parameter("t")
# Create instruction for rotation about XX+YY-ZZ:
Rxyz_circ = QuantumCircuit(2)
Rxyz_circ.rxx(t, 0, 1)
Rxyz_circ.ryy(t, 0, 1)
Rxyz_circ.rzz(t, 0, 1)
Rxyz_instr = Rxyz_circ.to_instruction(label="RXX+YY+ZZ")
interaction_list = [
[[i, i + 1] for i in range(0, n_qubits - 1, 2)],
[[i, i + 1] for i in range(1, n_qubits - 1, 2)],
] # linear chain
qr = QuantumRegister(n_qubits)
trotter_step_circ = QuantumCircuit(qr)
for i, color in enumerate(interaction_list):
for interaction in color:
trotter_step_circ.append(Rxyz_instr, interaction)
if i < len(interaction_list) - 1:
trotter_step_circ.barrier()
reverse_trotter_step_circ = trotter_step_circ.reverse_ops()
qc_evol = QuantumCircuit(qr)
for step in range(num_trotter_steps):
if step % 2 == 0:
qc_evol = qc_evol.compose(trotter_step_circ)
else:
qc_evol = qc_evol.compose(reverse_trotter_step_circ)
qc_evol.decompose().draw("mpl", fold=-1, scale=0.5)

Bruuch ne optimierte Schaltkreis för de Zustandsvörbereitung
control = 0
excitation = int(n_qubits / 2) + 1
controlled_state_prep = QuantumCircuit(n_qubits + 1)
controlled_state_prep.cx(control, excitation)
controlled_state_prep.draw("mpl", fold=-1, scale=0.5)
Schablone-Schaltkreise för de Berechnung vun Matrixelemente vun un övver dä Hadamard-Test
Dä einzige Ungescheed zwesche dä Schaltkreise, die em Hadamard-Test bruche wäde, es de Phase em Zickentwicklungsoperator un de gemessene Observable. Doher künne mir ne Schablone-Schaltkreis vörbereide, dä dä generische Schaltkreis för dä Hadamard-Test darstellt, met Platzhalter för de Gates, die vum Zickentwicklungsoperator afhänge.
# Parameters for the template circuits
parameters = []
for idx in range(1, krylov_dim):
parameters.append(2 * dt_circ * (idx))
# Create modified hadamard test circuit
qr = QuantumRegister(n_qubits + 1)
qc = QuantumCircuit(qr)
qc.h(0)
qc.compose(controlled_state_prep, list(range(n_qubits + 1)), inplace=True)
qc.barrier()
qc.compose(qc_evol, list(range(1, n_qubits + 1)), inplace=True)
qc.barrier()
qc.x(0)
qc.compose(
controlled_state_prep.inverse(), list(range(n_qubits + 1)), inplace=True
)
qc.x(0)
qc.decompose().draw("mpl", fold=-1)

print(
"Dä optimierte Schaltkreis hät en 2Q-Gates-Deef: ",
qc.decompose().decompose().depth(lambda x: x[0].num_qubits == 2),
)
Dä optimierte Schaltkreis hät en 2Q-Gates-Deef: 74
Mir han de Deef vum Hadamard-Test erheblich reduziert met ener Kombination vun Trotter-Approximation un ungesteuerte Unitäre
Schritt 3: Föhr us met Qiskit-Primitive
Instanziier et Backend un setz Runtime-Parameter
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)
if (
"if_else" not in backend.target.operation_names
): # Needed as "op_name" could be "if_else"
backend.target.add_instruction(IfElseOp, name="if_else")
print(backend.name)
Transpilierung op ene QPU
Zeez loss oss Subsets vun dä Kopplungskaat met "gude" performende Qubits ussuche (woh "gud" hee ziemlich willkörlich es, mir welle hauptsächlich echt schläächt performende Qubits vermeide) un e neu Target för de Transpilierung erstelle
target = backend.target
cmap = target.build_coupling_map(filter_idle_qubits=True)
cmap_list = list(cmap.get_edges())
cust_cmap_list = copy.deepcopy(cmap_list)
for q in range(target.num_qubits):
meas_err = target["measure"][(q,)].error
t2 = target.qubit_properties[q].t2 * 1e6
if meas_err > 0.02 or t2 < 100:
for q_pair in cmap_list:
if q in q_pair:
try:
cust_cmap_list.remove(q_pair)
except:
continue
for q in cmap_list:
op_name = list(target.operation_names_for_qargs(q))[0]
twoq_gate_err = target[f"{op_name}"][q].error
if twoq_gate_err > 0.005:
for q_pair in cmap_list:
if q == q_pair:
try:
cust_cmap_list.remove(q)
except:
continue
cust_cmap = CouplingMap(cust_cmap_list)
cust_target = Target.from_configuration(
basis_gates=backend.configuration().basis_gates,
coupling_map=cust_cmap,
)
Dann transpilier dä virtuelle Schaltkreis op et besste physikalische Layout en däm neue Target
basis_gates = list(target.operation_names)
pm = generate_preset_pass_manager(
optimization_level=3,
target=cust_target,
basis_gates=basis_gates,
)
qc_trans = pm.run(qc)
print("depth", qc_trans.depth(lambda x: x[0].num_qubits == 2))
print("num 2q ops", qc_trans.count_ops())
print(
"physical qubits",
sorted(
[
idx
for idx, qb in qc_trans.layout.initial_layout.get_physical_bits().items()
if qb._register.name != "ancilla"
]
),
)
depth 52
num 2q ops OrderedDict([('rz', 2058), ('sx', 1703), ('cz', 728), ('x', 84), ('barrier', 8)])
physical qubits [91, 92, 93, 94, 95, 98, 99, 108, 109, 110, 111, 113, 114, 115, 119, 127, 132, 133, 134, 135, 137, 139, 147, 148, 149, 150, 151, 152, 153, 154, 155]
Erstell PUBs för de Usführung met Estimator
# Define observables to measure for S
observable_S_real = "I" * (n_qubits) + "X"
observable_S_imag = "I" * (n_qubits) + "Y"
observable_op_real = SparsePauliOp(
observable_S_real
) # define a sparse pauli operator for the observable
observable_op_imag = SparsePauliOp(observable_S_imag)
layout = qc_trans.layout # get layout of transpiled circuit
observable_op_real = observable_op_real.apply_layout(
layout
) # apply physical layout to the observable
observable_op_imag = observable_op_imag.apply_layout(layout)
observable_S_real = (
observable_op_real.paulis.to_labels()
) # get the label of the physical observable
observable_S_imag = observable_op_imag.paulis.to_labels()
observables_S = [[observable_S_real], [observable_S_imag]]
# Define observables to measure for H
# Hamiltonian terms to measure
observable_list = []
for pauli, coeff in zip(H_op.paulis, H_op.coeffs):
# print(pauli)
observable_H_real = pauli[::-1].to_label() + "X"
observable_H_imag = pauli[::-1].to_label() + "Y"
observable_list.append([observable_H_real])
observable_list.append([observable_H_imag])
layout = qc_trans.layout
observable_trans_list = []
for observable in observable_list:
observable_op = SparsePauliOp(observable)
observable_op = observable_op.apply_layout(layout)
observable_trans_list.append([observable_op.paulis.to_labels()])
observables_H = observable_trans_list
# Define a sweep over parameter values
params = np.vstack(parameters).T
# Estimate the expectation value for all combinations of
# observables and parameter values, where the pub result will have
# shape (# observables, # parameter values).
pub = (qc_trans, observables_S + observables_H, params)
Föhr Schaltkreise us
Schaltkreise för sin klassisch berechenbar
qc_cliff = qc.assign_parameters({t: 0})
# Get expectation values from experiment
S_expval_real = StabilizerState(qc_cliff).expectation_value(
Pauli("I" * (n_qubits) + "X")
)
S_expval_imag = StabilizerState(qc_cliff).expectation_value(
Pauli("I" * (n_qubits) + "Y")
)
# Get expectation values
S_expval = S_expval_real + 1j * S_expval_imag
H_expval = 0
for obs_idx, (pauli, coeff) in enumerate(zip(H_op.paulis, H_op.coeffs)):
# Get expectation values from experiment
expval_real = StabilizerState(qc_cliff).expectation_value(
Pauli(pauli[::-1].to_label() + "X")
)
expval_imag = StabilizerState(qc_cliff).expectation_value(
Pauli(pauli[::-1].to_label() + "Y")
)
expval = expval_real + 1j * expval_imag
# Fill-in matrix elements
H_expval += coeff * expval
print(H_expval)
(25+0j)
Föhr Schaltkreise för un met däm Estimator us
# Experiment options
num_randomizations = 300
num_randomizations_learning = 30
shots_per_randomization = 100
noise_factors = [1, 1.2, 1.4]
learning_pair_depths = [0, 4, 24, 48]
experimental_opts = {}
experimental_opts["resilience"] = {
"measure_mitigation": True,
"measure_noise_learning": {
"num_randomizations": num_randomizations_learning,
"shots_per_randomization": shots_per_randomization,
},
"zne_mitigation": True,
"zne": {"noise_factors": noise_factors},
"layer_noise_learning": {
"max_layers_to_learn": 10,
"layer_pair_depths": learning_pair_depths,
"shots_per_randomization": shots_per_randomization,
"num_randomizations": num_randomizations_learning,
},
"zne": {
"amplifier": "pea",
"extrapolated_noise_factors": [0] + noise_factors,
},
}
experimental_opts["twirling"] = {
"num_randomizations": num_randomizations,
"shots_per_randomization": shots_per_randomization,
"strategy": "all",
}
estimator = Estimator(mode=backend, options=experimental_opts)
job = estimator.run([pub])
Schritt 4: Nohverarbeidung un Rückgab vum Ergebnis em gewünschte klassische Format
results = job.result()[0]
Berechne Effektive Hamiltonian- un Övverlappungsmatritze
Zeez berechne de Phase, die vum Zustand während dä ungesteuerte Zickentwicklung akkumuliert weed
prefactors = [
np.exp(-1j * sum([c for p, c in H_op.to_list() if "Z" in p]) * i * dt)
for i in range(1, krylov_dim)
]
Sobald mir de Ergebnisse vun dä Schaltkreisusführunge han, künne mir de Date nohverarbeide, öm de Matrixelemente vun ze berechne
# Assemble S, the overlap matrix of dimension D:
S_first_row = np.zeros(krylov_dim, dtype=complex)
S_first_row[0] = 1 + 0j
# Add in ancilla-only measurements:
for i in range(krylov_dim - 1):
# Get expectation values from experiment
expval_real = results.data.evs[0][0][
i
] # automatic extrapolated evs if ZNE is used
expval_imag = results.data.evs[1][0][
i
] # automatic extrapolated evs if ZNE is used
# Get expectation values
expval = expval_real + 1j * expval_imag
S_first_row[i + 1] += prefactors[i] * expval
S_first_row_list = S_first_row.tolist() # for saving purposes
S_circ = np.zeros((krylov_dim, krylov_dim), dtype=complex)
# Distribute entries from first row across matrix:
for i, j in it.product(range(krylov_dim), repeat=2):
if i >= j:
S_circ[j, i] = S_first_row[i - j]
else:
S_circ[j, i] = np.conj(S_first_row[j - i])
Matrix(S_circ)
Un de Matrixelemente vun
# Assemble S, the overlap matrix of dimension D:
H_first_row = np.zeros(krylov_dim, dtype=complex)
H_first_row[0] = H_expval
for obs_idx, (pauli, coeff) in enumerate(zip(H_op.paulis, H_op.coeffs)):
# Add in ancilla-only measurements:
for i in range(krylov_dim - 1):
# Get expectation values from experiment
expval_real = results.data.evs[2 + 2 * obs_idx][0][
i
] # automatic extrapolated evs if ZNE is used
expval_imag = results.data.evs[2 + 2 * obs_idx + 1][0][
i
] # automatic extrapolated evs if ZNE is used
# Get expectation values
expval = expval_real + 1j * expval_imag
H_first_row[i + 1] += prefactors[i] * coeff * expval
H_first_row_list = H_first_row.tolist()
H_eff_circ = np.zeros((krylov_dim, krylov_dim), dtype=complex)
# Distribute entries from first row across matrix:
for i, j in it.product(range(krylov_dim), repeat=2):
if i >= j:
H_eff_circ[j, i] = H_first_row[i - j]
else:
H_eff_circ[j, i] = np.conj(H_first_row[j - i])
Matrix(H_eff_circ)
Endlich künne mir et verallgemeinert Eigenwäätproblem för löse:
un en Schätzung vun dä Grundzustandsenergie kreije
gnd_en_circ_est_list = []
for d in range(1, krylov_dim + 1):
# Solve generalized eigenvalue problem for different size of the Krylov space
gnd_en_circ_est = solve_regularized_gen_eig(
H_eff_circ[:d, :d], S_circ[:d, :d], threshold=9e-1
)
gnd_en_circ_est_list.append(gnd_en_circ_est)
print("De geschätzde Grundzustandsenergie es: ", gnd_en_circ_est)
De geschätzde Grundzustandsenergie es: 25.0
De geschätzde Grundzustandsenergie es: 22.572154819954875
De geschätzde Grundzustandsenergie es: 21.691509219286587
De geschätzde Grundzustandsenergie es: 21.23882298756386
De geschätzde Grundzustandsenergie es: 20.965499325470294
För ne Einzelpartikel-Sektor künne mir effizient dä Grundzustand vun däm Sektor vum Hamiltonian klassisch berechne
gs_en = single_particle_gs(H_op, n_qubits)
n_sys_qubits 30
n_exc 1 , subspace dimension 31
single particle ground state energy: 21.021912418526906
plt.plot(
range(1, krylov_dim + 1),
gnd_en_circ_est_list,
color="blue",
linestyle="-.",
label="KQD-Schätzung",
)
plt.plot(
range(1, krylov_dim + 1),
[gs_en] * krylov_dim,
color="red",
linestyle="-",
label="exakt",
)
plt.xticks(range(1, krylov_dim + 1), range(1, krylov_dim + 1))
plt.legend()
plt.xlabel("Krylov-Ruum-Dimension")
plt.ylabel("Energie")
plt.title(
"Schätzung vun dä Grundzustandsenergie met Krylov-Quante-Diagonalisierung"
)
plt.show()
Aanhang: Krylov-Teilruum us echte Zickentwicklunge
Dä unitäre Krylov-Ruum es definiert als
för irgendenem Zickschritt , dä mir später bestimme wäde. Nimm temporär aan, es gerade: dann definier . Bemärk, dat wann mir dä Hamiltonian en dä Krylov-Ruum dovve projiziere, es hä unungerscheidbar vum Krylov-Ruum
dat heisst, woh all Zickentwicklunge öm Zickschritte retuur verschove wäde. Dä Grund doför, dat et unungerscheidbar es, es weil de Matrixelemente