Vai al contenuto principale
Questo progetto è in fase beta. Hai trovato un bug? Apri una issue su GitHub. Hai una domanda o un'idea? Chiedi su GitHub Discussions — apprezziamo il tuo feedback!
Com'è la qualità della traduzione su questa pagina?

Guida rapida

Questa guida mostra un esempio minimo funzionante del pacchetto qiskit-addon-obp. Usiamo la retropropagazione dell'operatore (OBP) per ridurre la profondità di un circuito quantistico assorbendo le gate finali nell'osservabile.

Un valore di aspettazione ⟨O⟩=⟨ψ∣U†OU∣ψ⟩\langle O \rangle = \langle \psi | U^\dagger O U | \psi \rangle non cambia se stacchiamo un blocco finale di gate dal circuito UU e coniughiamo classicamente l'osservabile con esso. L'OBP applica questa operazione ripetutamente, valutando classicamente parte del circuito in modo che sull'hardware venga eseguito solo un circuito meno profondo. Il prezzo da pagare è che ogni gate assorbita può espandere l'osservabile in un numero maggiore di termini di Pauli, quindi la profondità risparmiata va bilanciata con la crescita dell'osservabile.

Per vedere esempi di come costruire workflow realistici con questo strumento ed eseguirli su hardware quantistico, consulta i tutorial su IBM Quantum Platform (Tutorial OBP).

Preparare gli input per l'OBP​

L'OBP prende come input un elenco di slice del circuito e un'osservabile. Retropropaga le slice una alla volta dalla fine del circuito nell'osservabile, riducendo la profondità del circuito al costo di termini di Pauli aggiuntivi nell'osservabile. Qui generiamo un circuito di evoluzione temporale per un modello di Heisenberg a 10 qubit e lo suddividiamo in slice per tipo di gate.

Di seguito disegniamo il circuito originale, poi lo stesso circuito ricombinato con barriere che segnano i confini delle slice: ogni slice è un'unità che può essere assorbita nell'osservabile in un singolo passo di retropropagazione.

# 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

Ridurre la profondità del circuito con l'OBP​

Chiamiamo backpropagate per assorbire le slice nell'osservabile. La funzione restituisce l'osservabile espansa, le slice del circuito attraverso le quali non è avvenuta la propagazione e i metadati sulla procedura.

Se non controllata, l'osservabile può crescere fino a 2n2^n termini di Pauli. L'operator_budget limita questa crescita: qui consentiamo al massimo 8 gruppi qubit-wise commutanti, il che determina approssimativamente il numero di shot necessari per valutare l'osservabile sulla QPU. La retropropagazione si ferma non appena l'assorbimento della slice successiva supererebbe il budget, ed è esattamente ciò che accade di seguito: solo 7 delle 18 slice vengono assorbite prima che l'osservabile riempia tutti gli 8 gruppi commutanti e la procedura si arresti.

Per spingersi più in profondità nel circuito, si può usare l'argomento keyword truncation_error_budget di backpropagate per scartare i termini di Pauli con coefficiente piccolo dall'osservabile man mano che cresce. Questo smorza la crescita dell'osservabile in cambio di un errore proporzionale alla magnitudine dei termini di Pauli troncati. I due budget sono complementari e possono essere usati insieme: operator_budget controlla la dimensione dell'osservabile, mentre truncation_error_budget controlla l'errore introdotto dallo scarto dei termini.

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