Zum Hauptinhalt springen
Dieses Projekt befindet sich in der Beta-Phase. Einen Fehler gefunden? GitHub Issue erstellen. Frage oder Idee? In Discussions fragen — wir freuen uns über dein Feedback!
Wie ist die Übersetzungsqualität auf dieser Seite?

Schnellstart

Diese Anleitung zeigt ein minimales Arbeitsbeispiel des Pakets qiskit-addon-obp. Wir verwenden Operator Backpropagation (OBP), um die Tiefe eines Quanten-Circuits zu verringern, indem abschließende Gates in die Observable aufgenommen werden.

Ein Erwartungswert ⟨O⟩=⟨ψ∣U†OU∣ψ⟩\langle O \rangle = \langle \psi | U^\dagger O U | \psi \rangle bleibt unverändert, wenn wir einen abschließenden Block von Gates vom Circuit UU abtrennen und die Observable klassisch damit konjugieren. OBP wendet das wiederholt an und wertet dabei einen Teil des Circuits klassisch aus, sodass nur ein flacherer Circuit auf der Hardware läuft. Der Preis ist, dass jedes aufgenommene Gate die Observable in mehr Pauli-Terme ausdehnen kann. Die eingesparte Tiefe muss daher gegen das Wachstum der Observable abgewogen werden.

Beispiele dafür, wie du realistische Workflows mit diesem Werkzeug aufbaust und auf Quantenhardware ausführst, findest du in den Tutorials auf der IBM Quantum Platform (OBP-Tutorial).

Eingaben für OBP vorbereiten​

OBP erhält als Eingabe eine Liste von Circuit-Slices und eine Observable. Es rückpropagiert die Slices nacheinander vom Ende des Circuits in die Observable und verringert so die Circuit-Tiefe auf Kosten zusätzlicher Pauli-Terme in der Observable. Hier erzeugen wir einen Zeitentwicklungs-Circuit für ein Heisenberg-Modell mit 10 Qubits und zerlegen ihn nach Gate-Typ in Slices.

Unten zeichnen wir den ursprünglichen Circuit und danach denselben Circuit, wieder zusammengesetzt mit Barrieren, die die Slice-Grenzen markieren. Jeder Slice ist eine Einheit, die in einem einzigen Rückpropagationsschritt in die Observable aufgenommen werden kann.

# 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:

Output of the previous code cell

Sliced circuit (recombined with barriers for visualization)

Output of the previous code cell

Circuit-Tiefe mit OBP verringern​

Wir rufen backpropagate auf, um Slices in die Observable aufzunehmen. Die Funktion gibt die erweiterte Observable, die Circuit-Slices, durch die nicht propagiert wurde, und Metadaten zum Verfahren zurück.

Ohne Begrenzung kann die Observable bis auf 2n2^n Pauli-Terme anwachsen. Das operator_budget begrenzt dieses Wachstum. Hier erlauben wir höchstens 8 Qubit-weise kommutierende Gruppen, was grob die Anzahl der Shots festlegt, die zur Auswertung der Observable auf der QPU nötig sind. Die Rückpropagation endet, sobald die Aufnahme des nächsten Slices das Budget überschreiten würde. Genau das passiert unten: Nur 7 der 18 Slices werden aufgenommen, bevor die Observable alle 8 kommutierenden Gruppen füllt und das Verfahren stoppt.

Um tiefer in den Circuit vorzudringen, kann das Schlüsselwortargument truncation_error_budget von backpropagate verwendet werden, um Pauli-Terme mit kleinen Koeffizienten aus der Observable zu entfernen, während sie wächst. Das dämpft das Wachstum der Observable im Austausch für einen Fehler, der proportional zur Größe der abgeschnittenen Pauli-Terme ist. Die beiden Budgets ergänzen sich und können zusammen verwendet werden: operator_budget steuert die Größe der Observable, während truncation_error_budget den Fehler steuert, der durch das Verwerfen von Termen entsteht.

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)

Output of the previous code cell