Pauli-Terme während der Rückpropagation abschneiden
In dieser Anleitung wird erläutert, wie der Mechanismus zur Trunkierung von Pauli-Termen konfiguriert wird, der vom Modul „ qiskit_addon_obp.utils.truncating “ bereitgestellt wird.
Die Operator-Backpropagation (OBP) kann genutzt werden, um die Tiefe von Quantenschaltungen zu verringern, was jedoch auf Kosten einer komplexeren Observablen geht. Um aussagekräftige Ergebnisse mit OBP zu erhalten, muss man in der Regel Terme aus der Beobachtungsvariablen kürzen, um zu verhindern, dass diese zu groß wird. Eine Möglichkeit, eine tiefere Rückpropagierung in die Schaltung zu ermöglichen und gleichzeitig zu verhindern, dass der Operator zu groß wird, besteht darin, Terme mit kleinen Koeffizienten zu kürzen, anstatt sie zum Operator hinzuzufügen. Das Kürzen von Termen kann zwar dazu führen, dass weniger Quantenschaltungen ausgeführt werden müssen, führt jedoch zu einem gewissen Fehler bei der Berechnung des endgültigen Erwartungswerts, der proportional zur Größe der Koeffizienten der gekürzten Terme ist.
Die Backpropagation-Methode akzeptiert ein optionales „TruncationErrorBudget“, das die Kürzung von Pauli-Termen mit geringem Gewicht für jede Observable nach der erfolgreichen Backpropagation jedes Slice konfiguriert. Die Anzahl der gekürzten Terme hängt von verschiedenen, vom Benutzer festgelegten Konfigurationsparametern ab. Derzeit steht nur eine einzige Trunkierungsstrategie zur Verfügung – die Methode „truncate_binary_search “. Ausgehend von einem Observablen und einem bestimmten Budget führt das Verfahren eine binäre Suche über die Pauli-Terme und Koeffizienten innerhalb dieses Observablen durch, um den optimalen Schwellenwert zu finden, sodass die Summe der abgeschnittenen Koeffizienten maximal ist, aber unter dem Budget liegt.
Hinweis : Standardmäßig wird zur Bewertung und Begrenzung des Abschneidefehlers die L1 -Norm verwendet; mit der Einstellung p_norm kann jedoch die zu verwendende Lp-Norm festgelegt werden.
Weitere Informationen zur Verwendung dieser Einstellung finden Sie im Leitfaden „Verwendung unterschiedlicher Lp-Normen für die Trunkierung des Pauli-Terms “.
Die folgenden Beispiele veranschaulichen verschiedene Möglichkeiten, ein „ TruncationErrorBudget “ mithilfe der beiliegenden Funktion „setup_budget“ zu erstellen.
Bauen Sie eine Beispielschaltung auf
In dieser Anleitung werden die folgenden Schaltungsabschnitte verwendet:
import rustworkx.generators
from qiskit.synthesis import LieTrotter
from qiskit_addon_utils.problem_generators import (
PauliOrderStrategy,
generate_time_evolution_circuit,
generate_xyz_hamiltonian,
)
from qiskit_addon_utils.slicing import combine_slices, slice_by_gate_types
# Generate a linear chain of 10 qubits
linear_chain = rustworkx.generators.path_graph(10)
# Use an arbitrary XY model
hamiltonian = generate_xyz_hamiltonian(
linear_chain,
coupling_constants=(0.05, 0.02, 0.0),
ext_magnetic_field=(0.02, 0.08, 0.0),
pauli_order_strategy=PauliOrderStrategy.InteractionThenColor,
)
# Evolve for some time
circuit = generate_time_evolution_circuit(
hamiltonian, synthesis=LieTrotter(reps=3), time=2.0
)
# slice the circuit by gate type
slices = slice_by_gate_types(circuit)
# for visualization purposes, recombine the slices with barriers between them and draw the resulting circuit
combine_slices(slices, include_barriers=True).draw("mpl", fold=50, scale=0.6)Output:
Wir werden uns eine einzige einfache Observable ansehen:
from qiskit.quantum_info import SparsePauliOp
obs = SparsePauliOp("IIIIIZIIII")Der einfachste Fall: ein festes Trunkierungsbudget für jede Scheibe
Das für die Trunkierung der Pauli-Terme zur Verfügung stehende Budget kann in jedem Schritt der Rückwärtsausbreitung variieren. Um zu verstehen, wie dies funktioniert, betrachten wir zunächst den einfachsten Fall eines vom Benutzer festgelegten Trunkierungsbudgets.
Die einfachste Möglichkeit, das Trunkierungsbudget festzulegen, ist die Verwendung des Arguments max_error_per_slice .
Genau das wird übrigens im Tutorial „Reduzierung der Schaltungstiefe mit Operator-Backpropagation “ behandelt. Wird float der Wert auf max_error_per_slice gesetzt, wird jedem Segment ein Budget in Höhe dieses Werts zugewiesen.
Im folgenden Beispiel setzen wir diesen Wert auf, was 0.001 einen impliziten Rundungsfehler von höchstens garantiert 0.018, wenn alle 18 Schichten rückwärtspropagiert würden.
Beachten Sie, dass ein nach der Rückpropagierung eines Schnitts und dem Weglassen von Termen mit kleinen Koeffizienten verbleibendes Fehlerbudget stets dem Fehlerbudget des folgenden Schnitts hinzugefügt wird.
from qiskit_addon_obp.utils.truncating import setup_budget
truncation_error_budget = setup_budget(max_error_per_slice=0.001)
print(truncation_error_budget)Output:
TruncationErrorBudget(per_slice_budget=[0.001], max_error_total=inf, p_norm=1)
from qiskit_addon_obp import backpropagate
from qiskit_addon_obp.utils.simplify import OperatorBudget
op_budget = OperatorBudget(max_qwc_groups=10)
bp_obs, remaining_slices, metadata = backpropagate(
obs,
slices,
operator_budget=op_budget,
truncation_error_budget=truncation_error_budget,
)
reduced_circuit = combine_slices(remaining_slices)
print(f"Backpropagated {len(slices) - len(remaining_slices)} circuit slices.")
print(
f"New observable contains {len(bp_obs)} terms and {len(bp_obs.group_commuting(qubit_wise=True))} commuting groups."
)Output:
Backpropagated 11 circuit slices.
New observable contains 29 terms and 10 commuting groups.
Anschließend nutzen wir die OBPMetadata- Instanz und die vom Visualisierungsmodul bereitgestellten Werkzeuge, um den Backpropagation-Prozess zu visualisieren.
- Die Grafik oben links zeigt, dass wir über genügend Rechenkapazität verfügen, um nach der Rückpropagierung des dritten Abschnitts mit der Kürzung der beobachtbaren Terme zu beginnen. Ab dem dritten Abschnitt wissen wir, dass wir bei jedem Abschnitt, den wir rückwärtsverbreiten, mindestens einen Term abschneiden, da nach jedem Abschnitt ein gewisser Abschneidefehler auftritt.
- Die Grafik oben rechts zeigt, dass das Fehlerbudget für
.003den dritten Ausschnitt auf anstieg. Wir stellen einen starken Rückgang des verbleibenden Budgets fest, was bedeutet, dass bestimmte Zeiträume aus den beobachtbaren Daten herausgeschnitten wurden. Dies stimmt mit dem überein, was wir aus der Grafik oben links abgeleitet haben. - Die Grafik unten links zeigt, dass unser insgesamt kumulierter Fehler monoton ansteigt, wenn wir Terme aus unserer Observablen weglassen. Diese Grafik bestätigt zudem, dass erst nach der Rückpropagierung des dritten Segments Terme abgeschnitten wurden.
- Die Grafik unten rechts zeigt, dass die Anzahl der pendelnden Pauli-Gruppen in unserer Observable auf den angegebenen Grenzwert von
10… angestiegen ist. Diese Grafik zeigt zudem, wie die Rückpropagierung einer weiteren Schicht dazu führen würde, dass unsere Beobachtungsgröße die festgelegte Grenze überschreitet, wie aus dem Schnittpunkt der schwarzen und roten Linien ersichtlich ist.
Beachten Sie, dass in allen diesen Diagrammen die x-Achse die rückwärtspropagierten Slices auflistet; da OBP jedoch am Ende der Schaltung arbeitet, slice 2 ist slice 1 das allerletzte Slice, das davorliegende und so weiter.
from matplotlib import pyplot as plt
from qiskit_addon_obp.utils.visualization import (
plot_accumulated_error,
plot_left_over_error_budget,
plot_num_qwc_groups,
plot_slice_errors,
)
fig, axes = plt.subplots(2, 2, figsize=(20, 10))
plot_slice_errors(metadata, axes[(0, 0)])
plot_left_over_error_budget(metadata, axes[(0, 1)])
plot_accumulated_error(metadata, axes[(1, 0)])
plot_num_qwc_groups(metadata, axes[(1, 1)])Output:
Das Slice-Budget explizit festlegen
Wenn Sie wissen, wie Sie jedem Slice ein Budget zuweisen können, sodass die Backpropagation-Leistung optimiert wird, sollten Sie jedem Slice explizit ein Budget zuweisen. Zu Demonstrationszwecken weisen wir den ersten drei Schnitten ein Budget von Null zu und verwenden für die übrigen Schnitte ein Budget von pro .001 Schnitt.
# Zero out the first 3 slices' budgets
max_error_per_slice = [0.0] * 3 + [0.001] * (len(slices) - 3)
truncation_error_budget = setup_budget(
max_error_per_slice=max_error_per_slice
)
print(truncation_error_budget)Output:
TruncationErrorBudget(per_slice_budget=[0.0, 0.0, 0.0, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001], max_error_total=inf, p_norm=1)
bp_obs, remaining_slices, metadata = backpropagate(
obs,
slices,
operator_budget=op_budget,
truncation_error_budget=truncation_error_budget,
)
reduced_circuit = combine_slices(remaining_slices)
print(f"Backpropagated {len(slices) - len(remaining_slices)} circuit slices.")
print(
f"New observable contains {len(bp_obs)} terms and {len(bp_obs.group_commuting(qubit_wise=True))} commuting groups."
)Output:
Backpropagated 11 circuit slices.
New observable contains 32 terms and 10 commuting groups.
Durch das Entfernen des Budgets aus den ersten drei Schichten wurden erst ab der vierten Schicht keine Terme mehr abgeschnitten, wie aus drei dieser Diagramme hervorgeht. Etwas überraschend mag sein, dass, obwohl dem vierten Segment kein Restbudget zugewiesen wurde, dennoch ein Term durch die Verwendung des zugewiesenen Budgets .001 gekürzt wurde. Dies geht aus der Grafik oben links hervor, lässt sich aber auch in der Grafik oben rechts beobachten, da sich die Kurve des verbleibenden Budgets zwischen den Schnitten drei und vier abflacht. Ein weiteres bemerkenswertes Detail ist, dass ab diesem Punkt mindestens ein Term abgeschnitten wurde, genau wie im vorherigen Beispiel.
Die wichtigste Erkenntnis ist, dass wir im zweiten Beispiel zwar aufgrund der Nullsetzung der Budgets einiger Slices einen geringeren Trunkierungsfehler hatten, aber dennoch die gleiche Anzahl von Slices rückwärts propagieren konnten und unser Observabil enthält die gleiche Anzahl von kommutierenden Pauli-Gruppen. Anhand der Grafik unten links können wir bestätigen, dass die Fehlergrenze im zweiten Beispiel kleiner ist.
fig, axes = plt.subplots(2, 2, figsize=(20, 10))
plot_slice_errors(metadata, axes[(0, 0)])
plot_left_over_error_budget(metadata, axes[(0, 1)])
plot_accumulated_error(metadata, axes[(1, 0)])
plot_num_qwc_groups(metadata, axes[(1, 1)])Output:
Das Budget zyklisch festlegen
Wenn Sie eine Schaltung mit einem sich wiederholenden Muster haben, wie beispielsweise eine Trotter-Schaltung, möchten Sie möglicherweise ein Budget für diese sich wiederholende Teilmenge von Slices festlegen und dieses Budget für alle nachfolgenden Wiederholungen dieser Slices verwenden lassen.
Genauer gesagt besteht die von uns verwendete Beispielschaltung aus sechs Segmenten, die dreimal wiederholt werden, was insgesamt 18 Segmente ergibt. Wir weisen den Einzel-Qubit- und den Schichten RYY willkürlich ein Budget von Null zu und jeder der Schichten RXX ein Budget .003 von zu. Wir werden beobachten, wie die Festlegung des Budgets als „ length-6 “-Sequenz dazu führt, dass das Budget zyklisch auf alle 18 Slices angewendet wird.
Beachten Sie auch hier, dass die Schichten in umgekehrter Reihenfolge (d. h. beginnend am Ende) rückwärts propagiert werden. Daher wird der erste Eintrag in unserem zyklischen Budget tatsächlich für das letzte Segment verwendet, der zweite Eintrag für das Segment davor und so weiter.
# Specify a length-6 per-slice budget.
# This will be cycled over three times to be applied to the 18 slices
max_error_per_slice = [0.0] * 4 + [0.003] * 2
truncation_error_budget = setup_budget(
max_error_per_slice=max_error_per_slice
)
print(truncation_error_budget)Output:
TruncationErrorBudget(per_slice_budget=[0.0, 0.0, 0.0, 0.0, 0.003, 0.003], max_error_total=inf, p_norm=1)
op_budget = OperatorBudget(max_qwc_groups=20)
bp_obs, remaining_slices, metadata = backpropagate(
obs,
slices,
operator_budget=op_budget,
truncation_error_budget=truncation_error_budget,
)
reduced_circuit = combine_slices(remaining_slices)
print(f"Backpropagated {len(slices) - len(remaining_slices)} circuit slices.")
print(
f"New observable contains {len(bp_obs)} terms and {len(bp_obs.group_commuting(qubit_wise=True))} commuting groups."
)Output:
Backpropagated 13 circuit slices.
New observable contains 49 terms and 14 commuting groups.
Wie auf den Abbildungen oben links und unten links zu sehen ist, wurde bei den ersten vier Schichten keine Kürzung vorgenommen, da ihnen kein Fehlerbudget zugewiesen wurde. Bei den Tranchen fünf und sechs wurden einige der Laufzeiten gekürzt, sobald Mittel zur Verfügung standen.
Wie auf dem Bild oben links zu sehen ist, entstand nach der Rückpropagierung von Schnitt sieben ein relativ großer Fehler, obwohl für diesen Schnitt kein Budget zugewiesen worden war. Das liegt daran, dass nur etwa .002 des für die Segmente fünf und sechs vorgesehenen .006 Gesamtbudgets verwendet wurde; der Restbetrag wurde daher auf Segment sieben übertragen und größtenteils ausgegeben, wie in der Abbildung oben rechts zu sehen ist.
Die Abbildungen oben links und oben rechts zeigen, dass der geringe Restbetrag des Budgets zwischen den Abschnitten 8 und 10 aufgebraucht wird und in Abschnitt 11, wie erwartet, neues Budget zur Verfügung steht. Die Abbildungen oben links, oben rechts und unten links veranschaulichen alle das zyklische Verhalten des Arguments max_error_per_slice , wenn dessen Länge kleiner ist als die Anzahl der Segmente. Dieses zyklische Verhalten hätte sich über alle Schichten hinweg fortgesetzt, doch das max_qwc_groups Abbruchkriterium wurde nach der Rückpropagierung von 13 Schichten erreicht, wie auf dem Bild unten rechts zu sehen ist.
Interessant ist auch, dass die Anzahl der Pauli-Gruppen tatsächlich abnimmt, nachdem in Slice 11 die zweite Budgetrunde verfügbar wird. Das liegt daran, dass es Gruppen mit kleinen Koeffizienten gab, die erst gekürzt werden konnten, als nach der Rückpropagierung von Slice 11 mehr Budget zur Verfügung stand; daher sammelten sie sich über mehrere Iterationen hinweg in der Observablen an. Dieser Fall verdeutlicht zudem, dass überschritten werden max_qwc_groups muss, damit der Algorithmus beendet wird.
fig, axes = plt.subplots(2, 2, figsize=(20, 10))
plot_slice_errors(metadata, axes[(0, 0)])
plot_left_over_error_budget(metadata, axes[(0, 1)])
plot_accumulated_error(metadata, axes[(1, 0)])
plot_num_qwc_groups(metadata, axes[(1, 1)])Output:
Den Gesamtfehler begrenzen
Neben der Festlegung des Fehlerbudgets pro Slice kann man auch den maximalen Fehler festlegen, der durch die Abschneidung entstehen darf. Sobald diese Grenze erreicht ist, wird keine weitere Kürzung mehr vorgenommen; die Rückpropagation wird jedoch fortgesetzt, bis die Beobachtungsgröße zu groß wird und eines der Abbruchkriterien erfüllt ist.
Anschließend wiederholen wir das obige Experiment und legen eine Obergrenze für fest, max_error_total sodass das Fehlerbudget nach der Rückpropagierung von Slice sieben aufgebraucht ist.
# Specify a length-6 per-slice budget.
# This will be cycled over 3 times to be applied to the 18 slices
max_error_per_slice = [0.0] * 4 + [0.003] * 2
truncation_error_budget = setup_budget(
max_error_per_slice=max_error_per_slice, max_error_total=0.006
)
print(truncation_error_budget)Output:
TruncationErrorBudget(per_slice_budget=[0.0, 0.0, 0.0, 0.0, 0.003, 0.003], max_error_total=0.006, p_norm=1)
bp_obs, remaining_slices, metadata = backpropagate(
obs,
slices,
operator_budget=op_budget,
truncation_error_budget=truncation_error_budget,
)
reduced_circuit = combine_slices(remaining_slices)
print(f"Backpropagated {len(slices) - len(remaining_slices)} circuit slices.")
print(
f"New observable contains {len(bp_obs)} terms and {len(bp_obs.group_commuting(qubit_wise=True))} commuting groups."
)Output:
Backpropagated 10 circuit slices.
New observable contains 67 terms and 20 commuting groups.
Wie erwartet erreicht unser Abschneidefehler seinen Höchstwert bei .006 (Diagramm unten links ). Bemerkenswert ist, dass wir bei diesem Durchlauf nicht in der Lage waren, die Rückpropagierung für Slice 11 durchzuführen. Das liegt daran, dass wir nicht über genügend Budget verfügten, um Terme zu kürzen, und die Anzahl der pendelnden Pauli-Gruppen die Grenze von überschritten hat 20, wie auf dem Bild unten rechts zu sehen ist.
fig, axes = plt.subplots(2, 2, figsize=(20, 10))
plot_slice_errors(metadata, axes[(0, 0)])
plot_left_over_error_budget(metadata, axes[(0, 1)])
plot_accumulated_error(metadata, axes[(1, 0)])
plot_num_qwc_groups(metadata, axes[(1, 1)])Output:
Es kann wünschenswert sein, das Gesamtbudget einfach auf zu begrenzen, ohne max_error_total genauer anzugeben max_error_per_slice.
truncation_error_budget = setup_budget(max_error_total=0.018)
print(truncation_error_budget)Output:
TruncationErrorBudget(per_slice_budget=[0.018], max_error_total=0.018, p_norm=1)
Die Ausgabe der obigen Zelle mag etwas überraschend sein, da auf gesetzt ist per_slice_budget``max_error_total . Dies deutet darauf hin, dass das gesamte verfügbare Budget gierig aufgebraucht wird.
Man kann es sich so vorstellen: Jedem Segment steht das gesamte Budget zur Verfügung (da wir eine Schleife über die durchlaufen per_slice_budget). Jedoch wird jedes bereits verbrauchte Budget von dem Budget abgezogen, das zu diesem bestimmten Zeitpunkt im Algorithmus noch zur Verfügung steht.
op_budget = OperatorBudget(max_qwc_groups=10)
bp_obs, remaining_slices, metadata = backpropagate(
obs,
slices,
operator_budget=op_budget,
truncation_error_budget=truncation_error_budget,
)
reduced_circuit = combine_slices(remaining_slices)
print(f"Backpropagated {len(slices) - len(remaining_slices)} circuit slices.")
print(
f"New observable contains {len(bp_obs)} terms and {len(bp_obs.group_commuting(qubit_wise=True))} commuting groups."
)Output:
Backpropagated 9 circuit slices.
New observable contains 25 terms and 9 commuting groups.
Das Bild oben rechts zeigt, wie das gesamte .018 Fehlerbudget dem ersten Schnitt zur Verfügung gestellt wird. Bis zum dritten Abschnitt erfolgt keine Kürzung, sodass das verbleibende Budget konstant bleibt. Das Budget nimmt monoton ab, da jedem rückwärtspropagierten Segment das gesamte Budget zur Verfügung gestellt wird, bis es aufgebraucht ist.
Bemerkenswert ist, dass bei diesem Experiment zwei zurückpropagierte Schichten weniger erzielt wurden als beim ersten Experiment in diesem Notizbuch, das nahezu identisch ist. Dies zeigt, dass bei manchen Problemen eine gleichmäßige Verteilung des Budgets möglicherweise optimal ist. Bei anderen Problemen kann es zu einer besseren Leistung führen, wenn man den Slices erlaubt, das gesamte Budget gierig auszuschöpfen.
fig, axes = plt.subplots(2, 2, figsize=(20, 10))
plot_slice_errors(metadata, axes[(0, 0)])
plot_left_over_error_budget(metadata, axes[(0, 1)])
plot_accumulated_error(metadata, axes[(1, 0)])
plot_num_qwc_groups(metadata, axes[(1, 1)])Output:
Die Anzahl der rückwärtspropagierten Schichten und den Gesamtfehler gemeinsam begrenzen
Wenn Sie das Fehlerbudget nicht über die gesamte Schaltung verteilen, es aber auch nicht zu großzügig verbrauchen möchten, können Sie die Anzahl der Slices, die Sie rückwärtsverbreiten möchten (num_slices), zusammen mit einem Gesamtfehlerbudget (max_error_total) angeben. Dadurch wird das Fehlerbudget gleichmäßig (gemäß p_norm) auf die Eingangs-Slices verteilt.
Hier werden wir die Anzahl der Schichten, die wir rückwärtsverbreiten 12, auf begrenzen und das gesamte Fehlerbudget beibehalten.
num_slices = 12
truncation_error_budget = setup_budget(
max_error_total=0.018, num_slices=num_slices, p_norm=1
)
print(truncation_error_budget)Output:
TruncationErrorBudget(per_slice_budget=[0.0014999999999999998], max_error_total=0.018, p_norm=1)
Nun werden wir versuchen, die 12 Slices, für die wir im vorherigen Schritt ein gewisses Budget zugewiesen haben, rückwärts zu propagieren. Dazu übergeben wir einfach die letzten 12 Segmente an unsere Schaltung. Mit p_norm=1verfügt jedes der 12 Segmente über ein Budget von 0.018 / num_slices = 0.0015.
bp_obs, remaining_slices, metadata = backpropagate(
obs,
slices[-num_slices:],
operator_budget=op_budget,
truncation_error_budget=truncation_error_budget,
)Da wir eine Teilmenge unserer Slices (im Code slices[-num_slices:]) an übergeben haben backpropagate, müssen wir die nach der Rückpropagation verbleibenden Slices mit den Slices kombinieren, die nie zur Rückpropagation gesendet wurden (im Code ) slices[:-num_slices].
Sobald wir alle verbleibenden Slices zusammengefügt haben, können wir mit combine\_slices den „ QuantumCircuit “ mit reduzierter Tiefe erzeugen. Als Nächstes untersuchen wir, wie viele Schichten rückwärtsverbreitet wurden und wie groß unsere Beobachtungsgröße dabei geworden ist.
# Recombine the slices remaining after backprop with the rest of the original circuit
reduced_circuit = combine_slices(slices[:-num_slices] + remaining_slices)
print(f"Backpropagated {num_slices - len(remaining_slices)} circuit slices.")
print(
f"New observable contains {len(bp_obs)} terms and {len(bp_obs.group_commuting(qubit_wise=True))} commuting groups."
)Output:
Backpropagated 12 circuit slices.
New observable contains 29 terms and 9 commuting groups.
Die Diagramme zeigen, dass es uns gelungen ist, alle 12 Schichten erfolgreich rückwärts zu propagieren und dabei unsere Observable unter 10 kommutierenden Pauli-Gruppen zu halten. Wir können außerdem feststellen, dass die Verwendung von num_slices zusammen mit zu einer Verteilung der Budgets auf die einzelnen Abschnitte max_error_total führt und nicht verwendete Mittel wiederum auf den nächsten Abschnitt übertragen werden. Am deutlichsten wird dies in der Grafik oben rechts, da das Budget während der Backpropagation sowohl aufgebraucht als auch wieder aufgefüllt wird.
Bemerkenswert ist, dass diese Methode der Fehlerverteilung im Vergleich zum ersten Experiment in diesem Notizbuch und zum unmittelbar vorangegangenen Beispiel die besten Ergebnisse lieferte (mehr rückwärtspropagierte Schichten). In all diesen Beispielen wurde ein .018 Fehlerbudget zugewiesen, doch die Backpropagation verlief je nach Verteilung dieses Budgets unterschiedlich.
fig, axes = plt.subplots(2, 2, figsize=(20, 10))
plot_slice_errors(metadata, axes[(0, 0)])
plot_left_over_error_budget(metadata, axes[(0, 1)])
plot_accumulated_error(metadata, axes[(1, 0)])
plot_num_qwc_groups(metadata, axes[(1, 1)])Output:
Arbeiten mit mehreren Observables
Mit dieser Methode qiskit_addon_obp.backpropagate kann man eine Folge von Observables übergeben. Dies vereinfacht den Arbeitsablauf beim Umgang mit mehreren Zielgrößen.
Wir weisen hier ausdrücklich darauf hin, um Ihnen zu verdeutlichen, wie die Trunkierungsstrategie in einem solchen Fall vorgeht. Für dieses Beispiel fügen wir dem bisher verwendeten Observable ein weiteres hinzu:
obs = [SparsePauliOp("IIIIIZIIII"), SparsePauliOp("IIIIIXIIII")]Zum Schluss wiederholen wir das erste Experiment aus diesem Tutorial., allerdings mit zwei Observablen, in die die Rückpropagierung der Schaltung erfolgen soll.
In diesem Beispiel hat dies keinen Einfluss auf die Anzahl der Schichten, die rückwärtsverbreitet werden könnten. Es zeigt sich jedoch, dass die beiden Observablen zu einer unterschiedlichen Anzahl von Pauli-Termen und kommutierenden Gruppen führten.
truncation_error_budget = setup_budget(max_error_per_slice=0.001)
print(truncation_error_budget)Output:
TruncationErrorBudget(per_slice_budget=[0.001], max_error_total=inf, p_norm=1)
bp_obs, remaining_slices, metadata = backpropagate(
obs,
slices,
operator_budget=op_budget,
truncation_error_budget=truncation_error_budget,
)
reduced_circuit = combine_slices(remaining_slices)
print(f"Backpropagated {len(slices) - len(remaining_slices)} circuit slices.")
print(
f"The new first observable contains {len(bp_obs[0])} terms and {len(bp_obs[0].group_commuting(qubit_wise=True))} commuting groups."
)
print(
f"The new second observable contains {len(bp_obs[1])} terms and {len(bp_obs[1].group_commuting(qubit_wise=True))} commuting groups."
)Output:
Backpropagated 11 circuit slices.
The new first observable contains 29 terms and 10 commuting groups.
The new second observable contains 23 terms and 8 commuting groups.
Die folgenden Grafiken veranschaulichen, wie der Backpropagation-Algorithmus mit mehreren Beobachtungsgrößen umgeht.
Zunächst zeigen die Diagramme oben links, oben rechts und unten links, dass das Budget für das Abschneiden von Termen für jede Beobachtungsgröße individuell festgelegt wird. Mit anderen Worten: Beide Observablen können Terme unter der Annahme eines Fehlers von pro 0.001 rückwärtsverbreitetem Slice abschneiden.
Aufgrund der unterschiedlichen Beschaffenheit der Beobachtungsgrößen führt dies zu einer unterschiedlichen Inanspruchnahme des Budgets. In diesem Beispiel überschneiden sie sich stark, was jedoch nicht immer der Fall ist.
Die Grafik unten rechts verdeutlicht, dass alle Beobachtungsgrößen max_qwc_groups berücksichtigt. Das bedeutet, dass die Terme aller Observablen zusammengefasst werden, um eine endgültige Anzahl von qubit-weisen kommutierenden Gruppen zu erhalten, die dann mit verglichen wird max_qwc_paulis. Das Gleiche gilt für den Schwellenwert max_paulis (der in diesem Notizbuch nicht behandelt wird), mit dem Sie eine Obergrenze für die Anzahl der Pauli-Terme über alle Observablen hinweg festlegen können.
fig, axes = plt.subplots(2, 2, figsize=(20, 10))
plot_slice_errors(metadata, axes[(0, 0)])
plot_left_over_error_budget(metadata, axes[(0, 1)])
plot_accumulated_error(metadata, axes[(1, 0)])
plot_num_qwc_groups(metadata, axes[(1, 1)])Output: