Início rápido
Este guia demonstra um exemplo mínimo funcional do pacote qiskit-addon-obp. Usamos a retropropagação de operadores (OBP) para reduzir a profundidade de um circuito quântico absorvendo as portas finais no observável.
Um valor esperado não muda se retirarmos um bloco final de portas do circuito e conjugarmos o observável por ele de forma clássica. A OBP aplica isso repetidamente, avaliando parte do circuito de forma clássica para que apenas um circuito mais raso seja executado no hardware. O preço é que cada porta absorvida pode expandir o observável em mais termos de Pauli, então a profundidade economizada precisa ser ponderada contra o crescimento do observável.
Para ver exemplos de como criar fluxos de trabalho realistas com essa ferramenta e executar em hardware quântico, confira os tutoriais na IBM Quantum Platform (Tutorial de OBP).
Prepare as entradas para a OBP
A OBP recebe como entrada uma lista de fatias de circuito e um observável. Ela retropropaga as fatias uma de cada vez, do final do circuito para o observável, reduzindo a profundidade do circuito ao custo de mais termos de Pauli no observável. Aqui geramos um circuito de evolução temporal para um modelo de Heisenberg de 10 qubits e o dividimos em fatias por tipo de porta.
Abaixo desenhamos o circuito original e, em seguida, o mesmo circuito recombinado com barreiras marcando os limites das fatias — cada fatia é uma unidade que pode ser absorvida pelo observável em uma única etapa de retropropagação.
# 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)

Reduza a profundidade do circuito com a OBP
Chamamos backpropagate para absorver fatias no observável. A função retorna o observável expandido, as fatias do circuito pelas quais não houve propagação e metadados sobre o procedimento.
Sem controle, o observável pode crescer até termos de Pauli. O operator_budget limita esse crescimento — aqui permitimos no máximo 8 grupos que comutam qubit a qubit, o que define aproximadamente o número de shots necessários para avaliar o observável na QPU. A retropropagação para assim que absorver a próxima fatia excederia o orçamento, que é exatamente o que acontece abaixo: apenas 7 das 18 fatias são absorvidas antes que o observável preencha todos os 8 grupos comutantes e o procedimento pare.
Para avançar mais fundo no circuito, o argumento nomeado truncation_error_budget de backpropagate pode ser usado para descartar termos de Pauli de coeficiente pequeno do observável conforme ele cresce. Isso reduz o crescimento do observável em troca de algum erro proporcional à magnitude dos termos de Pauli truncados. Os dois orçamentos são complementares e podem ser usados juntos: operator_budget controla o tamanho do observável, enquanto truncation_error_budget controla o erro incorrido ao descartar termos.
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)