Näherungsweise Quantenkompilierung für Zeitentwicklungsschaltkreise
Geschätzter Ressourcenverbrauch: Fünf Minuten auf einem Eagle-Prozessor (HINWEIS: Dies ist nur eine Schätzung. Deine tatsächliche Laufzeit kann abweichen.)
Hintergrund
Dieses Tutorial zeigt, wie du Approximate Quantum Compilation (näherungsweise Quantenkompilierung) mithilfe von Tensornetzwerken (AQC-Tensor) mit Qiskit umsetzt, um die Leistung von Quantenschaltkreisen zu verbessern. Wir wenden AQC-Tensor im Kontext einer trotterisierten Zeitentwicklung an, um die Schaltkreistiefe zu reduzieren und dabei die Simulationsgenauigkeit zu erhalten – gemäß dem Qiskit-Framework für Zustandsvorbereitung und Optimierung. Du lernst, wie du aus einem initialen Trotter-Schaltkreis einen Ansatz-Schaltkreis mit geringer Tiefe erzeugst, ihn mit Tensornetzwerken optimierst und für die Ausführung auf Quantenhardware vorbereitest.
Das Hauptziel besteht darin, die Zeitentwicklung eines Modell-Hamiltonians mit reduzierter Schaltkreistiefe zu simulieren. Dies wird mit dem AQC-Tensor Qiskit-Addon qiskit-addon-aqc-tensor erreicht, das Tensornetzwerke – konkret Matrix-Produkt-Zustände (MPS) – nutzt, um den initialen Schaltkreis zu komprimieren und zu optimieren. Durch iterative Anpassungen bleibt der komprimierte Ansatz-Schaltkreis dem ursprünglichen Schaltkreis treu und ist dennoch für Quantenhardware der nahen Zukunft praktikabel. Weitere Details findest du in der zugehörigen Dokumentation sowie in einem einfachen Einstiegsbeispiel.
Approximate Quantum Compilation ist besonders vorteilhaft bei Quantensimulationen, die die Hardware-Kohärenzzeiten überschreiten, da sich damit komplexe Simulationen effizienter durchführen lassen. Dieses Tutorial führt dich durch den AQC-Tensor-Workflow in Qiskit: von der Initialisierung eines Hamiltonians über die Erzeugung von Trotter-Schaltkreisen bis hin zur Transpilierung des final optimierten Schaltkreises für ein Zielgerät.
Voraussetzungen
Stelle vor Beginn dieses Tutorials sicher, dass Folgendes installiert ist:
- Qiskit SDK v1.0 oder neuer, mit Unterstützung für Visualisierung
- Qiskit Runtime v0.22 oder neuer (
pip install qiskit-ibm-runtime) - AQC-Tensor Qiskit-Addon (
pip install 'qiskit-addon-aqc-tensor[aer,quimb-jax]')
Setup
# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-addon-aqc-tensor qiskit-addon-utils qiskit-ibm-runtime quimb rustworkx scipy
import numpy as np
import quimb.tensor
import datetime
import matplotlib.pyplot as plt
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 rustworkx.visualization import graphviz_draw
Teil I. Kleinskaliges Beispiel
Der erste Teil dieses Tutorials verwendet ein kleinskaliges Beispiel mit 10 Gitterplätzen, um den Prozess der Abbildung eines Quantensimulationsproblems auf einen ausführbaren Quantenschaltkreis zu veranschaulichen. Wir untersuchen die Dynamik eines XXZ-Modells mit 10 Gitterplätzen und bauen dabei einen handhabbaren Quantenschaltkreis auf und optimieren ihn, bevor wir zu größeren Systemen skalieren.
Das XXZ-Modell wird in der Physik intensiv zur Untersuchung von Spin-Wechselwirkungen und magnetischen Eigenschaften studiert. Wir richten den Hamiltonian mit offenen Randbedingungen ein, bei denen ortsabhängige Wechselwirkungen zwischen benachbarten Gitterplätzen entlang der Kette vorliegen.
Modell-Hamiltonian und Observable
Der Hamiltonian für unser 10-Gitterplatz-XXZ-Modell ist definiert als:
wobei ein zufälliger Koeffizient für die Kante ist und die Anzahl der Gitterplätze bezeichnet.
Indem wir die Entwicklung dieses Systems mit reduzierter Schaltkreistiefe simulieren, gewinnen wir Einblicke in den Einsatz von AQC-Tensor zur Komprimierung und Optimierung von Schaltkreisen.
Hamiltonian und Observable einrichten
Bevor wir unser Problem abbilden, müssen wir die Kopplungskarte (Coupling Map), den Hamiltonian und die Observable für das 10-Gitterplatz-XXZ-Modell einrichten.
# L is the number of sites, also the length of the 1D spin chain
L = 10
# Generate the coupling map
edge_list = [(i - 1, i) for i in range(1, L)]
# Generate an edge-coloring so we can make hw-efficient circuits
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
)
print("Hamiltonian:", hamiltonian)
print("Observable:", observable)
graphviz_draw(coupling_map.graph, method="circo")
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])
Nachdem der Hamiltonian definiert ist, können wir mit der Konstruktion des Anfangszustands fortfahren.
# Generate an initial state
initial_state = QuantumCircuit(L)
for i in range(L):
if i % 2:
initial_state.x(i)
Schritt 1: Klassische Eingaben auf ein Quantenproblem abbilden
Nun, da wir den Hamiltonian konstruiert haben – der die Spin-Spin-Wechselwirkungen und externen Magnetfelder des Systems beschreibt –, folgen wir drei Hauptschritten im AQC-Tensor-Workflow:
- Den optimierten AQC-Schaltkreis erzeugen: Mithilfe von Trotterisierung approximieren wir die initiale Entwicklung, die anschließend komprimiert wird, um die Schaltkreistiefe zu reduzieren.
- Den verbleibenden Zeitentwicklungsschaltkreis erstellen: Erfasse die Entwicklung für die verbleibende Zeit nach dem initialen Segment.
- Die Schaltkreise kombinieren: Füge den optimierten AQC-Schaltkreis mit dem verbleibenden Entwicklungsschaltkreis zu einem vollständigen Zeitentwicklungsschaltkreis zusammen, der zur Ausführung bereit ist.
Dieser Ansatz erzeugt einen Ansatz mit geringer Tiefe für die Zielentwicklung und unterstützt eine effiziente Simulation innerhalb der Grenzen von Quantenhardware der nahen Zukunft.
Den klassisch zu simulierenden Zeitentwicklungsanteil bestimmen
Unser Ziel ist es, die Zeitentwicklung des zuvor definierten Modell-Hamiltonians mithilfe von Trotter-Entwicklung zu simulieren. Um diesen Prozess für Quantenhardware effizient zu gestalten, teilen wir die Entwicklung in zwei Segmente auf:
-
Initiales Segment: Dieser anfängliche Teil der Entwicklung, von bis , ist mit MPS simulierbar und kann mit AQC-Tensor effizient „kompiliert" werden. Mit dem AQC-Tensor Qiskit-Addon erzeugen wir einen komprimierten Schaltkreis für dieses Segment, den sogenannten
aqc_target_circuit. Da dieses Segment auf einem Tensornetzwerk-Simulator simuliert wird, können wir eine höhere Anzahl von Trotter-Schichten verwenden, ohne die Hardware-Ressourcen wesentlich zu beanspruchen. Für dieses Segment setzen wiraqc_target_num_trotter_steps = 32. -
Folgesegment: Dieser verbleibende Teil der Entwicklung, von bis , wird auf Quantenhardware ausgeführt und als
subsequent_circuitbezeichnet. Aufgrund von Hardware-Beschränkungen streben wir an, so wenige Trotter-Schichten wie möglich zu verwenden, um eine handhabbare Schaltkreistiefe zu erhalten. Für dieses Segment verwenden wirsubsequent_num_trotter_steps = 3.
Den Teilungszeitpunkt wählen
Wir wählen als Teilungszeitpunkt, um klassische Simulierbarkeit mit Hardware-Machbarkeit in Einklang zu bringen. Zu Beginn der Entwicklung bleibt die Verschränkung im XXZ-Modell niedrig genug, damit klassische Methoden wie MPS sie noch genau approximieren können.
Bei der Wahl eines Teilungszeitpunkts empfiehlt es sich, einen Punkt zu wählen, an dem die Verschränkung klassisch noch handhabbar ist, aber bereits genug von der Entwicklung erfasst wurde, um den auf der Hardware auszuführenden Teil zu vereinfachen. Für verschiedene Hamiltonians kann es nötig sein, durch Ausprobieren die beste Balance zu finden.
# Generate the AQC target circuit (initial segment)
aqc_evolution_time = 0.2
aqc_target_num_trotter_steps = 32
aqc_target_circuit = initial_state.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 the subsequent circuit
subsequent_num_trotter_steps = 3
subsequent_evolution_time = 0.2
subsequent_circuit = generate_time_evolution_circuit(
hamiltonian,
synthesis=SuzukiTrotter(reps=subsequent_num_trotter_steps),
time=subsequent_evolution_time,
)
subsequent_circuit.draw("mpl", fold=-1)

Um einen sinnvollen Vergleich zu ermöglichen, erzeugen wir zwei weitere Schaltkreise:
-
AQC-Vergleichsschaltkreis: Dieser Schaltkreis entwickelt sich bis zu
aqc_evolution_time, verwendet jedoch dieselbe Trotter-Schrittdauer wie dersubsequent_circuit. Er dient als Vergleich zumaqc_target_circuitund zeigt die Entwicklung, die wir ohne eine erhöhte Anzahl von Trotter-Schritten beobachten würden. Wir bezeichnen diesen Schaltkreis alsaqc_comparison_circuit. -
Referenzschaltkreis: Dieser Schaltkreis dient als Basislinie für das exakte Ergebnis. Er simuliert die vollständige Entwicklung mithilfe von Tensornetzwerken zur Berechnung des exakten Ergebnisses und liefert eine Referenz zur Bewertung der Wirksamkeit von AQC-Tensor. Wir bezeichnen diesen Schaltkreis als
reference_circuit.
# Generate the AQC comparison circuit
aqc_comparison_num_trotter_steps = int(
subsequent_num_trotter_steps
/ subsequent_evolution_time
* aqc_evolution_time
)
print(
"Number of Trotter steps for comparison:",
aqc_comparison_num_trotter_steps,
)
aqc_comparison_circuit = generate_time_evolution_circuit(
hamiltonian,
synthesis=SuzukiTrotter(reps=aqc_comparison_num_trotter_steps),
time=aqc_evolution_time,
)
Number of Trotter steps for comparison: 3
# Generate the reference circuit
evolution_time = 0.4
reps = 200
reference_circuit = initial_state.copy()
reference_circuit.compose(
generate_time_evolution_circuit(
hamiltonian,
synthesis=SuzukiTrotter(reps=reps),
time=evolution_time,
),
inplace=True,
)
Einen Ansatz und initiale Parameter aus einem Trotter-Schaltkreis mit weniger Schritten erzeugen
Nachdem wir unsere vier Schaltkreise konstruiert haben, fahren wir mit dem AQC-Tensor-Workflow fort. Zuerst konstruieren wir einen „guten" Schaltkreis, der dieselbe Entwicklungszeit wie der Zielschaltkreis hat, aber mit weniger Trotter-Schritten (und damit weniger Schichten).
Anschließend übergeben wir diesen „guten" Schaltkreis an die Funktion generate_ansatz_from_circuit von AQC-Tensor. Diese Funktion analysiert die Zwei-Qubit-Konnektivität des Schaltkreises und gibt zwei Dinge zurück:
- Einen allgemeinen, parametrisierten Ansatz-Schaltkreis mit derselben Zwei-Qubit-Konnektivität wie der Eingabeschaltkreis.
- Parameter, die – in den Ansatz eingesetzt – den Eingabe-(guten) Schaltkreis ergeben.
Diese Parameter werden wir nun iterativ anpassen, um den Ansatz-Schaltkreis so nah wie möglich an den Ziel-MPS heranzubringen.
aqc_ansatz_num_trotter_steps = 1
aqc_good_circuit = initial_state.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
)
aqc_ansatz.draw("mpl", fold=-1)

print(f"AQC Comparison circuit: depth {aqc_comparison_circuit.depth()}")
print(f"Target circuit: depth {aqc_target_circuit.depth()}")
print(
f"Ansatz circuit: depth {aqc_ansatz.depth()}, with {len(aqc_initial_parameters)} parameters"
)
AQC Comparison circuit: depth 36
Target circuit: depth 385
Ansatz circuit: depth 7, with 156 parameters
Einstellungen für die Tensornetzwerk-Simulation wählen
Hier verwenden wir Quimbs Matrix-Produkt-Zustands-Schaltkreissimulator zusammen mit JAX für die Gradientenberechnung.
simulator_settings = QuimbSimulator(
quimb.tensor.CircuitMPS, autodiff_backend="jax"
)
Als nächstes erstellen wir eine MPS-Darstellung des Zielzustands, der mit AQC-Tensor approximiert werden soll. Diese Darstellung ermöglicht eine effiziente Handhabung der Verschränkung und liefert eine kompakte Beschreibung des Quantenzustands für die weitere Optimierung.
aqc_target_mps = tensornetwork_from_circuit(
aqc_target_circuit, simulator_settings
)
print("Target MPS maximum bond dimension:", aqc_target_mps.psi.max_bond())
# Obtains the reference MPS, where we can obtain the exact expectation value by examining the `local_expectation``
reference_mps = tensornetwork_from_circuit(
reference_circuit, simulator_settings
)
reference_expval = reference_mps.local_expectation(
quimb.pauli("Z") & quimb.pauli("Z"), (L // 2 - 1, L // 2)
).real.item()
print("Reference MPS maximum bond dimension:", reference_mps.psi.max_bond())
Target MPS maximum bond dimension: 5
Reference MPS maximum bond dimension: 7
Beachte: Durch die Wahl einer größeren Anzahl von Trotter-Schritten für den Zielzustand haben wir dessen Trotter-Fehler im Vergleich zum initialen Schaltkreis effektiv reduziert. Wir können die Fidelität () zwischen dem vom initialen Schaltkreis vorbereiteten Zustand und dem Zielzustand berechnen, um diesen Unterschied zu quantifizieren.
good_mps = tensornetwork_from_circuit(aqc_good_circuit, simulator_settings)
starting_fidelity = abs(compute_overlap(good_mps, aqc_target_mps)) ** 2
print("Starting fidelity:", starting_fidelity)
Starting fidelity: 0.9982464959067222
Die Parameter des Ansatzes mithilfe von MPS-Berechnungen optimieren
In diesem Schritt optimieren wir die Ansatz-Parameter, indem wir eine einfache Kostenfunktion, MaximizeStateFidelity, mit dem L-BFGS-Optimierer von SciPy minimieren. Wir legen ein Abbruchkriterium für die Fidelität fest, das sicherstellt, dass sie die Fidelität des initialen Schaltkreises ohne AQC-Tensor übertrifft. Sobald dieser Schwellenwert erreicht ist, weist der komprimierte Schaltkreis sowohl einen geringeren Trotter-Fehler als auch eine reduzierte Tiefe im Vergleich zum ursprünglichen Schaltkreis auf. Durch zusätzlichen CPU-Einsatz kann die Optimierung fortgesetzt werden, um die Fidelität weiter zu steigern.
# Setting values for the optimization
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:
# Good enough for now
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,
)
): # 0 => success; 1 => max iterations reached; 99 => early termination via StopIteration
raise RuntimeError(
f"Optimization failed: {result.message} (status={result.status})"
)
print(f"Done after {result.nit} iterations.")
aqc_final_parameters = result.x
2025-04-14 11:46:52.174235 Intermediate result: Fidelity 0.99795851
2025-04-14 11:46:52.218249 Intermediate result: Fidelity 0.99822826
2025-04-14 11:46:52.280924 Intermediate result: Fidelity 0.99829675
2025-04-14 11:46:52.356214 Intermediate result: Fidelity 0.99832474
2025-04-14 11:46:52.411609 Intermediate result: Fidelity 0.99836131
2025-04-14 11:46:52.453747 Intermediate result: Fidelity 0.99839954
2025-04-14 11:46:52.496184 Intermediate result: Fidelity 0.99846517
2025-04-14 11:46:52.542046 Intermediate result: Fidelity 0.99865029
2025-04-14 11:46:52.583679 Intermediate result: Fidelity 0.99872332
2025-04-14 11:46:52.628732 Intermediate result: Fidelity 0.99892359
2025-04-14 11:46:52.690386 Intermediate result: Fidelity 0.99900640
2025-04-14 11:46:52.759398 Intermediate result: Fidelity 0.99907169
2025-04-14 11:46:52.819496 Intermediate result: Fidelity 0.99911423
2025-04-14 11:46:52.884505 Intermediate result: Fidelity 0.99918716
2025-04-14 11:46:52.947919 Intermediate result: Fidelity 0.99921278
2025-04-14 11:46:53.012808 Intermediate result: Fidelity 0.99924853
2025-04-14 11:46:53.083626 Intermediate result: Fidelity 0.99928797
2025-04-14 11:46:53.153235 Intermediate result: Fidelity 0.99933028
2025-04-14 11:46:53.221371 Intermediate result: Fidelity 0.99935757
2025-04-14 11:46:53.286211 Intermediate result: Fidelity 0.99938140
2025-04-14 11:46:53.352391 Intermediate result: Fidelity 0.99940964
2025-04-14 11:46:53.420472 Intermediate result: Fidelity 0.99944051
2025-04-14 11:46:53.486279 Intermediate result: Fidelity 0.99946828
2025-04-14 11:46:53.552338 Intermediate result: Fidelity 0.99948723
2025-04-14 11:46:53.618688 Intermediate result: Fidelity 0.99951011
2025-04-14 11:46:53.690878 Intermediate result: Fidelity 0.99954718
2025-04-14 11:46:53.762725 Intermediate result: Fidelity 0.99956267
2025-04-14 11:46:53.829784 Intermediate result: Fidelity 0.99958949
2025-04-14 11:46:53.897477 Intermediate result: Fidelity 0.99960498
2025-04-14 11:46:53.954633 Intermediate result: Fidelity 0.99961308
2025-04-14 11:46:54.010125 Intermediate result: Fidelity 0.99962894
2025-04-14 11:46:54.064717 Intermediate result: Fidelity 0.99964121
2025-04-14 11:46:54.118892 Intermediate result: Fidelity 0.99964348
2025-04-14 11:46:54.183236 Intermediate result: Fidelity 0.99964860
2025-04-14 11:46:54.245521 Intermediate result: Fidelity 0.99965695
2025-04-14 11:46:54.305792 Intermediate result: Fidelity 0.99966398
2025-04-14 11:46:54.355819 Intermediate result: Fidelity 0.99967816
2025-04-14 11:46:54.409580 Intermediate result: Fidelity 0.99968293
2025-04-14 11:46:54.457979 Intermediate result: Fidelity 0.99968936
2025-04-14 11:46:54.505891 Intermediate result: Fidelity 0.99969223
2025-04-14 11:46:54.551084 Intermediate result: Fidelity 0.99970009
2025-04-14 11:46:54.601817 Intermediate result: Fidelity 0.99970724
2025-04-14 11:46:54.650097 Intermediate result: Fidelity 0.99970987
2025-04-14 11:46:54.714727 Intermediate result: Fidelity 0.99971237
2025-04-14 11:46:54.780052 Intermediate result: Fidelity 0.99971916
2025-04-14 11:46:54.871994 Intermediate result: Fidelity 0.99971940
2025-04-14 11:46:54.958244 Intermediate result: Fidelity 0.99972465
2025-04-14 11:46:55.011057 Intermediate result: Fidelity 0.99972763
2025-04-14 11:46:55.175339 Intermediate result: Fidelity 0.99972894
2025-04-14 11:46:56.688912 Intermediate result: Fidelity 0.99972894
Done after 50 iterations.
parameters = [float(param) for param in aqc_final_parameters]
print("Final parameters:", parameters)
Final parameters: [-7.853983035039254, 1.5707966468427772, 1.5707962768868613, -1.570798010835122, 1.570794480409574, 1.5707972214146968, -1.570796593027083, 1.5707968206822998, -1.5707959018046258, -1.5707991700969144, 1.5707965852600927, 4.712386891737442, -7.853980840717957, 1.5707967508132654, 1.5707943162503217, -1.5707955382023582, 1.5707958007156742, 1.570796096113293, -1.5707928509846847, 1.5707971042943747, -1.570797909276557, -1.5707941020637393, 1.5707980179540793, 4.712389823219363, -1.5707928752386107, 1.5707996426312891, -1.5707975640471001, -1.570794132802984, 1.5707944361599957, 4.712390747060803, 0.1048818190315936, 0.06686710468840577, -0.0668645844756557, -3.1415923537135466, 1.2374931269696063, 6.323169390432535e-07, 3.53229204771738e-08, 2.1091105688681484, 6.283186439944202, 0.12152258846156239, 0.07961752617254866, -0.07961775088604585, -1.6564278051174865e-06, 2.0771163596472384, 3.141592651630471, -6.283185775192653, 1.7691609006726954, 3.1415922910116216, 0.19837572065074083, 0.11114901449078964, -0.11115124544944892, -3.141591983034976, 0.8570788408766729, 4.201601390404146e-07, -3.141593736550978, 0.34652010942396333, 6.283186232785291, 0.13606356527241956, 0.03891676349289617, -0.03891524189533726, -1.5707965732853424, 1.5707968967088564, -0.3086133992238162, 1.5707957152428194, 1.5707968398959653, -0.32062737993080026, 0.11027416939993417, 0.0726167290795046, -0.07262020423334464, -2.3729431959735024e-06, 1.8204437429254703, 9.299060301196612e-07, -3.141592899563451, 2.103269568939461, 3.1415937539734626, 0.11536891854817125, 0.09099022308254198, -0.09098864958606581, -3.1415913307373127, 2.078429034357281, -1.509777998069368e-06, -3.1415922600663255, 1.5189162645358172, -3.1415878461323583, 0.09999070991480716, 0.04352011445148391, -0.04351849541849812, -1.570797642506462, 1.570795238023824, 0.8903442644396505, 1.5707962698006606, 1.5707946765132268, 0.9098791754570567, 0.10448284343424026, 0.07317037684936827, -0.07316718173961152, -3.141592682240966, 2.1665363080039612, -7.450882112394189e-07, -5.771181304929921e-07, 2.615334999517103, -3.1415914971653898, 0.1890887078648001, 0.13578163074571992, -0.13578078143610256, 7.156734195912883e-07, 1.7915385305413096, -5.188866034727312e-07, 1.2827742939197711e-06, 1.2348316581417487, 6.28318357406372, 0.08061187643781703, 0.03820789039271876, -0.03820731868804904, 1.5707964027727628, 1.570798734462218, 4.387336153720882, -1.570795722044763, 1.570798457375325, 4.450361734163248, 0.092360147257953, 0.06047700345049011, -0.06048592856713045, -3.141591214829027, 2.6593289993286047, -2.366937342261038e-07, 8.112162974032695e-08, 1.8907014631413432, 8.355881261853104e-07, 0.23303641819370874, 0.14331998953606456, -0.1433194488304741, -3.141591621822901, 0.7455776479558791, 3.1415914520163586, -3.1415933560496105, 0.7603938554148255, -1.6230983177616282e-06, 0.07186349688535713, 0.03197144517771341, -0.031971177878588546, -4.712389048748508, 1.5707948403165752, 1.2773619319829186, -1.5707990802172127, 1.5707957676951863, 1.289083769394045, 0.13644999397718796, 0.032761460443590046, -0.032762060585195645, -1.5707977610073176, 1.5707964181578042, -3.4826435600366983, -4.712389691708343, 1.570794277502252, 2.799088046133275]
An diesem Punkt müssen wir lediglich die finalen Parameter für den Ansatz-Schaltkreis ermitteln. Wir können dann den optimierten AQC-Schaltkreis mit dem verbleibenden Entwicklungsschaltkreis zusammenführen, um einen vollständigen Zeitentwicklungsschaltkreis für die Ausführung auf Quantenhardware zu erstellen.
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)

Wir müssen auch unseren aqc_comparison_circuit mit dem verbleibenden Entwicklungsschaltkreis zusammenführen. Dieser Schaltkreis wird verwendet, um die Leistung des mit AQC-Tensor optimierten Schaltkreises mit dem ursprünglichen Schaltkreis zu vergleichen.
aqc_comparison_circuit.compose(subsequent_circuit, inplace=True)
aqc_comparison_circuit.draw("mpl", fold=-1)

Schritt 2: Problem für die Ausführung auf Quantenhardware optimieren
Wähle die Hardware aus. Hier verwenden wir ein beliebiges verfügbares IBM Quantum®-Gerät mit mindestens 127 Qubits.
service = QiskitRuntimeService()
backend = service.least_busy(min_num_qubits=127)
print(backend)
Wir transpilieren PUBs (Schaltkreis und Observablen), um sie an die ISA (Instruction Set Architecture) des Backends anzupassen. Durch das Setzen von optimization_level=3 optimiert der Transpiler den Schaltkreis so, dass er auf eine eindimensionale Qubit-Kette passt, was das Rauschen reduziert, das die Schaltkreis-Fidelität beeinträchtigt. Nachdem die Schaltkreise in ein mit dem Backend kompatibles Format umgewandelt wurden, wenden wir eine entsprechende Transformation auf die Observablen an, um sicherzustellen, dass sie mit dem geänderten Qubit-Layout übereinstimmen.
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("Observable info:", isa_observable)
print("Circuit depth:", isa_circuit.depth())
isa_circuit.draw("mpl", fold=-1, idle_wires=False)
Observable info: SparsePauliOp(['IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZ'],
coeffs=[1.+0.j])
Circuit depth: 111

Transpilierung für den Vergleichsschaltkreis durchführen.
isa_comparison_circuit = pass_manager.run(aqc_comparison_circuit)
isa_comparison_observable = observable.apply_layout(
isa_comparison_circuit.layout
)
print("Observable info:", isa_comparison_observable)
print("Circuit depth:", isa_comparison_circuit.depth())
isa_comparison_circuit.draw("mpl", fold=-1, idle_wires=False)
Observable info: SparsePauliOp(['IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZ'],
coeffs=[1.+0.j])
Circuit depth: 158

Schritt 3: Mit Qiskit-Primitives ausführen
In diesem Schritt führen wir den transpilierten Schaltkreis auf Quantenhardware (oder einem simulierten Backend) aus. Mithilfe der Klasse EstimatorV2 aus qiskit_ibm_runtime richten wir einen Estimator ein, der den Schaltkreis ausführt und die angegebene Observable misst. Das Job-Ergebnis liefert den erwarteten Wert für die Observable und gibt uns Aufschluss über die Leistung des Schaltkreises auf der Zielhardware.
estimator = Estimator(backend)
job = estimator.run([(isa_circuit, isa_observable)])
print("Job ID:", job.job_id())
job.result()
Job ID: czyhqdxd8drg008hx0yg
PrimitiveResult([PubResult(data=DataBin(evs=np.ndarray(<shape=(), dtype=float64>), stds=np.ndarray(<shape=(), dtype=float64>), ensemble_standard_error=np.ndarray(<shape=(), dtype=float64>)), metadata={'shots': 4096, 'target_precision': 0.015625, 'circuit_metadata': {}, 'resilience': {}, 'num_randomizations': 32})], metadata={'dynamical_decoupling': {'enable': False, 'sequence_type': 'XX', 'extra_slack_distribution': 'middle', 'scheduling_method': 'alap'}, 'twirling': {'enable_gates': False, 'enable_measure': True, 'num_randomizations': 'auto', 'shots_per_randomization': 'auto', 'interleave_randomizations': True, 'strategy': 'active-accum'}, 'resilience': {'measure_mitigation': True, 'zne_mitigation': False, 'pec_mitigation': False}, 'version': 2})
Ausführung für den Vergleichsschaltkreis durchführen.
job_comparison = estimator.run([(isa_comparison_circuit, isa_observable)])
print("Job Comparison ID:", job.job_id())
job_comparison.result()
Job Comparison ID: czyhqdxd8drg008hx0yg
PrimitiveResult([PubResult(data=DataBin(evs=np.ndarray(<shape=(), dtype=float64>), stds=np.ndarray(<shape=(), dtype=float64>), ensemble_standard_error=np.ndarray(<shape=(), dtype=float64>)), metadata={'shots': 4096, 'target_precision': 0.015625, 'circuit_metadata': {}, 'resilience': {}, 'num_randomizations': 32})], metadata={'dynamical_decoupling': {'enable': False, 'sequence_type': 'XX', 'extra_slack_distribution': 'middle', 'scheduling_method': 'alap'}, 'twirling': {'enable_gates': False, 'enable_measure': True, 'num_randomizations': 'auto', 'shots_per_randomization': 'auto', 'interleave_randomizations': True, 'strategy': 'active-accum'}, 'resilience': {'measure_mitigation': True, 'zne_mitigation': False, 'pec_mitigation': False}, 'version': 2})
Schritt 4: Nachbearbeitung und Rückgabe des Ergebnisses im gewünschten klassischen Format
In diesem Fall ist keine Rekonstruktion notwendig. Wir können das Ergebnis direkt betrachten, indem wir auf den Erwartungswert aus der Ausführungsausgabe zugreifen.
# AQC results
hw_results = job.result()
hw_results_dicts = [pub_result.data.__dict__ for pub_result in hw_results]
hw_expvals = [
pub_result_data["evs"].tolist() for pub_result_data in hw_results_dicts
]
aqc_expval = hw_expvals[0]
# AQC comparison results
hw_comparison_results = job_comparison.result()
hw_comparison_results_dicts = [
pub_result.data.__dict__ for pub_result in hw_comparison_results
]
hw_comparison_expvals = [
pub_result_data["evs"].tolist()
for pub_result_data in hw_comparison_results_dicts
]
aqc_compare_expval = hw_comparison_expvals[0]
print(f"Exact: \t{reference_expval:.4f}")
print(
f"AQC: \t{aqc_expval:.4f}, |∆| = {np.abs(reference_expval- aqc_expval):.4f}"
)
print(
f"AQC Comparison:\t{aqc_compare_expval:.4f}, |∆| = {np.abs(reference_expval- aqc_compare_expval):.4f}"
)
Exact: -0.5252
AQC: -0.4903, |∆| = 0.0349
AQC Comparison: 0.5424, |∆| = 1.0676
Balkendiagramm zum Vergleich der Ergebnisse des AQC-, Vergleichs- und exakten Schaltkreises.
plt.style.use("seaborn-v0_8")
labels = ["AQC Result", "AQC Comparison Result"]
values = [abs(aqc_expval), abs(aqc_compare_expval)]
plt.figure(figsize=(10, 6))
bars = plt.bar(labels, values, color=["tab:blue", "tab:purple"])
plt.axhline(
y=abs(reference_expval), color="red", linestyle="--", label="Exact Result"
)
plt.xlabel("Results")
plt.ylabel("Absolute Expected Value")
plt.title("AQC Result vs AQC Comparison Result (Absolute Values)")
plt.legend()
for bar in bars:
y_val = bar.get_height()
plt.text(
bar.get_x() + bar.get_width() / 2.0,
y_val,
round(y_val, 2),
va="bottom",
)
plt.show()
Teil II: Skalierung auf größere Systeme
Der zweite Teil dieses Tutorials baut auf dem vorherigen Beispiel auf und skaliert auf ein größeres System mit 50 Gitterpunkten. Damit zeigen wir, wie sich komplexere Quantensimulationsprobleme auf ausführbare Quantencircuits abbilden lassen. Wir untersuchen die Dynamik eines XXZ-Modells mit 50 Gitterpunkten und erstellen sowie optimieren einen umfangreichen Quantencircuit, der realistischeren Systemgrößen entspricht.
Der Hamiltonoperator für unser 50-Gitterpunkte-XXZ-Modell ist definiert als:
wobei ein zufälliger Koeffizient für die Kante ist und die Anzahl der Gitterpunkte. Definiere die Kopplungskarte und die Kanten für den Hamiltonoperator.
L = 50 # L = length of our 1D spin chain
# Generate the edge list for this spin-chain
edge_list = [(i - 1, i) for i in range(1, L)]
# Generate an edge-coloring so we can make hw-efficient circuits
even_edges = edge_list[::2]
odd_edges = edge_list[1::2]
# Instantiate a CouplingMap object
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,
)
observable = SparsePauliOp.from_sparse_list(
[("ZZ", (L // 2 - 1, L // 2), 1.0)], num_qubits=L
)
# Generate an initial state
L = hamiltonian.num_qubits
initial_state = QuantumCircuit(L)
for i in range(L):
if i % 2:
initial_state.x(i)
Schritt 1: Klassische Eingaben auf ein Quantenproblem abbilden
Für dieses größere Problem beginnen wir mit der Konstruktion des Hamiltonoperators für das 50-Gitterpunkte-XXZ-Modell, indem wir Spin-Spin-Wechselwirkungen über alle Gitterpunkte hinweg definieren. Danach folgen drei Hauptschritte:
- Den optimierten AQC-Circuit erzeugen: Wir verwenden Trotterisierung, um die anfängliche Zeitentwicklung zu approximieren, und komprimieren dieses Segment anschließend, um die Circuit-Tiefe zu reduzieren.
- Den restlichen Zeitentwicklungs-Circuit erstellen: Wir erfassen die verbleibende Zeitentwicklung jenseits des anfänglichen Segments.
- Die Circuits kombinieren: Wir fügen den optimierten AQC-Circuit mit dem restlichen Zeitentwicklungs-Circuit zusammen, um einen vollständigen, ausführungsbereiten Zeitentwicklungs-Circuit zu erhalten.
Erzeuge den AQC-Zielcircuit (das anfängliche Segment).
aqc_evolution_time = 0.2
aqc_target_num_trotter_steps = 32
aqc_target_circuit = initial_state.copy()
aqc_target_circuit.compose(
generate_time_evolution_circuit(
hamiltonian,
synthesis=SuzukiTrotter(reps=aqc_target_num_trotter_steps),
time=aqc_evolution_time,
),
inplace=True,
)
Erzeuge den nachfolgenden Circuit (das verbleibende Segment).
subsequent_num_trotter_steps = 3
subsequent_evolution_time = 0.2
subsequent_circuit = generate_time_evolution_circuit(
hamiltonian,
synthesis=SuzukiTrotter(reps=subsequent_num_trotter_steps),
time=subsequent_evolution_time,
)
Erzeuge den AQC-Vergleichscircuit (das anfängliche Segment, aber mit derselben Anzahl von Trotter-Schritten wie der nachfolgende Circuit).
# Generate the AQC comparison circuit
aqc_comparison_num_trotter_steps = int(
subsequent_num_trotter_steps
/ subsequent_evolution_time
* aqc_evolution_time
)
print(
"Number of Trotter steps for comparison:",
aqc_comparison_num_trotter_steps,
)
aqc_comparison_circuit = generate_time_evolution_circuit(
hamiltonian,
synthesis=SuzukiTrotter(reps=aqc_comparison_num_trotter_steps),
time=aqc_evolution_time,
)
Number of Trotter steps for comparison: 3
Erzeuge den Referenzcircuit.
evolution_time = 0.4
reps = 200
reference_circuit = initial_state.copy()
reference_circuit.compose(
generate_time_evolution_circuit(
hamiltonian,
synthesis=SuzukiTrotter(reps=reps),
time=evolution_time,
),
inplace=True,
)
Erzeuge einen Ansatz und initiale Parameter aus einem Trotter-Circuit mit weniger Schritten.
aqc_ansatz_num_trotter_steps = 1
aqc_good_circuit = initial_state.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
)
print(f"AQC Comparison circuit: depth {aqc_comparison_circuit.depth()}")
print(f"Target circuit: depth {aqc_target_circuit.depth()}")
print(
f"Ansatz circuit: depth {aqc_ansatz.depth()}, with {len(aqc_initial_parameters)} parameters"
)
AQC Comparison circuit: depth 36
Target circuit: depth 385
Ansatz circuit: depth 7, with 816 parameters
Konfiguriere die Einstellungen für die Tensor-Netzwerk-Simulation und erstelle dann eine Matrix-Produkt-Zustands-Darstellung (MPS) des Zielzustands für die Optimierung. Berechne anschließend die Fidelity zwischen dem anfänglichen Circuit und dem Zielzustand, um den Unterschied im Trotter-Fehler zu quantifizieren.
simulator_settings = QuimbSimulator(
quimb.tensor.CircuitMPS, autodiff_backend="jax"
)
# Build the matrix-product representation of the state to be approximated by AQC
aqc_target_mps = tensornetwork_from_circuit(
aqc_target_circuit, simulator_settings
)
print("Target MPS maximum bond dimension:", aqc_target_mps.psi.max_bond())
# Obtains the reference MPS, where we can obtain the exact expectation value by examining the `local_expectation``
reference_mps = tensornetwork_from_circuit(
reference_circuit, simulator_settings
)
reference_expval = reference_mps.local_expectation(
quimb.pauli("Z") & quimb.pauli("Z"), (L // 2 - 1, L // 2)
).real.item()
# Compute the starting fidelity
good_mps = tensornetwork_from_circuit(aqc_good_circuit, simulator_settings)
starting_fidelity = abs(compute_overlap(good_mps, aqc_target_mps)) ** 2
print("Starting fidelity:", starting_fidelity)
Target MPS maximum bond dimension: 5
Starting fidelity: 0.9926466919924161
Um die Ansatz-Parameter zu optimieren, minimieren wir die Kostenfunktion MaximizeStateFidelity mit dem L-BFGS-Optimierer von SciPy. Als Abbruchkriterium wird festgelegt, dass die Fidelity des anfänglichen Circuits ohne AQC-Tensor übertroffen werden muss. So ist sichergestellt, dass der komprimierte Circuit sowohl einen geringeren Trotter-Fehler als auch eine reduzierte Tiefe aufweist.
# Setting values for the optimization
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:
# Good enough for now
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,
)
): # 0 => success; 1 => max iterations reached; 99 => early termination via StopIteration
raise RuntimeError(
f"Optimization failed: {result.message} (status={result.status})"
)
print(f"Done after {result.nit} iterations.")
aqc_final_parameters = result.x
2025-04-14 11:48:28.705807 Intermediate result: Fidelity 0.99795851
2025-04-14 11:48:28.743265 Intermediate result: Fidelity 0.99822826
2025-04-14 11:48:28.776629 Intermediate result: Fidelity 0.99829675
2025-04-14 11:48:28.816153 Intermediate result: Fidelity 0.99832474
2025-04-14 11:48:28.856437 Intermediate result: Fidelity 0.99836131
2025-04-14 11:48:28.896432 Intermediate result: Fidelity 0.99839954
2025-04-14 11:48:28.936670 Intermediate result: Fidelity 0.99846517
2025-04-14 11:48:28.982069 Intermediate result: Fidelity 0.99865029
2025-04-14 11:48:29.026130 Intermediate result: Fidelity 0.99872332
2025-04-14 11:48:29.067426 Intermediate result: Fidelity 0.99892359
2025-04-14 11:48:29.110742 Intermediate result: Fidelity 0.99900640
2025-04-14 11:48:29.161362 Intermediate result: Fidelity 0.99907169
2025-04-14 11:48:29.207933 Intermediate result: Fidelity 0.99911423
2025-04-14 11:48:29.266772 Intermediate result: Fidelity 0.99918716
2025-04-14 11:48:29.331727 Intermediate result: Fidelity 0.99921278
2025-04-14 11:48:29.401694 Intermediate result: Fidelity 0.99924853
2025-04-14 11:48:29.467980 Intermediate result: Fidelity 0.99928797
2025-04-14 11:48:29.533281 Intermediate result: Fidelity 0.99933028
2025-04-14 11:48:29.600833 Intermediate result: Fidelity 0.99935757
2025-04-14 11:48:29.670816 Intermediate result: Fidelity 0.99938140
2025-04-14 11:48:29.736928 Intermediate result: Fidelity 0.99940964
2025-04-14 11:48:29.802931 Intermediate result: Fidelity 0.99944051
2025-04-14 11:48:29.869177 Intermediate result: Fidelity 0.99946828
2025-04-14 11:48:29.940156 Intermediate result: Fidelity 0.99948723
2025-04-14 11:48:30.005751 Intermediate result: Fidelity 0.99951011
2025-04-14 11:48:30.070853 Intermediate result: Fidelity 0.99954718
2025-04-14 11:48:30.139171 Intermediate result: Fidelity 0.99956267
2025-04-14 11:48:30.210506 Intermediate result: Fidelity 0.99958949
2025-04-14 11:48:30.279647 Intermediate result: Fidelity 0.99960498
2025-04-14 11:48:30.348016 Intermediate result: Fidelity 0.99961308
2025-04-14 11:48:30.414311 Intermediate result: Fidelity 0.99962894
2025-04-14 11:48:30.488910 Intermediate result: Fidelity 0.99964121
2025-04-14 11:48:30.561298 Intermediate result: Fidelity 0.99964348
2025-04-14 11:48:30.632214 Intermediate result: Fidelity 0.99964860
2025-04-14 11:48:30.705703 Intermediate result: Fidelity 0.99965695
2025-04-14 11:48:30.775679 Intermediate result: Fidelity 0.99966398
2025-04-14 11:48:30.842629 Intermediate result: Fidelity 0.99967816
2025-04-14 11:48:30.912357 Intermediate result: Fidelity 0.99968293
2025-04-14 11:48:30.979420 Intermediate result: Fidelity 0.99968936
2025-04-14 11:48:31.049196 Intermediate result: Fidelity 0.99969223
2025-04-14 11:48:31.125391 Intermediate result: Fidelity 0.99970009
2025-04-14 11:48:31.201256 Intermediate result: Fidelity 0.99970724
2025-04-14 11:48:31.272424 Intermediate result: Fidelity 0.99970987
2025-04-14 11:48:31.338907 Intermediate result: Fidelity 0.99971237
2025-04-14 11:48:31.404800 Intermediate result: Fidelity 0.99971916
2025-04-14 11:48:31.475226 Intermediate result: Fidelity 0.99971940
2025-04-14 11:48:31.547746 Intermediate result: Fidelity 0.99972465
2025-04-14 11:48:31.622827 Intermediate result: Fidelity 0.99972763
2025-04-14 11:48:31.819516 Intermediate result: Fidelity 0.99972894
2025-04-14 11:48:33.444538 Intermediate result: Fidelity 0.99972894
Done after 50 iterations.
parameters = [float(param) for param in aqc_final_parameters]
Erstelle den finalen Circuit für die Transpilierung, indem du den optimierten Ansatz mit dem restlichen Zeitentwicklungs-Circuit zusammensetzt.
aqc_final_circuit = aqc_ansatz.assign_parameters(aqc_final_parameters)
aqc_final_circuit.compose(subsequent_circuit, inplace=True)
aqc_comparison_circuit.compose(subsequent_circuit, inplace=True)
Schritt 2: Problem für die Ausführung auf Quantenhardware optimieren
Wähle das Backend aus.
service = QiskitRuntimeService()
backend = service.least_busy(min_num_qubits=127)
print(backend)
Transpiliere den fertigen Circuit für die Zielhardware und bereite ihn für die Ausführung vor. Der resultierende ISA-Circuit kann anschließend zur Ausführung auf dem Backend übermittelt werden.
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("Observable info:", isa_observable)
print("Circuit depth:", isa_circuit.depth())
isa_circuit.draw("mpl", fold=-1, idle_wires=False)
Observable info: SparsePauliOp(['IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII'],
coeffs=[1.+0.j])
Circuit depth: 122

isa_comparison_circuit = pass_manager.run(aqc_comparison_circuit)
isa_comparison_observable = observable.apply_layout(
isa_comparison_circuit.layout
)
print("Observable info:", isa_comparison_observable)
print("Circuit depth:", isa_comparison_circuit.depth())
isa_comparison_circuit.draw("mpl", fold=-1, idle_wires=False)
Observable info: SparsePauliOp(['IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII'],
coeffs=[1.+0.j])
Circuit depth: 158

Schritt 3: Ausführung mit Qiskit-Primitiven
In diesem Schritt führen wir den transpilierten Circuit auf Quantenhardware (oder einem simulierten Backend) aus. Dazu verwenden wir EstimatorV2 aus qiskit_ibm_runtime, um das angegebene Observable zu messen. Das Job-Ergebnis liefert wertvolle Einblicke in die Leistung des Circuits auf der Zielhardware.
Für dieses größere Beispiel zeigen wir, wie du EstimatorOptions nutzen kannst, um die Parameter deines Hardware-Experiments besser zu verwalten und zu steuern. Diese Einstellungen sind optional, helfen aber dabei, Experimentparameter nachzuverfolgen und die Ausführungsoptionen für optimale Ergebnisse zu verfeinern.
Eine vollständige Liste der verfügbaren Ausführungsoptionen findest du in der qiskit-ibm-runtime-Dokumentation.
twirling_options = {
"enable_gates": True,
"enable_measure": True,
"num_randomizations": 300,
"shots_per_randomization": 100,
"strategy": "active",
}
zne_options = {
"amplifier": "gate_folding",
"noise_factors": [1, 2, 3],
"extrapolated_noise_factors": list(np.linspace(0, 3, 31)),
"extrapolator": ["exponential", "linear", "fallback"],
}
meas_learning_options = {
"num_randomizations": 512,
"shots_per_randomization": 512,
}
resilience_options = {
"measure_mitigation": True,
"zne_mitigation": True,
"zne": zne_options,
"measure_noise_learning": meas_learning_options,
}
estimator_options = {
"resilience": resilience_options,
"twirling": twirling_options,
}
estimator = Estimator(backend, options=estimator_options)
job = estimator.run([(isa_circuit, isa_observable)])
print("Job ID:", job.job_id())
job.result()
Job ID: czyjx6crxz8g008f63r0
PrimitiveResult([PubResult(data=DataBin(evs=np.ndarray(<shape=(), dtype=float64>), stds=np.ndarray(<shape=(), dtype=float64>), evs_noise_factors=np.ndarray(<shape=(3,), dtype=float64>), stds_noise_factors=np.ndarray(<shape=(3,), dtype=float64>), ensemble_stds_noise_factors=np.ndarray(<shape=(3,), dtype=float64>), evs_extrapolated=np.ndarray(<shape=(3, 31), dtype=float64>), stds_extrapolated=np.ndarray(<shape=(3, 31), dtype=float64>)), metadata={'shots': 30000, 'target_precision': 0.005773502691896258, 'circuit_metadata': {}, 'resilience': {'zne': {'extrapolator': 'exponential'}}, 'num_randomizations': 300})], metadata={'dynamical_decoupling': {'enable': False, 'sequence_type': 'XX', 'extra_slack_distribution': 'middle', 'scheduling_method': 'alap'}, 'twirling': {'enable_gates': True, 'enable_measure': True, 'num_randomizations': 300, 'shots_per_randomization': 100, 'interleave_randomizations': True, 'strategy': 'active'}, 'resilience': {'measure_mitigation': True, 'zne_mitigation': True, 'pec_mitigation': False, 'zne': {'noise_factors': [1, 2, 3], 'extrapolator': ['exponential', 'linear', 'fallback'], 'extrapolated_noise_factors': [0, 0.1, 0.2, 0.30000000000000004, 0.4, 0.5, 0.6000000000000001, 0.7000000000000001, 0.8, 0.9, 1, 1.1, 1.2000000000000002, 1.3, 1.4000000000000001, 1.5, 1.6, 1.7000000000000002, 1.8, 1.9000000000000001, 2, 2.1, 2.2, 2.3000000000000003, 2.4000000000000004, 2.5, 2.6, 2.7, 2.8000000000000003, 2.9000000000000004, 3]}}, 'version': 2})
job_comparison = estimator.run([(isa_comparison_circuit, isa_observable)])
print("Job Comparison ID:", job.job_id())
job_comparison.result()
Job Comparison ID: czyjx6crxz8g008f63r0
PrimitiveResult([PubResult(data=DataBin(evs=np.ndarray(<shape=(), dtype=float64>), stds=np.ndarray(<shape=(), dtype=float64>), evs_noise_factors=np.ndarray(<shape=(3,), dtype=float64>), stds_noise_factors=np.ndarray(<shape=(3,), dtype=float64>), ensemble_stds_noise_factors=np.ndarray(<shape=(3,), dtype=float64>), evs_extrapolated=np.ndarray(<shape=(3, 31), dtype=float64>), stds_extrapolated=np.ndarray(<shape=(3, 31), dtype=float64>)), metadata={'shots': 30000, 'target_precision': 0.005773502691896258, 'circuit_metadata': {}, 'resilience': {'zne': {'extrapolator': 'exponential'}}, 'num_randomizations': 300})], metadata={'dynamical_decoupling': {'enable': False, 'sequence_type': 'XX', 'extra_slack_distribution': 'middle', 'scheduling_method': 'alap'}, 'twirling': {'enable_gates': True, 'enable_measure': True, 'num_randomizations': 300, 'shots_per_randomization': 100, 'interleave_randomizations': True, 'strategy': 'active'}, 'resilience': {'measure_mitigation': True, 'zne_mitigation': True, 'pec_mitigation': False, 'zne': {'noise_factors': [1, 2, 3], 'extrapolator': ['exponential', 'linear', 'fallback'], 'extrapolated_noise_factors': [0, 0.1, 0.2, 0.30000000000000004, 0.4, 0.5, 0.6000000000000001, 0.7000000000000001, 0.8, 0.9, 1, 1.1, 1.2000000000000002, 1.3, 1.4000000000000001, 1.5, 1.6, 1.7000000000000002, 1.8, 1.9000000000000001, 2, 2.1, 2.2, 2.3000000000000003, 2.4000000000000004, 2.5, 2.6, 2.7, 2.8000000000000003, 2.9000000000000004, 3]}}, 'version': 2})
Schritt 4: Nachbearbeitung und Ausgabe im gewünschten klassischen Format
Hier ist — wie zuvor — keine Rekonstruktion erforderlich; wir können den Erwartungswert direkt aus der Ausführungsausgabe auslesen, um das Ergebnis zu untersuchen.
# AQC results
hw_results = job.result()
hw_results_dicts = [pub_result.data.__dict__ for pub_result in hw_results]
hw_expvals = [
pub_result_data["evs"].tolist() for pub_result_data in hw_results_dicts
]
aqc_expval = hw_expvals[0]
# AQC comparison results
hw_comparison_results = job_comparison.result()
hw_comparison_results_dicts = [
pub_result.data.__dict__ for pub_result in hw_comparison_results
]
hw_comparison_expvals = [
pub_result_data["evs"].tolist()
for pub_result_data in hw_comparison_results_dicts
]
aqc_compare_expval = hw_comparison_expvals[0]
print(f"Exact: \t{reference_expval:.4f}")
print(
f"AQC: \t{aqc_expval:.4f}, |∆| = {np.abs(reference_expval- aqc_expval):.4f}"
)
print(
f"AQC Comparison:\t{aqc_compare_expval:.4f}, |∆| = {np.abs(reference_expval- aqc_compare_expval):.4f}"
)
Exact: -0.5888
AQC: -0.4809, |∆| = 0.1078
AQC Comparison: 1.1764, |∆| = 1.7652
Stelle die Ergebnisse des AQC-, Vergleichs- und Exaktcircuits für das 50-Gitterpunkte-XXZ-Modell grafisch dar.
labels = ["AQC Result", "AQC Comparison Result"]
values = [abs(aqc_expval), abs(aqc_compare_expval)]
plt.figure(figsize=(10, 6))
bars = plt.bar(labels, values, color=["tab:blue", "tab:purple"])
plt.axhline(
y=abs(reference_expval), color="red", linestyle="--", label="Exact Result"
)
plt.xlabel("Results")
plt.ylabel("Absolute Expected Value")
plt.title("AQC Result vs AQC Comparison Result (Absolute Values)")
plt.legend()
for bar in bars:
y_val = bar.get_height()
plt.text(
bar.get_x() + bar.get_width() / 2.0,
y_val,
round(y_val, 2),
va="bottom",
)
plt.show()
Fazit
Dieses Tutorial hat gezeigt, wie man Approximate Quantum Compilation mit Tensor-Netzwerken (AQC-Tensor) einsetzt, um Circuits für die skalierte Simulation von Quantendynamik zu komprimieren und zu optimieren. Anhand kleiner und großer Heisenberg-Modelle haben wir AQC-Tensor genutzt, um die Circuit-Tiefe der trotterisierten Zeitentwicklung zu reduzieren. Durch die Erzeugung eines parametrisierten Ansatzes aus einem vereinfachten Trotter-Circuit und dessen Optimierung mit Matrix-Produkt-Zustands-Techniken (MPS) haben wir eine tiefenreduzierte Näherung der Zielentwicklung erreicht, die sowohl genau als auch effizient ist.
Der hier vorgestellte Arbeitsablauf verdeutlicht die zentralen Vorteile von AQC-Tensor für die Skalierung von Quantensimulationen:
- Deutliche Circuit-Komprimierung: AQC-Tensor hat die erforderliche Circuit-Tiefe für komplexe Zeitentwicklungen reduziert und damit die Ausführbarkeit auf aktuellen Geräten verbessert.
- Effiziente Optimierung: Der MPS-Ansatz lieferte einen robusten Rahmen für die Parameteroptimierung und balancierte Fidelity mit rechnerischer Effizienz.
- Hardwarebereite Ausführung: Die Transpilierung des final optimierten Circuits stellte sicher, dass er die Einschränkungen der Ziel-Quantenhardware erfüllt.
Mit zunehmend größeren Quantengeräten und leistungsfähigeren Algorithmen werden Techniken wie AQC-Tensor unverzichtbar, um komplexe Quantensimulationen auf Near-Term-Hardware auszuführen — ein vielversprechender Fortschritt in der Handhabung von Circuit-Tiefe und Fidelity für skalierbare Quantenanwendungen.
Tutorial-Umfrage
Nimm dir kurz Zeit für diese Umfrage, um Feedback zu diesem Tutorial zu geben. Deine Einschätzung hilft uns, unsere Inhalte und die Nutzererfahrung weiter zu verbessern.