Obcinanie wyrazów Pauliego podczas propagacji wstecznej
Ten przewodnik wyjaśnia, jak skonfigurować mechanizm obcinania wyrazów Pauliego udostępniany przez moduł qiskit_addon_obp.utils.truncating.
Propagację wsteczną operatorów (OBP) można wykorzystać do zmniejszenia głębokości obwodów kwantowych kosztem bardziej złożonej obserwabli. Aby uzyskać sensowne wyniki z OBP, zwykle trzeba obcinać wyrazy obserwabli, by nie rosła zbyt mocno. Jednym ze sposobów na umożliwienie głębszej propagacji wstecznej w obwodzie przy jednoczesnym niedopuszczeniu do nadmiernego wzrostu operatora jest obcinanie wyrazów o małych współczynnikach zamiast dodawania ich do operatora. Obcinanie wyrazów może zmniejszyć liczbę obwodów kwantowych do wykonania, ale powoduje pewien błąd w końcowym obliczeniu wartości oczekiwanej, proporcjonalny do wielkości współczynników obciętych wyrazów.
Metoda backpropagate przyjmuje opcjonalny TruncationErrorBudget, który konfiguruje obcinanie wyrazów Pauliego o niskiej wadze w każdej obserwabli po pomyślnej propagacji wstecznej każdego wycinka. Liczba obcinanych wyrazów zależy od różnych parametrów konfiguracyjnych określanych przez użytkownika. Obecnie dostępna jest tylko jedna strategia obcinania — metoda truncate_binary_search. Dla danej obserwabli i pewnego budżetu wykonuje ona wyszukiwanie binarne po wyrazach Pauliego i współczynnikach w tej obserwabli, aby znaleźć optymalny próg, taki że suma obciętych współczynników jest maksymalna, ale mniejsza od budżetu.
Uwaga: Domyślnie do oceny i ograniczania błędu obcięcia używana jest norma L1; ustawienie p_norm pozwala jednak określić, jaka norma Lp ma być użyta.
Więcej informacji o tym ustawieniu znajdziesz w przewodniku Używanie różnych norm Lp do obcinania wyrazów Pauliego.
Poniższe przykłady ilustrują różne sposoby budowania TruncationErrorBudget za pomocą towarzyszącej funkcji setup_budget.
Zbuduj przykładowy obwód
Ten przewodnik używa następujących wycinków obwodu:
# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util
_needed = {"matplotlib": "matplotlib", "qiskit": "qiskit", "qiskit_addon_obp": "qiskit-addon-obp", "qiskit_addon_utils": "qiskit-addon-utils", "rustworkx": "rustworkx"}
_missing = [pip for module, pip in _needed.items()
if importlib.util.find_spec(module) is None]
# One at a time, so a package that fails to install does not block the others
for _pip in _missing:
%pip install -q {_pip}
if not _missing:
print("\u2713 All required packages are installed")
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)

Przyjrzymy się jednej prostej obserwablę:
from qiskit.quantum_info import SparsePauliOp
obs = SparsePauliOp("IIIIIZIIII")
Najprostszy przypadek: stały budżet obcinania dla każdego wycinka
Dostępny budżet na obcinanie wyrazów Pauliego może się zmieniać w każdym kroku propagacji wstecznej. Aby zrozumieć, jak to działa, najpierw przyjrzymy się najprostszemu przypadkowi stałego budżetu obcinania określonego przez użytkownika.
Najprostszym sposobem określenia budżetu obcinania jest użycie argumentu max_error_per_slice. W istocie tak właśnie robi się to w samouczku Zmniejszanie głębokości obwodów za pomocą propagacji wstecznej operatorów. Ustawienie max_error_per_slice na float powoduje, że każdy wycinek otrzymuje budżet równy tej wartości.
W poniższym przykładzie ustawiamy tę wartość na 0.001, co gwarantuje niejawny błąd obcięcia wynoszący co najwyżej 0.018, jeśli propagacji wstecznej poddano by wszystkie 18 wycinków.
Zauważ, że każdy budżet błędu pozostały po propagacji wstecznej wycinka i obcięciu wyrazów o małych współczynnikach zawsze zostanie dodany do budżetu błędu następnego wycinka.
from qiskit_addon_obp.utils.truncating import setup_budget
truncation_error_budget = setup_budget(max_error_per_slice=0.001)
print(truncation_error_budget)
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."
)
Backpropagated 11 circuit slices.
New observable contains 29 terms and 10 commuting groups.
Następnie używamy instancji OBPMetadata oraz narzędzi udostępnianych przez moduł visualization, aby zwizualizować proces propagacji wstecznej.
-
Wykres w lewym górnym rogu pokazuje, że mamy wystarczający budżet, aby zacząć obcinać wyrazy obserwabli po propagacji wstecznej trzeciego wycinka. Począwszy od trzeciego wycinka wiemy, że z każdego propagowanego wstecznie wycinka obcinamy co najmniej jeden wyraz, ponieważ po każdym wycinku ponosimy pewien błąd obcięcia.
-
Wykres w prawym górnym rogu pokazuje, że budżet błędu rośnie do
.003dla trzeciego wycinka. Widzimy gwałtowny spadek pozostałego budżetu, co oznacza, że z obserwabli obcięto wyrazy. Jest to zgodne z tym, co wywnioskowaliśmy z wykresu w lewym górnym rogu. -
Wykres w lewym dolnym rogu pokazuje, że w miarę obcinania wyrazów z naszej obserwabli całkowity skumulowany błąd rośnie monotonicznie. Ten wykres potwierdza też, że żadne wyrazy nie były obcinane aż do czasu propagacji wstecznej trzeciego wycinka.
-
Wykres w prawym dolnym rogu pokazuje, że liczba komutujących grup Pauliego w naszej obserwabli wzrosła do określonego limitu
10. Ten wykres pokazuje także, że propagacja wsteczna jeszcze jednej warstwy sprawiłaby, że obserwabla przekroczyłaby określoną granicę, co widać po przecięciu się czarnej i czerwonej linii.
Zauważ, że na wszystkich tych wykresach oś x numeruje wycinki poddane propagacji wstecznej, ale ponieważ OBP działa od końca obwodu, slice 1 jest ostatnim wycinkiem, slice 2 jest tym przed nim i tak dalej.
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)])

Jawne określenie budżetu wycinków
Jeśli wiesz, jak przypisać budżet każdemu wycinkowi, tak aby zoptymalizować wydajność propagacji wstecznej, możesz chcieć jawnie przypisać budżet dla każdego wycinka. Dla celów demonstracyjnych przypiszemy pierwszym trzem wycinkom zerowy budżet, a pozostałym wycinkom budżet .001 na wycinek.
# 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)
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."
)
Backpropagated 11 circuit slices.
New observable contains 32 terms and 10 commuting groups.
Usunięcie budżetu z pierwszych trzech warstw spowodowało, że żadne wyrazy nie były obcinane aż do czwartej warstwy, co można potwierdzić na trzech z tych wykresów. Co może być nieco zaskakujące, chociaż czwarty wycinek nie otrzymał żadnego pozostałego budżetu, wyraz został mimo to obcięty dzięki przydzielonemu budżetowi .001. Jest to oczywiste na wykresie w lewym górnym rogu, ale widać to także na wykresie w prawym górnym rogu, gdzie krzywa pozostałego budżetu wypłaszcza się między wycinkami trzecim a czwartym. Kolejnym godnym uwagi szczegółem jest to, że od tego momentu obcinany był co najmniej jeden wyraz, tak samo jak w poprzednim przykładzie.
Kluczowy wniosek jest taki, że choć w drugim przykładzie ponieśliśmy mniejszy błąd obcięcia dzięki wyzerowaniu budżetów niektórych wycinków, udało nam się wykonać propagację wsteczną tej samej liczby wycinków, a nasza obserwabla zawiera taką samą liczbę komutujących grup Pauliego. Że ograniczenie naszego błędu jest mniejsze w drugim przykładzie, możemy potwierdzić, sprawdzając wykres w lewym dolnym rogu.
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)])

Cykliczne określenie budżetu
Jeśli masz obwód z pewnym powtarzalnym wzorcem, takim jak obwód Trottera, możesz chcieć określić budżet dla tego powtarzanego podzbioru wycinków i użyć go dla wszystkich kolejnych powtórzeń tych wycinków.
Dokładniej, przykładowy obwód, którego używamy, ma sześć wycinków powtórzonych trzykrotnie, co daje łącznie 18 wycinków. Arbitralnie przypiszemy zerowy budżet warstwom jednokubitowym i RYY, a budżet .003 każdej z warstw RXX. Zobaczymy, jak określenie budżetu jako sekwencji o długości 6 powoduje, że budżet jest stosowany cyklicznie do wszystkich 18 wycinków.
Ponownie zauważ, że wycinki są propagowane wstecznie w odwrotnej kolejności (czyli zaczynając od końca). Dlatego pierwszy element naszego cyklicznego budżetu jest faktycznie użyty dla ostatniego wycinka, drugi dla wycinka przed nim i tak dalej.
# 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)
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."
)
Backpropagated 13 circuit slices.
New observable contains 49 terms and 14 commuting groups.
Jak widać na obrazach w lewym górnym i lewym dolnym rogu, w pierwszych czterech wycinkach nie przeprowadzono obcinania, ponieważ nie przydzielono im budżetu błędu. W wycinkach piątym i szóstym część wyrazów została obcięta, gdy tylko budżet stał się dostępny.
Jak widać na obrazie w lewym górnym rogu, po propagacji wstecznej wycinka siódmego wprowadzono stosunkowo duży błąd, mimo że temu wycinkowi nie przydzielono budżetu. Dzieje się tak, ponieważ z łącznego budżetu .006 przydzielonego wycinkom piątemu i szóstemu wykorzystano tylko około .002, więc reszta została przekazana do wycinka siódmego i w większości zużyta, co widać na obrazie w prawym górnym rogu.
Obrazy w lewym górnym i prawym górnym rogu pokazują, że niewielki pozostały budżet zostaje zużyty między wycinkami 8–10, a nowy budżet staje się dostępny w wycinku 11, zgodnie z oczekiwaniami. Obrazy w lewym górnym, prawym górnym i lewym dolnym rogu pokazują cykliczne zachowanie argumentu max_error_per_slice, gdy jego długość jest mniejsza niż liczba wycinków. To cykliczne zachowanie trwałoby przez wszystkie wycinki, ale kryterium zatrzymania max_qwc_groups zostało osiągnięte po propagacji wstecznej 13 wycinków, co widać na obrazie w prawym dolnym rogu.
Interesujące jest również to, że liczba grup Pauliego faktycznie spada po tym, jak w wycinku 11 staje się dostępna druga porcja budżetu. Dzieje się tak, ponieważ istniały grupy o małych współczynnikach, których nie można było obciąć, dopóki po propagacji wstecznej wycinka 11 nie pojawił się większy budżet, więc przez kilka iteracji gromadziły się w obserwabli. Ten przypadek podkreśla również, że aby algorytm się zakończył, max_qwc_groups musi zostać przekroczone.
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)])

Ograniczenie całkowitego błędu
Oprócz określenia budżetu błędu na wycinek można określić maksymalną wielkość błędu wprowadzanego przez obcinanie. Po osiągnięciu tego limitu obcinanie nie będzie już wykonywane; propagacja wsteczna będzie jednak trwać, aż obserwabla stanie się zbyt duża i zostanie spełnione jedno z kryteriów zatrzymania.
Następnie ponownie uruchamiamy powyższy eksperyment, ustawiając limit max_error_total tak, aby budżet błędu został wyczerpany po propagacji wstecznej wycinka siódmego.
# 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)
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."
)
Backpropagated 10 circuit slices.
New observable contains 67 terms and 20 commuting groups.
Zgodnie z oczekiwaniami nasz błąd obcięcia osiąga maksimum .006 (wykres w lewym dolnym rogu). Warto zauważyć, że w tym przebiegu nie udało nam się wykonać propagacji wstecznej wycinka 11. Dzieje się tak, ponieważ nie mieliśmy dość budżetu, aby obcinać wyrazy, a liczba komutujących grup Pauliego przekroczyła limit 20, co widać na obrazie w prawym dolnym rogu.
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)])

Można chcieć po prostu ograniczyć całkowity budżet za pomocą max_error_total, bez określania max_error_per_slice.
truncation_error_budget = setup_budget(max_error_total=0.018)
print(truncation_error_budget)
TruncationErrorBudget(per_slice_budget=[0.018], max_error_total=0.018, p_norm=1)
Wynik powyższej komórki może być nieco zaskakujący, ponieważ per_slice_budget jest ustawiony na max_error_total. Oznacza to, że cały dostępny budżet zostanie zużyty zachłannie.
Można to rozumieć tak: cały budżet jest dostępny dla każdego wycinka (ponieważ iterujemy po per_slice_budget). Jednak każdy budżet, który został już zużyty, zostanie odjęty od budżetu dostępnego w danym momencie działania algorytmu.
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."
)
Backpropagated 9 circuit slices.
New observable contains 25 terms and 9 commuting groups.
Obraz w prawym górnym rogu pokazuje, jak cały budżet błędu .018 jest udostępniony pierwszemu wycinkowi. Obcinanie nie jest wykonywane aż do trzeciego wycinka, więc pozostały budżet pozostaje stały. Budżet maleje monotonicznie, ponieważ pełny budżet jest udostępniany każdemu wycinkowi poddanemu propagacji wstecznej, aż do jego wyczerpania.
Warto zauważyć, że ten eksperyment dał o dwa mniej wycinki poddane propagacji wstecznej w porównaniu z pierwszym eksperymentem w tym notatniku, który jest niemal identyczny. Pokazuje to, że dla niektórych problemów równomierne rozłożenie budżetu może być optymalne. Dla innych problemów pozwolenie wycinkom na zachłanne wydatkowanie pełnego budżetu może dać lepszą wydajność.
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)])

Ograniczenie jednocześnie liczby wycinków poddanych propagacji wstecznej i całkowitego błędu
Jeśli nie chcesz rozkładać budżetu błędu na cały obwód, ale nie chcesz też wydawać go zachłannie, możesz określić liczbę wycinków, które zamierzasz poddać propagacji wstecznej (num_slices), wraz z całkowitym budżetem błędu (max_error_total). Spowoduje to równomierne rozłożenie budżetu błędu (zgodnie z p_norm) na wejściowe wycinki.
Tutaj ograniczymy liczbę wycinków, które możemy poddać propagacji wstecznej, do 12 i zachowamy ten sam całkowity budżet błędu.
num_slices = 12
truncation_error_budget = setup_budget(
max_error_total=0.018, num_slices=num_slices, p_norm=1
)
print(truncation_error_budget)
TruncationErrorBudget(per_slice_budget=[0.0014999999999999998], max_error_total=0.018, p_norm=1)
Teraz spróbujemy wykonać propagację wsteczną 12 wycinków, którym w poprzednim kroku przydzieliliśmy pewien budżet. Robimy to, przekazując po prostu ostatnie 12 wycinków naszego obwodu. Przy p_norm=1 każdy z 12 wycinków ma dostępny budżet 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,
)
Ponieważ do backpropagate przekazaliśmy podzbiór naszych wycinków (w kodzie slices[-num_slices:]), musimy połączyć wycinki pozostałe po propagacji wstecznej z wycinkami, które nigdy nie zostały wysłane do propagacji wstecznej (w kodzie slices[:-num_slices]).
Po połączeniu wszystkich pozostałych wycinków możemy użyć combine_slices, aby wygenerować obwód QuantumCircuit o zmniejszonej głębokości. Następnie sprawdzamy, ile wycinków poddano propagacji wstecznej w porównaniu z tym, jak duża stała się nasza obserwabla.
# 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."
)
Backpropagated 12 circuit slices.
New observable contains 29 terms and 9 commuting groups.
Wykresy pokazują, że z powodzeniem wykonaliśmy propagację wsteczną wszystkich 12 warstw, utrzymując nasza obserwablę poniżej 10 komutujących grup Pauliego. Widać też, że użycie num_slices razem z max_error_total skutkuje rozdzieleniem budżetów między warstwy, a niewykorzystany budżet jest ponownie przekazywany do następnej warstwy. Najlepiej widać to na wykresie w prawym górnym rogu, ponieważ budżet jest zarówno wydawany, jak i uzupełniany w trakcie propagacji wstecznej.
Warto zauważyć, że ta metoda rozdzielania błędu dała najlepsze wyniki (więcej warstw objętych propagacją wsteczną) w porównaniu z pierwszym eksperymentem w tym notatniku i z przykładem bezpośrednio poprzedzającym ten. We wszystkich tych przykładach przydzielono budżet błędu .018, ale propagacja wsteczna działała różnie w zależności od tego, jak ten budżet został rozdzielony.
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)])

Praca z wieloma obserwablami
Metoda qiskit_addon_obp.backpropagate pozwala przekazać sekwencję obserwabli. Upraszcza to przepływ pracy przy wielu docelowych obserwablach.
Wspominamy o tym wprost, aby pokazać, jak strategia obcinania radzi sobie z takim przypadkiem. Na potrzeby tego przykładu dodajemy jedną dodatkową obserwablę do tej, której używaliśmy dotychczas:
obs = [SparsePauliOp("IIIIIZIIII"), SparsePauliOp("IIIIIXIIII")]
Na koniec powtarzamy pierwszy eksperyment z tego samouczka, ale z dwiema obserwablami, w które propagujemy wstecznie obwód.
W tym przykładzie nie wpływa to na liczbę warstw, które można było objąć propagacją wsteczną. Widać jednak, że dwie obserwable dały różne liczby składników Pauliego i grup komutujących.
truncation_error_budget = setup_budget(max_error_per_slice=0.001)
print(truncation_error_budget)
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."
)
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.
Poniższe wykresy pomagają zilustrować, jak algorytm propagacji wstecznej obsługuje wiele obserwabli.
Po pierwsze, wykresy w lewym górnym rogu, prawym górnym rogu i lewym dolnym rogu pokazują, że budżet na obcinanie składników jest ustalany osobno dla każdej obserwabli. Innymi słowy, obie obserwable mogą obcinać składniki, przyjmując błąd 0.001 na każdą warstwę objętą propagacją wsteczną.
Ze względu na różny charakter obserwabli prowadzi to do różnego zużycia budżetu. W tym przykładzie widać między nimi duże pokrycie, co nie zawsze się zdarza.
Wykres w prawym dolnym rogu pokazuje, że max_qwc_groups uwzględnia wszystkie obserwable. Oznacza to, że składniki wszystkich obserwabli są grupowane razem, aby uzyskać jedną końcową liczbę grup komutujących kubitowo, którą porównuje się z max_qwc_paulis. To samo dotyczy progu max_paulis (nieomawianego w tym notatniku), który pozwala ustawić limit liczby składników Pauliego we wszystkich obserwablach.
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)])
