Démarrage rapide
Ce guide présente un exemple minimal fonctionnel du paquet qiskit-addon-obp. Nous utilisons la rétropropagation d'opérateurs (OBP) pour réduire la profondeur d'un circuit quantique en absorbant les portes finales dans l'observable.
Une valeur moyenne reste inchangée si l'on retire un bloc final de portes du circuit et que l'on conjugue classiquement l'observable par ce bloc. L'OBP applique cela de façon répétée, en évaluant classiquement une partie du circuit afin que seul un circuit moins profond s'exécute sur le matériel. Le prix à payer est que chaque porte absorbée peut étendre l'observable en un plus grand nombre de termes de Pauli ; la profondeur économisée doit donc être mise en balance avec la croissance de l'observable.
Pour voir des exemples de construction de workflows réalistes avec cet outil et d'exécution sur du matériel quantique, consulte les tutoriels sur la IBM Quantum Platform (Tutoriel OBP).
Préparer les entrées pour l'OBP
L'OBP prend en entrée une liste de tranches de circuit et une observable. Il rétropropage les tranches une à une, de la fin du circuit vers l'observable, réduisant la profondeur du circuit au prix de termes de Pauli supplémentaires dans l'observable. Ici, nous générons un circuit d'évolution temporelle pour un modèle de Heisenberg à 10 qubits et le découpons par type de porte.
Ci-dessous, nous dessinons le circuit original, puis le même circuit recombiné avec des barrières marquant les limites des tranches — chaque tranche est une unité qui peut être absorbée dans l'observable en une seule étape de rétropropagation.
# 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)

Réduire la profondeur du circuit avec l'OBP
Nous appelons backpropagate pour absorber des tranches dans l'observable. La fonction renvoie l'observable étendue, les tranches de circuit qui n'ont pas été rétropropagées, et des métadonnées sur la procédure.
Sans contrôle, l'observable peut croître jusqu'à termes de Pauli. Le operator_budget plafonne cette croissance — ici, nous autorisons au plus 8 groupes qui commutent qubit à qubit, ce qui fixe approximativement le nombre de shots nécessaires pour évaluer l'observable sur le QPU. La rétropropagation s'arrête dès que l'absorption de la tranche suivante dépasserait le budget, ce qui est exactement ce qui se passe ci-dessous : seules 7 des 18 tranches sont absorbées avant que l'observable remplisse les 8 groupes commutants et que la procédure s'arrête.
Pour aller plus profondément dans le circuit, l'argument nommé truncation_error_budget de backpropagate peut être utilisé pour supprimer de l'observable les termes de Pauli de faible coefficient à mesure qu'elle grandit. Cela amortit la croissance de l'observable en échange d'une erreur proportionnelle à la magnitude des termes de Pauli tronqués. Les deux budgets sont complémentaires et peuvent être utilisés ensemble : operator_budget contrôle la taille de l'observable, tandis que truncation_error_budget contrôle l'erreur introduite en écartant des termes.
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)