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?

Come rendere più stretti i limiti con uno speed limit

L'informazione in un circuito di gate a due qubit non può diffondersi arbitrariamente in fretta. Un errore che si verifica qualche livello prima della fine, su un qubit lontano dal supporto dell'osservabile, non ha alcun cammino per raggiungerla. tighten_with_speed_limit trasforma questo argomento geometrico (la stessa intuizione alla base dei limiti di Lieb-Robinson) in limiti forward più stretti.

Il passaggio è economico, non richiede nulla oltre a ciò che i limiti forward richiedevano già, e può solo rendere i limiti più stretti. Non c'è alcun compromesso di accuratezza da valutare.

La domanda a cui vale la pena rispondere per prima è "quando è utile?", perché non aiuta spesso su un insieme di limiti forward completamente convergiti.

Configurazione​

Usa lo stesso circuito di Ising con Trotterizzazione della guida rapida, con l'osservabile su un singolo qubit al centro della catena. La geometria 1D rende facile ragionare sull'effetto: un'influenza deve attraversare la catena un legame per livello.

# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util

_needed = {"numpy": "numpy", "qiskit": "qiskit", "qiskit_addon_slc": "qiskit-addon-slc", "samplomatic": "samplomatic"}
_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 import QuantumCircuit
from qiskit.quantum_info import Pauli
from qiskit_addon_slc.bounds import (
compute_forward_bounds,
tighten_with_speed_limit,
)
from qiskit_addon_slc.utils import generate_noise_model_paulis
from samplomatic.transpiler import generate_boxing_pass_manager
from samplomatic.utils import find_unique_box_instructions

def trotter_ising_circuit(num_qubits, num_steps, rx_angle, rzz_angle):
"""Trotterized transverse-field Ising evolution on a 1D chain."""
circuit = QuantumCircuit(num_qubits)
for _ in range(num_steps):
circuit.rx(rx_angle, range(num_qubits))
circuit.barrier()
for start in (0, 1): # even then odd bonds
for i in range(start, num_qubits - 1, 2):
circuit.rzz(rzz_angle, i, i + 1)
circuit.barrier()
return circuit

num_qubits = 10
circuit = trotter_ising_circuit(
num_qubits, num_steps=4, rx_angle=np.pi / 16, rzz_angle=-np.pi / 3
)

# A single-qubit observable in the middle of the chain.
observable = Pauli("I" * num_qubits).compose("Z", [num_qubits // 2])

boxing_pass = generate_boxing_pass_manager(
inject_noise_targets="all",
inject_noise_strategy="individual_modification",
inject_noise_site="after",
twirling_strategy="active",
remove_barriers="never",
)
boxed_circuit = boxing_pass.run(circuit)
noise_model_paulis = generate_noise_model_paulis(
find_unique_box_instructions(boxed_circuit)
)

def compare(label, bounds):
"""Tightens a set of bounds and prints what changed."""
tightened = tighten_with_speed_limit(
bounds, boxed_circuit, noise_model_paulis, observable
)
before = np.concatenate([bound.rates for bound in bounds.values()])
after = np.concatenate([bound.rates for bound in tightened.values()])
print(
f"{label:30} mean {before.mean():.4f} -> {after.mean():.4f} "
f"tightened {np.count_nonzero(after < before - 1e-12):4} of {len(before)} "
f"loosened {np.count_nonzero(after > before + 1e-12)}"
)

print(
f"observable: Z on qubit {num_qubits // 2}, circuit depth {circuit.depth()}"
)
observable: Z on qubit 5, circuit depth 12

Quando aiuta e quando no​

tighten_with_speed_limit prende i limiti che hai già e restituisce una copia più stretta; i tuoi limiti originali non vengono mai modificati. Quanto guadagna dipende interamente da quanto fosse largo l'input.

# 1. Fully computed forward bounds.
full_bounds = compute_forward_bounds(
boxed_circuit, noise_model_paulis, observable
)
compare("fully computed", full_bounds)

# 2. Bounds where the eigenvalue step was skipped, leaving the looser triangle inequality.
loose_bounds = compute_forward_bounds(
boxed_circuit, noise_model_paulis, observable, eigval_max_qubits=0
)
compare("triangle inequality only", loose_bounds)

# 3. Bounds computed only for the first few boxes, the rest left at the trivial bound of 2.0.
shallow_bounds = compute_forward_bounds(
boxed_circuit, noise_model_paulis, observable, max_num_boxes=2
)
compare("max_num_boxes=2", shallow_bounds)
fully computed mean 0.3639 -> 0.3639 tightened 0 of 792 loosened 0
triangle inequality only mean 0.5081 -> 0.4842 tightened 105 of 792 loosened 0
max_num_boxes=2 mean 1.5707 -> 0.5284 tightened 450 of 792 loosened 0

La prima riga è quella da notare. Sui limiti forward completamente calcolati, lo speed limit non cambia nulla. Non è un fallimento. Significa che i limiti erano già almeno tanto stretti quanto l'argomento geometrico può dimostrare, perché l'evoluzione dell'operatore ha di fatto scoperto da sola il cono di luce.

I guadagni compaiono dove i limiti non erano completamente calcolati:

  • eigval_max_qubits=0 forza il ripiego sulla disuguaglianza triangolare, quindi i limiti non riflettono più la norma spettrale effettiva del commutatore. Lo speed limit recupera parte di quella perdita.

  • max_num_boxes=2 lascia la maggior parte dei livelli al limite banale di 2.0 (un limite che non codifica alcuna informazione). Qui lo speed limit è utile, riducendo la media di circa un fattore tre.

In ogni caso la colonna "allentati" è zero. Nessun termine peggiora mai, ed è la garanzia che rende il passaggio sicuro da applicare incondizionatamente.

Cosa guadagni in overhead di campionamento​

Limiti più stretti significano meno termini di errore da mitigare per una data tolleranza di bias, che è il numero che conta sull'hardware.

from qiskit.quantum_info import PauliLindbladMap, QubitSparsePauliList
from qiskit_addon_slc.bounds import compute_local_scales

# Stand-in for rates that a noise-learning experiment would measure on hardware.
rng = np.random.default_rng(42)
noise_rates = {
layer: PauliLindbladMap.from_components(
rng.random(len(terms)) * 5e-3,
QubitSparsePauliList.from_sparse_list(
terms.to_sparse_list(), terms.num_qubits
),
)
for layer, terms in noise_model_paulis.items()
}

tightened_shallow = tighten_with_speed_limit(
shallow_bounds, boxed_circuit, noise_model_paulis, observable
)

print(
f"{'bounds':30} {'overhead':>9} {'mitigated':>10} {'residual bias':>14}"
)
for label, bounds in (
("max_num_boxes=2", shallow_bounds),
("max_num_boxes=2 + speed limit", tightened_shallow),
("fully computed", full_bounds),
):
scales, cost, bias = compute_local_scales(
boxed_circuit, bounds, noise_rates, bias_tolerance=0.05
)
mitigated = sum(int(np.count_nonzero(scale)) for scale in scales.values())
total = sum(len(scale) for scale in scales.values())
print(
f"{label:30} {cost:9.4f} {f'{mitigated}/{total}':>10} {bias:>14.4f}"
)
bounds overhead mitigated residual bias
max_num_boxes=2 420.2823 617/792 0.0490
max_num_boxes=2 + speed limit 7.8511 226/792 0.0485
fully computed 5.4031 188/792 0.0494

Quando usarlo​

Applicalo ai limiti forward ogni volta che tali limiti non sono completamente convergiti (in sostanza, ogni volta che hai scambiato accuratezza con velocità usando una qualsiasi delle opzioni della guida alla configurazione). È economico rispetto al calcolo dei limiti, non può peggiorare nulla e recupera gran parte di ciò che un limite max_num_boxes o eigval_max_qubits aveva ceduto.

Sui limiti completamente calcolati non ha effetto, quindi non c'è alcun danno ad applicarlo incondizionatamente.

Due limitazioni:

  • Si applica solo ai limiti forward. I limiti backward riguardano l'effetto dell'errore sullo stato iniziale anziché sull'osservabile, quindi l'argomento non si estende.

  • L'osservabile deve essere un singolo termine di Pauli. Per un'osservabile con più termini, chiamalo una volta per ogni Pauli e combina tu i risultati. Se l'osservabile non è un singolo termine di Pauli, solleva un NotImplementedError.

Nel workflow standard va collocato tra il calcolo dei limiti forward e la loro fusione con i limiti backward, come mostra la guida rapida.