Szybki start
Ten przewodnik przedstawia minimalny działający przykład pakietu qiskit-addon-obp. Używamy propagacji wstecznej operatorów (OBP), aby zmniejszyć głębokość obwodu kwantowego, wchłaniając końcowe bramki do obserwabli.
Wartość oczekiwana nie zmienia się, jeśli oderwiemy od obwodu końcowy blok bramek i sprzęgniemy nim klasycznie obserwablę. OBP stosuje to wielokrotnie, obliczając część obwodu klasycznie, tak aby na sprzęcie działał tylko płytszy obwód. Ceną jest to, że każda wchłonięta bramka może rozszerzyć obserwablę o kolejne wyrazy Pauliego, dlatego zyskaną głębokość trzeba zestawić ze wzrostem obserwabli.
Aby zobaczyć przykłady budowania realistycznych przepływów pracy za pomocą tego narzędzia i wykonywania ich na sprzęcie kwantowym, zajrzyj do samouczków na IBM Quantum Platform (Samouczek OBP).
Przygotuj dane wejściowe dla OBP
OBP przyjmuje na wejściu listę wycinków obwodu i obserwablę. Propaguje wstecznie wycinki jeden po drugim od końca obwodu do obserwabli, zmniejszając głębokość obwodu kosztem dodatkowych wyrazów Pauliego w obserwabli. Tutaj generujemy obwód ewolucji w czasie dla 10-kubitowego modelu Heisenberga i dzielimy go na wycinki według typu bramki.
Poniżej rysujemy oryginalny obwód, a następnie ten sam obwód złożony ponownie z barierami wyznaczającymi granice wycinków — każdy wycinek jest jednostką, którą można wchłonąć do obserwabli w jednym kroku propagacji wstecznej.
# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util
_needed = {"numpy": "numpy", "qiskit": "qiskit", "qiskit_addon_obp": "qiskit-addon-obp", "qiskit_addon_utils": "qiskit-addon-utils"}
_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 numpy as np
from qiskit.quantum_info import SparsePauliOp
from qiskit.synthesis import LieTrotter
from qiskit.transpiler import CouplingMap
from qiskit_addon_utils.problem_generators import (
generate_time_evolution_circuit,
generate_xyz_hamiltonian,
)
from qiskit_addon_utils.slicing import combine_slices, slice_by_gate_types
# Generate a circuit to reduce
coupling_map = CouplingMap.from_heavy_hex(3, bidirectional=False)
reduced_coupling_map = coupling_map.reduce(
[0, 13, 1, 14, 10, 16, 5, 12, 8, 18]
)
hamiltonian = generate_xyz_hamiltonian(
reduced_coupling_map,
coupling_constants=(np.pi / 8, np.pi / 4, np.pi / 2),
ext_magnetic_field=(np.pi / 3, np.pi / 6, np.pi / 9),
)
circuit = generate_time_evolution_circuit(
hamiltonian,
time=0.2,
synthesis=LieTrotter(reps=2),
)
# Slice the circuit and define an observable
slices = slice_by_gate_types(circuit)
observable = SparsePauliOp("IIIIIIIIIZ")
print(f"Original circuit depth: {circuit.depth()}")
print(f"Number of slices: {len(slices)}")
print(f"Observable terms: {len(observable)}")
Original circuit depth: 18
Number of slices: 18
Observable terms: 1
# Recombine the slices with barriers to make the slice boundaries visible
sliced_circuit = combine_slices(slices, include_barriers=True)
print("Original circuit:")
display(circuit.draw("mpl", scale=0.6, fold=-1))
print("Sliced circuit (recombined with barriers for visualization)")
sliced_circuit.draw("mpl", scale=0.6, fold=-1)
Original circuit:

Sliced circuit (recombined with barriers for visualization)

Zmniejsz głębokość obwodu za pomocą OBP
Wywołujemy backpropagate, aby wchłonąć wycinki do obserwabli. Funkcja zwraca rozszerzoną obserwablę, wycinki obwodu, przez które nie przeprowadzono propagacji, oraz metadane dotyczące procedury.
Bez ograniczeń obserwabla może urosnąć do wyrazów Pauliego. Parametr operator_budget ogranicza ten wzrost — tutaj dopuszczamy co najwyżej 8 grup komutujących kubitowo, co z grubsza wyznacza liczbę strzałów potrzebnych do oszacowania obserwabli na QPU. Propagacja wsteczna zatrzymuje się, gdy tylko wchłonięcie następnego wycinka przekroczyłoby budżet, co dokładnie dzieje się poniżej: wchłoniętych zostaje tylko 7 z 18 wycinków, zanim obserwabla wypełni wszystkie 8 grup komutujących i procedura się zatrzyma.
Aby zajść głębiej w obwód, można użyć argumentu nazwanego truncation_error_budget metody backpropagate, który pozwala odrzucać z rosnącej obserwabli wyrazy Pauliego o małych współczynnikach. Dzięki temu wzrost obserwabli jest tłumiony kosztem pewnego błędu proporcjonalnego do wielkości obcinanych wyrazów Pauliego. Oba budżety się uzupełniają i można ich używać razem: operator_budget kontroluje rozmiar obserwabli, a truncation_error_budget kontroluje błąd wprowadzany przez odrzucanie wyrazów.
from qiskit_addon_obp import backpropagate
from qiskit_addon_obp.utils.simplify import OperatorBudget
max_qwc_groups = 8
bp_obs, remaining_slices, metadata = backpropagate(
observable,
slices,
operator_budget=OperatorBudget(max_qwc_groups=max_qwc_groups),
)
reduced_circuit = combine_slices(remaining_slices)
num_groups = len(bp_obs.group_commuting(qubit_wise=True))
print(
f"Backpropagated {metadata.num_backpropagated_slices} of {len(slices)} slices."
)
print(
f"Reduced circuit depth: {reduced_circuit.depth()} (was {circuit.depth()})"
)
print(f"Observable grew from {len(observable)} to {len(bp_obs)} Pauli terms.")
print(
f"Filled {num_groups} of {max_qwc_groups} commuting groups, exhausting the budget."
)
Backpropagated 7 of 18 slices.
Reduced circuit depth: 11 (was 18)
Observable grew from 1 to 18 Pauli terms.
Filled 8 of 8 commuting groups, exhausting the budget.
reduced_circuit.draw("mpl", scale=0.6, fold=-1)