Ungefähre Quantenkompilierung für Zeitentwicklungsschaltungen
Geschätzte Laufzeit: 15 Sekunden auf einem Heron-Prozessor (HINWEIS: Dies ist lediglich eine Schätzung. (Die Laufzeit kann variieren.)
Lernziele
- So verwenden Sie das AQC-Tensor-Qiskit-Add-on, um tiefe Trotter-Schaltungen in flache Ansatz-Schaltungen zu komprimieren
- Wie man aus einem Trotter-Schaltkreis einen parametrisierten Ansatz generiert und dessen Parameter mithilfe von Tensornetzwerk-Methoden (MPS) optimiert
- Wie man die Genauigkeit eines komprimierten Schaltkreises anhand der Sollentwicklung bewertet und ihn auf Quantenhardware ausführt
Voraussetzungen
- Grundlagen von Quantenschaltungen
- Hamilton-Simulation und Trotterisierung
- Einführung in die Grundelemente
Hintergrund
Dieses Tutorial zeigt, wie man die approximative Quantenkompilierung unter Verwendung von Tensornetzwerken (AQC-Tensor) mit Qiskit implementiert, um die Leistung von Quantenschaltungen zu verbessern. AQC-Tensor komprimiert tiefe Trotter-Schaltkreise zu flacheren, hardwarefreundlicheren Schaltkreisen, wobei die Simulationsgenauigkeit erhalten bleibt.
So funktioniert AQC-Tensor
Betrachten wir die Simulation eines Hamilton-Operators über die Gesamtzeit unter Verwendung von Trotter-Schritten. Der vollständige Trotter-Kreis lautet:
Bei einem naiven Ansatz werden nur wenige Trotter-Schritte verwendet, um die Schaltungstiefe überschaubar zu halten, was jedoch zu einem erheblichen Trotter-Fehler führt. AQC-Tensor löst diesen Konflikt, indem es Genauigkeit und Tiefe voneinander trennt:
-
Zielschaltung (hohe Genauigkeit, tief): Konstruieren Sie eine Trotter-Schaltung mit vielen Schritten – beispielsweise – für dieselbe Entwicklungszeit. Diese Schaltung weist einen weitaus geringeren Trotter-Fehler auf, ist jedoch für die Hardware zu komplex. Da dies klassisch lediglich als Matrixproduktzustand (MPS) simuliert wird, spielt die Tiefe keine Rolle.
-
Ansatz-Schaltung (geringe Tiefe, parametrisiert): Definieren Sie eine parametrisierte Schaltung mit derselben Struktur wie eine Ein-Schritt-Trotter-Schaltung. Initialisiere es so, dass „ “, und optimiere dann iterativ „ “, sodass „ “ den hochgenauen Zielzustand so genau wie möglich wiedergibt.
Das Ergebnis ist eine Schaltung, die die Tiefe eines einzelnen Trotter-Schritts beibehält, aber die Genauigkeit vieler Schritte erreicht, wodurch sie für Quantencomputer der nahen Zukunft realisierbar wird.
Wann sollte man AQC-Tensor verwenden?
AQC-Tensor ist am effektivsten, wenn:
- Die Schaltungstiefe übersteigt die Kohärenzzeiten der Hardware. Wenn eine Trotter-Simulation mehr Trotter-Schritte erfordert, als das Gerät unterstützen kann, kann AQC-Tensor die Entwicklung in eine flachere Schaltung komprimieren.
- Verschränkung bleibt klassisch berechenbar. Die Gesamtverschränkung in einem zeitlich entwickelten Zustand hängt in erster Linie von der Entwicklungszeit ab, nicht von der Anzahl der Trotter-Schritte . Das bedeutet, dass eine Zielschaltung mit Schritten in der Regel nicht schwieriger als MPS darzustellen ist als eine mit Schritten, solange kurz genug ist, damit die Bindungsabstände überschaubar bleiben.
- Es gibt einen natürlichen Ansatz. Da der Ansatz die Struktur eines Trotter-Kreises widerspiegelt, bietet er einen physikalisch begründeten Ausgangspunkt mit genau definierten Anfangsparametern und vermeidet so die Konvergenzprobleme, die bei beliebigen Variationsansätzen auftreten können.
Dieser Ansatz unterscheidet sich von der generischen Schaltungskompression: Anstatt zu versuchen, eine beliebige Unitarität mit weniger Gatter zu approximieren, behält AQC-Tensor die gleiche Gatterstruktur bei und optimiert deren Parameter, um den Trotter-Fehler zu reduzieren. Weitere Informationen finden Sie in der AQC-Tensor-Dokumentation.
Dieses Tutorial führt Sie durch den gesamten AQC-Tensor-Workflow zur Zustandsvorbereitung: Definition eines Hamilton-Operators, Erzeugung von Trotter-Schaltkreisen, deren Komprimierung mittels Tensor-Netzwerk-Optimierung und Ausführung des Ergebnisses auf der Hardware „ IBM Quantum® “.
Anforderungen
Vergewissern Sie sich vor Beginn dieses Tutorials, dass Sie die folgenden Komponenten installiert haben:
- Qiskit SDK v2.0 oder höher, mit Visualisierungsunterstützung
- Qiskit Runtime v0.22 oder später (
pip install qiskit-ibm-runtime) - AQC-Tensor Qiskit-Erweiterung (
pip install 'qiskit-addon-aqc-tensor[aer,quimb-jax]')
Einrichtung
import numpy as np
import quimb.tensor
import datetime
import matplotlib.pyplot as plt
from scipy.linalg import expm
from scipy.optimize import OptimizeResult, minimize
from qiskit.quantum_info import SparsePauliOp, Pauli
from qiskit.transpiler import CouplingMap
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit import QuantumCircuit
from qiskit.synthesis import SuzukiTrotter
from qiskit_addon_utils.problem_generators import (
generate_time_evolution_circuit,
)
from qiskit_addon_aqc_tensor.ansatz_generation import (
generate_ansatz_from_circuit,
)
from qiskit_addon_aqc_tensor.objective import MaximizeStateFidelity
from qiskit_addon_aqc_tensor.simulation.quimb import QuimbSimulator
from qiskit_addon_aqc_tensor.simulation import tensornetwork_from_circuit
from qiskit_addon_aqc_tensor.simulation import compute_overlap
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import EstimatorV2 as Estimator
from qiskit_ibm_runtime.fake_provider import FakeKyiv
from rustworkx.visualization import graphviz_drawBeispiel für einen Simulator im kleinen Maßstab
In diesem Abschnitt wird der AQC-Tensor-Workflow anhand eines Systems mit 10 Standorten Schritt für Schritt veranschaulicht. Wir simulieren die Dynamik einer XXZ-Spinkette mit 10 Gitterpunkten, einem vielfach untersuchten Modell zur Untersuchung von Spinwechselwirkungen und magnetischen Eigenschaften.
Der Hamilton-Operator lautet wie folgt:
wobei ein Zufallskoeffizient für die Kanten und ist.
Schritt 1: Klassische Eingaben einem Quantenproblem zuordnen
In diesem Schritt:
- Definieren Sie den Hamilton-Operator, die Observable und den Anfangszustand.
- Berechnen Sie den genauen Erwartungswert auf klassische Weise, um ihn später vergleichen zu können.
- Erstellen Sie eine hochgenaue Trotter-Schaltung (das AQC-Ziel) und komprimieren Sie diese mithilfe von AQC-Tensor zu einem Ansatz mit geringer Tiefe.
Bestimme den Hamilton-Operator, die Observable und den Anfangszustand
# L is the number of sites in the 1D spin chain
L = 10
# Generate the coupling map
edge_list = [(i - 1, i) for i in range(1, L)]
even_edges = edge_list[::2]
odd_edges = edge_list[1::2]
coupling_map = CouplingMap(edge_list)
# Generate random coefficients for our XXZ Hamiltonian
np.random.seed(0)
Js = np.random.rand(L - 1) + 0.5 * np.ones(L - 1)
hamiltonian = SparsePauliOp(Pauli("I" * L))
for i, edge in enumerate(even_edges + odd_edges):
hamiltonian += SparsePauliOp.from_sparse_list(
[
("XX", (edge), Js[i] / 2),
("YY", (edge), Js[i] / 2),
("ZZ", (edge), Js[i]),
],
num_qubits=L,
)
# Generate a ZZ observable between the two middle qubits
observable = SparsePauliOp.from_sparse_list(
[("ZZ", (L // 2 - 1, L // 2), 1.0)], num_qubits=L
)
# Generate an initial Néel state |1010101010⟩
initial_state_circuit = QuantumCircuit(L)
for i in range(L):
if i % 2:
initial_state_circuit.x(i)
print("Hamiltonian:", hamiltonian)
print("Observable:", observable)
graphviz_draw(coupling_map.graph, method="circo")Output:
Hamiltonian: SparsePauliOp(['IIIIIIIIII', 'IIIIIIIIXX', 'IIIIIIIIYY', 'IIIIIIIIZZ', 'IIIIIIXXII', 'IIIIIIYYII', 'IIIIIIZZII', 'IIIIXXIIII', 'IIIIYYIIII', 'IIIIZZIIII', 'IIXXIIIIII', 'IIYYIIIIII', 'IIZZIIIIII', 'XXIIIIIIII', 'YYIIIIIIII', 'ZZIIIIIIII', 'IIIIIIIXXI', 'IIIIIIIYYI', 'IIIIIIIZZI', 'IIIIIXXIII', 'IIIIIYYIII', 'IIIIIZZIII', 'IIIXXIIIII', 'IIIYYIIIII', 'IIIZZIIIII', 'IXXIIIIIII', 'IYYIIIIIII', 'IZZIIIIIII'],
coeffs=[1. +0.j, 0.52440675+0.j, 0.52440675+0.j, 1.0488135 +0.j,
0.60759468+0.j, 0.60759468+0.j, 1.21518937+0.j, 0.55138169+0.j,
0.55138169+0.j, 1.10276338+0.j, 0.52244159+0.j, 0.52244159+0.j,
1.04488318+0.j, 0.4618274 +0.j, 0.4618274 +0.j, 0.9236548 +0.j,
0.57294706+0.j, 0.57294706+0.j, 1.14589411+0.j, 0.46879361+0.j,
0.46879361+0.j, 0.93758721+0.j, 0.6958865 +0.j, 0.6958865 +0.j,
1.391773 +0.j, 0.73183138+0.j, 0.73183138+0.j, 1.46366276+0.j])
Observable: SparsePauliOp(['IIIIZZIIII'],
coeffs=[1.+0.j])
Berechne den genauen Erwartungswert
Bei einem System dieser Größenordnung können wir den exakten zeitabhängigen Erwartungswert direkt mittels Matrixexponentiation berechnen. Dies dient uns als Referenzwert für die Bewertung der Genauigkeit der AQC-Schaltung.
aqc_evolution_time = 0.2
# Each baseline Trotter step covers dt = aqc_evolution_time / 3
# The subsequent (uncompressed) step covers 1 additional dt
subsequent_evolution_time = aqc_evolution_time / 3
total_evolution_time = aqc_evolution_time + subsequent_evolution_time
# Compute exact expectation value via matrix exponentiation
H_matrix = hamiltonian.to_matrix()
U_exact = expm(-1j * H_matrix * total_evolution_time)
# Build the initial state vector (Néel state)
initial_state_vec = np.zeros(2**L)
state_idx = sum(2**i for i in range(L) if i % 2)
initial_state_vec[state_idx] = 1.0
# Evolve and compute expectation value
evolved_state = U_exact @ initial_state_vec
obs_matrix = observable.to_matrix()
exact_expval = (evolved_state.conj() @ obs_matrix @ evolved_state).real
print(f"AQC evolution time: {aqc_evolution_time}")
print(f"Subsequent evolution time: {subsequent_evolution_time:.6f}")
print(f"Total evolution time: {total_evolution_time:.6f}")
print(f"Exact expectation value: {exact_expval:.6f}")Output:
AQC evolution time: 0.2
Subsequent evolution time: 0.066667
Total evolution time: 0.266667
Exact expectation value: -0.700899
Erstellen Sie die AQC-Zielschaltung
Wir konstruieren nun den Trotter-Schaltkreis, der als Ziel für den AQC dienen soll. Diese Schaltung nutzt zahlreiche Trotter-Stufen (32), um eine hohe Genauigkeit zu gewährleisten. Da dies lediglich klassisch als MPS simuliert – und nicht auf Hardware ausgeführt – wird, stellt die große Tiefe kein Problem dar.
aqc_target_num_trotter_steps = 32
aqc_target_circuit = initial_state_circuit.copy()
aqc_target_circuit.compose(
generate_time_evolution_circuit(
hamiltonian,
synthesis=SuzukiTrotter(reps=aqc_target_num_trotter_steps),
time=aqc_evolution_time,
),
inplace=True,
)Erstellen Sie einen Ansatz, Anfangsparameter, einen Folgekreis und einen Referenzkreis
Als Nächstes konstruieren wir eine „gute“ Schaltung mit derselben Evolutionszeit wie das AQC-Ziel, jedoch mit weitaus weniger Trotter-Schritten (nur einem). Wir übergeben diese Funktion an generate_ansatz_from_circuit, die Folgendes zurückgibt:
- Eine allgemeine, parametrisierte Ansatz schaltung mit derselben Zwei-Qubit-Verbindung.
- Ausgangsparameter, die die Eingangsschaltung nachbilden, wenn sie in den Ansatz eingefügt werden.
Außerdem konstruieren wir:
- Eine nachfolgende Schaltung mit einem Trotter-Schritt, die (unkomprimiert) an den AQC-optimierten Teil angehängt wird, entsprechend dem Ansatz im Tutorial zum AQC-Tensor-Ausgangszustand.
- Ein Basis-Trotter-Schaltkreis, der vier Trotter-Schritte über die gesamte Laufzeit hinweg verwendet (
aqc_evolution_time + subsequent_evolution_time). Dies dient als Vergleichsgrundlage: Er entspricht dem, was man auf Hardware ohne AQC ausführen würde. Der AQC-Ansatz (3 komprimierte Schritte + 1 unkomprimierter Schritt) erzielt bei geringerer Tiefe eine höhere Genauigkeit.
aqc_ansatz_num_trotter_steps = 1
aqc_good_circuit = initial_state_circuit.copy()
aqc_good_circuit.compose(
generate_time_evolution_circuit(
hamiltonian,
synthesis=SuzukiTrotter(reps=aqc_ansatz_num_trotter_steps),
time=aqc_evolution_time,
),
inplace=True,
)
aqc_ansatz, aqc_initial_parameters = generate_ansatz_from_circuit(
aqc_good_circuit
)
# Subsequent circuit: 1 non-compressed Trotter step appended after AQC
subsequent_num_trotter_steps = 1
subsequent_circuit = generate_time_evolution_circuit(
hamiltonian,
synthesis=SuzukiTrotter(reps=subsequent_num_trotter_steps),
time=subsequent_evolution_time,
)
# Baseline Trotter circuit: 4 Trotter steps over total evolution time, no AQC
baseline_num_trotter_steps = 4
baseline_circuit = initial_state_circuit.copy()
baseline_circuit.compose(
generate_time_evolution_circuit(
hamiltonian,
synthesis=SuzukiTrotter(reps=baseline_num_trotter_steps),
time=total_evolution_time,
),
inplace=True,
)
print(
f"Target circuit: depth {aqc_target_circuit.depth(lambda x: x.operation.num_qubits == 2)}"
)
print(
f"Baseline circuit: depth {baseline_circuit.depth(lambda x: x.operation.num_qubits == 2)} ({baseline_num_trotter_steps} Trotter steps, time={total_evolution_time:.4f})"
)
print(
f"Subsequent circuit: depth {subsequent_circuit.depth(lambda x: x.operation.num_qubits == 2)} ({subsequent_num_trotter_steps} Trotter step, time={subsequent_evolution_time:.4f})"
)
print(
f"Ansatz circuit: depth {aqc_ansatz.depth(lambda x: x.operation.num_qubits == 2)}, with {len(aqc_initial_parameters)} parameters"
)
aqc_ansatz.draw("mpl", fold=-1)Output:
Target circuit: depth 384
Baseline circuit: depth 48 (4 Trotter steps, time=0.2667)
Subsequent circuit: depth 12 (1 Trotter step, time=0.0667)
Ansatz circuit: depth 3, with 156 parameters
Richten Sie eine Tensor-Network-Simulation ein und erstellen Sie das gewünschte MPS
Wir verwenden den Matrix-Product-State-Schaltkreissimulator (MPS) „quimb“, wobei JAX die automatische Differenzierung für die gradientbasierte Optimierung übernimmt. Anschließend erstellen wir eine MPS-Darstellung des Zielzustands und berechnen die anfängliche Übereinstimmung zwischen dem Ausgangsansatz und dem Ziel. Da es sich bei der Probleminstanz um ein relativ kleines Beispiel handelt, ist die anfängliche Genauigkeit recht hoch.
simulator_settings = QuimbSimulator(
quimb.tensor.CircuitMPS, autodiff_backend="jax"
)
aqc_target_mps = tensornetwork_from_circuit(
aqc_target_circuit, simulator_settings
)
print("Target MPS maximum bond dimension:", aqc_target_mps.psi.max_bond())
good_mps = tensornetwork_from_circuit(aqc_good_circuit, simulator_settings)
starting_fidelity = abs(compute_overlap(good_mps, aqc_target_mps)) ** 2
print(f"Starting fidelity: {starting_fidelity:.6f}")Output:
Target MPS maximum bond dimension: 5
Starting fidelity: 0.998246
Die Ansatzparameter optimieren
Wir minimieren die MaximizeStateFidelity Kostenfunktion mit Hilfe des L-BFGS-B-Optimierers. Der Optimierer passt die Ansatzparameter iterativ an, um die Übereinstimmung zwischen der Ansatzschaltung und dem Ziel-MPS zu maximieren.
aqc_stopping_fidelity = 1
aqc_max_iterations = 500
stopping_point = 1.0 - aqc_stopping_fidelity
objective = MaximizeStateFidelity(
aqc_target_mps, aqc_ansatz, simulator_settings
)
def callback(intermediate_result: OptimizeResult):
fidelity = 1 - intermediate_result.fun
print(
f"{datetime.datetime.now()} Intermediate result: Fidelity {fidelity:.8f}"
)
if intermediate_result.fun < stopping_point:
raise StopIteration
result = minimize(
objective,
aqc_initial_parameters,
method="L-BFGS-B",
jac=True,
options={"maxiter": aqc_max_iterations},
callback=callback,
)
if result.status not in (0, 1, 99):
raise RuntimeError(
f"Optimization failed: {result.message} (status={result.status})"
)
print(f"Done after {result.nit} iterations.")
aqc_final_parameters = result.xOutput:
2026-05-18 13:14:49.731596 Intermediate result: Fidelity 0.99952882
2026-05-18 13:14:49.734425 Intermediate result: Fidelity 0.99958531
2026-05-18 13:14:49.737101 Intermediate result: Fidelity 0.99960093
2026-05-18 13:14:49.739813 Intermediate result: Fidelity 0.99961046
2026-05-18 13:14:49.742969 Intermediate result: Fidelity 0.99962560
2026-05-18 13:14:49.745916 Intermediate result: Fidelity 0.99964395
2026-05-18 13:14:49.748615 Intermediate result: Fidelity 0.99968150
2026-05-18 13:14:49.753684 Intermediate result: Fidelity 0.99970569
2026-05-18 13:14:49.756208 Intermediate result: Fidelity 0.99973788
2026-05-18 13:14:49.759067 Intermediate result: Fidelity 0.99975385
2026-05-18 13:14:49.762321 Intermediate result: Fidelity 0.99976458
2026-05-18 13:14:49.765526 Intermediate result: Fidelity 0.99977661
2026-05-18 13:14:49.768496 Intermediate result: Fidelity 0.99978663
2026-05-18 13:14:49.771278 Intermediate result: Fidelity 0.99980236
2026-05-18 13:14:49.773735 Intermediate result: Fidelity 0.99981607
2026-05-18 13:14:49.776339 Intermediate result: Fidelity 0.99982811
2026-05-18 13:14:49.779177 Intermediate result: Fidelity 0.99985827
2026-05-18 13:14:49.782243 Intermediate result: Fidelity 0.99988354
2026-05-18 13:14:49.784904 Intermediate result: Fidelity 0.99991608
2026-05-18 13:14:49.787737 Intermediate result: Fidelity 0.99993336
2026-05-18 13:14:49.790414 Intermediate result: Fidelity 0.99993956
2026-05-18 13:14:49.793029 Intermediate result: Fidelity 0.99994421
2026-05-18 13:14:49.795585 Intermediate result: Fidelity 0.99994743
2026-05-18 13:14:49.835045 Intermediate result: Fidelity 0.99994791
2026-05-18 13:14:49.839786 Intermediate result: Fidelity 0.99994803
2026-05-18 13:14:49.842403 Intermediate result: Fidelity 0.99994898
2026-05-18 13:14:49.873779 Intermediate result: Fidelity 0.99994898
Done after 27 iterations.
Die endgültige AQC-Schaltung zusammenbauen
Nachdem wir die optimierten Parameter ermittelt haben, setzen wir sie in den Ansatz ein und fügen anschließend den (unkomprimierten) Trotter-Schritt hinzu. Die resultierende Schaltung hat die Tiefe eines einzelnen komprimierten Trotter-Schritts plus eines unkomprimierten Schritts, doch der komprimierte Teil erreicht eine Genauigkeit, die der von 32 Trotter-Schritten nahekommt.
aqc_final_circuit = aqc_ansatz.assign_parameters(aqc_final_parameters)
aqc_final_circuit.compose(subsequent_circuit, inplace=True)
aqc_final_circuit.draw("mpl", fold=-1)Output:
Schritt 2: Optimierung des Problems für die Ausführung auf Quantenhardware
Für dieses kleine Beispiel verwenden wir ein simuliertes Backend (FakeKyiv), um die Hardwareausführung lokal zu simulieren. Wir transpilieren sowohl die AQC-optimierte Schaltung (aqc_final_circuit) als auch die Basis-Trotter-Schaltung (baseline_circuit, vier Trotter-Schritte über die gesamte Laufzeit, ohne AQC) in die Befehlssatzarchitektur (ISA) des Backends, wobei optimization_level=3 verwendet wird, um die Schaltungstiefe weiter zu reduzieren.
backend = FakeKyiv()
pass_manager = generate_preset_pass_manager(
backend=backend, optimization_level=3
)
# Transpile the AQC-optimized circuit (compressed + subsequent step)
isa_circuit = pass_manager.run(aqc_final_circuit)
isa_observable = observable.apply_layout(isa_circuit.layout)
print(
"AQC circuit depth:",
isa_circuit.depth(lambda x: x.operation.num_qubits == 2),
)
# Transpile the baseline Trotter circuit (no AQC optimization)
isa_baseline_circuit = pass_manager.run(baseline_circuit)
isa_baseline_observable = observable.apply_layout(isa_baseline_circuit.layout)
print(
"Baseline Trotter circuit depth:",
isa_baseline_circuit.depth(lambda x: x.operation.num_qubits == 2),
)Output:
AQC circuit depth: 15
Baseline Trotter circuit depth: 27
Schritt 3: Mit „ Qiskit primitives “ ausführen
Wir verwenden die Primitive EstimatorV2 mit dem simulierten Backend, um sowohl die AQC-optimierte Schaltung als auch die Basis-Trotter-Schaltung auszuführen, und messen dabei jeweils die ZZ-Beobachtungsgröße.
estimator = Estimator(backend)
# Run both circuits
aqc_result = estimator.run([(isa_circuit, isa_observable)]).result()
baseline_result = estimator.run(
[(isa_baseline_circuit, isa_baseline_observable)]
).result()Schritt 4: Nachbearbeitung und Rückgabe des Ergebnisses im gewünschten klassischen Format
Wir ermitteln die Erwartungswerte aus beiden Durchläufen und vergleichen sie mit dem exakten Ergebnis. Der Referenz-Trotter-Schaltkreis zeigt, was wir ohne AQC bei gleicher Schaltkreistiefe erhalten würden, während der AQC-Schaltkreis die Verbesserung durch die Tensor-Netzwerk-Optimierung verdeutlicht.
aqc_expval = aqc_result[0].data.evs.tolist()
baseline_expval = baseline_result[0].data.evs.tolist()
print(f"Exact: {exact_expval:.4f}")
print(
f"Baseline Trotter: {baseline_expval:.4f}, |\u0394| = {np.abs(exact_expval - baseline_expval):.4f} (depth {isa_baseline_circuit.depth(lambda x: x.operation.num_qubits == 2)}, {baseline_num_trotter_steps} steps)"
)
print(
f"AQC (3+1): {aqc_expval:.4f}, |\u0394| = {np.abs(exact_expval - aqc_expval):.4f} (depth {isa_circuit.depth(lambda x: x.operation.num_qubits == 2)}, compressed+subsequent)"
)Output:
Exact: -0.7009
Baseline Trotter: -0.5400, |Δ| = 0.1609 (depth 27, 4 steps)
AQC (3+1): -0.5728, |Δ| = 0.1281 (depth 15, compressed+subsequent)
plt.style.use("seaborn-v0_8")
labels = [
f"Baseline Trotter\n({baseline_num_trotter_steps} steps, depth {isa_baseline_circuit.depth(lambda x: x.operation.num_qubits == 2)})",
f"AQC (3+1)\n(depth {isa_circuit.depth(lambda x: x.operation.num_qubits == 2)})",
]
values = [baseline_expval, aqc_expval]
colors = ["tab:orange", "tab:blue"]
plt.figure(figsize=(8, 5))
bars = plt.bar(labels, values, color=colors, width=0.5)
plt.axhline(
y=exact_expval,
color="tab:green",
linestyle="--",
linewidth=2,
label=f"Exact ({exact_expval:.4f})",
)
plt.ylabel("Expected Value")
plt.title(
"AQC-Tensor (3 compressed + 1 uncompressed) vs Baseline Trotter (10-site XXZ)"
)
plt.legend()
for bar in bars:
y_val = bar.get_height()
plt.text(
bar.get_x() + bar.get_width() / 2.0,
y_val,
f"{y_val:.4f}",
ha="center",
va="bottom" if y_val >= 0 else "top",
)
plt.axhline(y=0, color="black", linewidth=0.3)
plt.tight_layout()
plt.show()Output:
Beispiel für groß angelegte Hardware
Wir erweitern nun auf ein XXZ-Modell mit 50 Standorten, um AQC-Tensor an einem realistischeren Problemumfang zu demonstrieren. Der Ablauf ist derselbe wie im Beispiel für den kleinen Maßstab: Wir komprimieren drei Trotter-Schritte mittels AQC und fügen einen unkomprimierten Schritt an.
Bei einem System dieser Größenordnung ist die Matrixpotenzierung nicht durchführbar (Dimensionen ), daher berechnen wir den Referenzerwartungswert direkt aus einem hochgenauen MPS, das über den gesamten Zeitraum entwickelt wurde.
Schritte 1–4 zusammengefasst
# -------------------------Step 1-------------------------
# Define the 50-site spin chain
L = 50
edge_list = [(i - 1, i) for i in range(1, L)]
even_edges = edge_list[::2]
odd_edges = edge_list[1::2]
coupling_map = CouplingMap(edge_list)
# Random XXZ Hamiltonian
np.random.seed(0)
Js = np.random.rand(L - 1) + 0.5 * np.ones(L - 1)
hamiltonian = SparsePauliOp(Pauli("I" * L))
for i, edge in enumerate(even_edges + odd_edges):
hamiltonian += SparsePauliOp.from_sparse_list(
[
("XX", (edge), Js[i] / 2),
("YY", (edge), Js[i] / 2),
("ZZ", (edge), Js[i]),
],
num_qubits=L,
)
observable = SparsePauliOp.from_sparse_list(
[("ZZ", (L // 2 - 1, L // 2), 1.0)], num_qubits=L
)
# Initial Néel state
initial_state_circuit = QuantumCircuit(L)
for i in range(L):
if i % 2:
initial_state_circuit.x(i)
# Time parameters
aqc_evolution_time = 0.2
subsequent_evolution_time = aqc_evolution_time / 3
total_evolution_time = aqc_evolution_time + subsequent_evolution_time
# AQC target circuit (high-accuracy, 32 Trotter steps for AQC portion)
aqc_target_num_trotter_steps = 32
aqc_target_circuit = initial_state_circuit.copy()
aqc_target_circuit.compose(
generate_time_evolution_circuit(
hamiltonian,
synthesis=SuzukiTrotter(reps=aqc_target_num_trotter_steps),
time=aqc_evolution_time,
),
inplace=True,
)
# Generate ansatz from 1-step Trotter circuit
aqc_good_circuit = initial_state_circuit.copy()
aqc_good_circuit.compose(
generate_time_evolution_circuit(
hamiltonian,
synthesis=SuzukiTrotter(reps=1),
time=aqc_evolution_time,
),
inplace=True,
)
aqc_ansatz, aqc_initial_parameters = generate_ansatz_from_circuit(
aqc_good_circuit
)
# Subsequent circuit: 1 non-compressed Trotter step
subsequent_circuit = generate_time_evolution_circuit(
hamiltonian,
synthesis=SuzukiTrotter(reps=1),
time=subsequent_evolution_time,
)
# Baseline Trotter circuit: 4 Trotter steps over total evolution time, no AQC
baseline_num_trotter_steps = 4
baseline_circuit = initial_state_circuit.copy()
baseline_circuit.compose(
generate_time_evolution_circuit(
hamiltonian,
synthesis=SuzukiTrotter(reps=baseline_num_trotter_steps),
time=total_evolution_time,
),
inplace=True,
)
print(
f"Target circuit: depth {aqc_target_circuit.depth(lambda x: x.operation.num_qubits == 2)}"
)
print(
f"Ansatz circuit: depth {aqc_ansatz.depth(lambda x: x.operation.num_qubits == 2)}, with {len(aqc_initial_parameters)} parameters"
)
print(
f"Subsequent circuit: depth {subsequent_circuit.depth(lambda x: x.operation.num_qubits == 2)}"
)
print(
f"Baseline circuit: depth {baseline_circuit.depth(lambda x: x.operation.num_qubits == 2)} ({baseline_num_trotter_steps} steps, time={total_evolution_time:.4f})"
)
# Build target MPS and compute reference expectation value
simulator_settings = QuimbSimulator(
quimb.tensor.CircuitMPS, autodiff_backend="jax"
)
aqc_target_mps = tensornetwork_from_circuit(
aqc_target_circuit, simulator_settings
)
print("Target MPS maximum bond dimension:", aqc_target_mps.psi.max_bond())
# For the reference expectation value, we need the full evolution (AQC + subsequent)
# Build a high-accuracy full circuit for MPS reference
full_target_circuit = initial_state_circuit.copy()
full_target_circuit.compose(
generate_time_evolution_circuit(
hamiltonian,
synthesis=SuzukiTrotter(reps=aqc_target_num_trotter_steps),
time=total_evolution_time,
),
inplace=True,
)
full_target_mps = tensornetwork_from_circuit(
full_target_circuit, simulator_settings
)
exact_expval = full_target_mps.local_expectation(
quimb.pauli("Z") & quimb.pauli("Z"), (L // 2 - 1, L // 2)
).real.item()
print(f"Reference expectation value (from MPS): {exact_expval:.6f}")
# Optimize ansatz parameters
objective = MaximizeStateFidelity(
aqc_target_mps, aqc_ansatz, simulator_settings
)
def callback(intermediate_result: OptimizeResult):
fidelity = 1 - intermediate_result.fun
print(
f"{datetime.datetime.now()} Intermediate result: Fidelity {fidelity:.8f}"
)
result = minimize(
objective,
aqc_initial_parameters,
method="L-BFGS-B",
jac=True,
options={"maxiter": 500},
callback=callback,
)
if result.status not in (0, 1, 99):
raise RuntimeError(
f"Optimization failed: {result.message} (status={result.status})"
)
print(f"Done after {result.nit} iterations.")
# Assemble the final AQC circuit: optimized ansatz + subsequent Trotter step
aqc_final_circuit = aqc_ansatz.assign_parameters(result.x)
aqc_final_circuit.compose(subsequent_circuit, inplace=True)
# -------------------------Step 2-------------------------
service = QiskitRuntimeService()
backend = service.least_busy(min_num_qubits=127)
print(backend)
pass_manager = generate_preset_pass_manager(
backend=backend, optimization_level=3
)
isa_circuit = pass_manager.run(aqc_final_circuit)
isa_observable = observable.apply_layout(isa_circuit.layout)
print(
"AQC circuit depth:",
isa_circuit.depth(lambda x: x.operation.num_qubits == 2),
)
# Also transpile the baseline Trotter circuit (4 Trotter steps, no AQC)
isa_baseline_circuit = pass_manager.run(baseline_circuit)
isa_baseline_observable = observable.apply_layout(isa_baseline_circuit.layout)
print(
"Baseline Trotter circuit depth:",
isa_baseline_circuit.depth(lambda x: x.operation.num_qubits == 2),
)
# -------------------------Step 3-------------------------
# Submit both circuits in a single job
estimator = Estimator(backend)
estimator.options.environment.job_tags = ["TUT_AQCTE"]
job = estimator.run(
[
(isa_circuit, isa_observable),
(isa_baseline_circuit, isa_baseline_observable),
]
)
print("Job ID:", job.job_id())Output:
Target circuit: depth 385
Ansatz circuit: depth 7, with 816 parameters
Subsequent circuit: depth 12
Baseline circuit: depth 49 (4 steps, time=0.2667)
Target MPS maximum bond dimension: 5
Reference expectation value (from MPS): -0.738669
2026-05-18 13:02:11.219150 Intermediate result: Fidelity 0.99795732
2026-05-18 13:02:11.232256 Intermediate result: Fidelity 0.99822481
2026-05-18 13:02:11.245160 Intermediate result: Fidelity 0.99829520
2026-05-18 13:02:11.257765 Intermediate result: Fidelity 0.99832379
2026-05-18 13:02:11.270280 Intermediate result: Fidelity 0.99836416
2026-05-18 13:02:11.284116 Intermediate result: Fidelity 0.99840073
2026-05-18 13:02:11.296856 Intermediate result: Fidelity 0.99846863
2026-05-18 13:02:11.309602 Intermediate result: Fidelity 0.99865244
2026-05-18 13:02:11.322012 Intermediate result: Fidelity 0.99872665
2026-05-18 13:02:11.334195 Intermediate result: Fidelity 0.99892335
2026-05-18 13:02:11.346570 Intermediate result: Fidelity 0.99901045
2026-05-18 13:02:11.359202 Intermediate result: Fidelity 0.99907181
2026-05-18 13:02:11.371511 Intermediate result: Fidelity 0.99911125
2026-05-18 13:02:11.383870 Intermediate result: Fidelity 0.99918585
2026-05-18 13:02:11.396184 Intermediate result: Fidelity 0.99921504
2026-05-18 13:02:11.408543 Intermediate result: Fidelity 0.99924936
2026-05-18 13:02:11.422557 Intermediate result: Fidelity 0.99929226
2026-05-18 13:02:11.436275 Intermediate result: Fidelity 0.99933099
2026-05-18 13:02:11.449511 Intermediate result: Fidelity 0.99935792
2026-05-18 13:02:11.462093 Intermediate result: Fidelity 0.99937925
2026-05-18 13:02:11.475783 Intermediate result: Fidelity 0.99940690
2026-05-18 13:02:11.490254 Intermediate result: Fidelity 0.99944409
2026-05-18 13:02:11.503292 Intermediate result: Fidelity 0.99946840
2026-05-18 13:02:11.516064 Intermediate result: Fidelity 0.99949378
2026-05-18 13:02:11.532861 Intermediate result: Fidelity 0.99951380
2026-05-18 13:02:11.546182 Intermediate result: Fidelity 0.99955313
2026-05-18 13:02:11.559168 Intermediate result: Fidelity 0.99955707
2026-05-18 13:02:11.571753 Intermediate result: Fidelity 0.99959306
2026-05-18 13:02:11.584257 Intermediate result: Fidelity 0.99960486
2026-05-18 13:02:11.597610 Intermediate result: Fidelity 0.99961714
2026-05-18 13:02:11.610106 Intermediate result: Fidelity 0.99962953
2026-05-18 13:02:11.622515 Intermediate result: Fidelity 0.99963525
2026-05-18 13:02:11.635543 Intermediate result: Fidelity 0.99964658
2026-05-18 13:02:11.649044 Intermediate result: Fidelity 0.99965027
2026-05-18 13:02:11.664148 Intermediate result: Fidelity 0.99965802
2026-05-18 13:02:11.678033 Intermediate result: Fidelity 0.99966731
2026-05-18 13:02:11.692714 Intermediate result: Fidelity 0.99967780
2026-05-18 13:02:11.706753 Intermediate result: Fidelity 0.99968567
2026-05-18 13:02:11.720780 Intermediate result: Fidelity 0.99969139
2026-05-18 13:02:11.733471 Intermediate result: Fidelity 0.99969628
2026-05-18 13:02:11.745998 Intermediate result: Fidelity 0.99970331
2026-05-18 13:02:11.758424 Intermediate result: Fidelity 0.99970796
2026-05-18 13:02:11.771986 Intermediate result: Fidelity 0.99971165
2026-05-18 13:02:11.785841 Intermediate result: Fidelity 0.99971892
2026-05-18 13:02:11.799105 Intermediate result: Fidelity 0.99972226
2026-05-18 13:02:11.811623 Intermediate result: Fidelity 0.99972441
2026-05-18 13:02:11.824114 Intermediate result: Fidelity 0.99972679
2026-05-18 13:02:11.837179 Intermediate result: Fidelity 0.99972965
2026-05-18 13:02:12.345479 Intermediate result: Fidelity 0.99972965
Done after 49 iterations.
<IBMBackend('ibm_pittsburgh')>
AQC circuit depth: 71
Baseline Trotter circuit depth: 111
Job ID: d85kc6o0bvlc73d5nhn0
# -------------------------Step 4-------------------------
hw_results = job.result()
aqc_expval = hw_results[0].data.evs.tolist()
baseline_expval = hw_results[1].data.evs.tolist()
print(f"Exact (MPS): {exact_expval:.4f}")
print(
f"Baseline Trotter: {baseline_expval:.4f}, |\u0394| = {np.abs(exact_expval - baseline_expval):.4f}"
)
print(
f"AQC (3+1): {aqc_expval:.4f}, |\u0394| = {np.abs(exact_expval - aqc_expval):.4f}"
)
labels = [
f"Baseline Trotter\n({baseline_num_trotter_steps} steps, depth {isa_baseline_circuit.depth(lambda x: x.operation.num_qubits == 2)})",
f"AQC (3+1)\n(depth {isa_circuit.depth(lambda x: x.operation.num_qubits == 2)})",
]
values = [baseline_expval, aqc_expval]
colors = ["tab:orange", "tab:blue"]
plt.figure(figsize=(8, 5))
bars = plt.bar(labels, values, color=colors, width=0.5)
plt.axhline(
y=exact_expval,
color="tab:green",
linestyle="--",
linewidth=2,
label=f"Exact ({exact_expval:.4f})",
)
plt.ylabel("Expected Value")
plt.title(
"AQC-Tensor (3 compressed + 1 uncompressed) vs Baseline Trotter (50-site XXZ)"
)
plt.legend()
for bar in bars:
y_val = bar.get_height()
plt.text(
bar.get_x() + bar.get_width() / 2.0,
y_val,
f"{y_val:.4f}",
ha="center",
va="bottom" if y_val >= 0 else "top",
)
plt.axhline(y=0, color="black", linewidth=0.3)
plt.tight_layout()
plt.show()Output:
Exact (MPS): -0.7387
Baseline Trotter: -0.5955, |Δ| = 0.1432
AQC (3+1): -0.6734, |Δ| = 0.0653
Nächste Schritte
Wenn Sie diese Arbeit interessant fanden, könnten Sie auch an folgenden Materialien interessiert sein:
- Dokumentation zum AQC-Tensor-Add-on – umfasst die zugehörige unitarische AQC -Technik, die parametrisierte Schaltkreise so optimiert, dass sie einen angestrebten unitarischen Operator anstelle eines vorbereiteten Zustands approximieren
- Techniken zur Fehlerminderung und -unterdrückung
- Fehlerbehebungstechniken kombinieren