Cancellazione probabilistica degli errori con coni di luce ombreggiati
Stima di utilizzo: 10 minuti su un processore Heron (NOTA: questa è solo una stima. Il tuo tempo di esecuzione potrebbe variare.)
Risultati di apprendimento
-
Cos'è la cancellazione probabilistica degli errori (PEC), e perché il suo overhead di campionamento cresce esponenzialmente con il rumore totale che agisce sul circuito
-
Come i coni di luce ombreggiati (SLC) limitano il contributo di ciascun termine di rumore all'osservabile target, in modo da poter spendere il budget di mitigazione dove conta — e come, oltre alla riduzione dell'overhead a bias zero già fornita da un cono di luce puramente geometrico, l'ombreggiatura permette di scambiare un bias residuo limitato per un overhead di campionamento ancora più basso
-
Come apprendere il rumore di strato con
NoiseLearnerV3e iniettare anti-rumore attraversosamplomatice la primitivaExecutor -
Come combinare PEC e PEC+SLC con TREX e postselezione per stimare un valore di aspettazione sull'hardware
Prerequisiti
-
Il workflow dei pattern Qiskit
-
Utilizzare la primitiva Estimator per calcolare valori di aspettazione di un osservabile
-
Tecniche di mitigazione degli errori come il twirling di Pauli e TREX, trattate in Combinare le opzioni di mitigazione degli errori con la primitiva Estimator
Background
Questo tutorial dimostra come mitigare gli errori utilizzando l'addon shaded lightcone (SLC). Questo addon è un'evoluzione della tecnica di cancellazione probabilistica degli errori (PEC), in cui un utente apprende il rumore di livelli unici in un circuito e poi annulla il rumore applicando gate a singolo qubit e tecniche di post-elaborazione. Rispetto ad altri metodi, PEC offre limiti più robusti sul bias del risultato mitigato, ma tende a soffrire di un overhead maggiore in termini di tempo di QPU. Durante il PEC, per compensare l'attenuazione del valore di aspettazione dovuta al rumore, il risultato medio viene riscalato di un fattore , dove è il tasso di rumore appreso dell'errore di Pauli al livello nel circuito. Questo riscalamento aumenta la varianza di un fattore , e quindi moltiplica anche il numero di esecuzioni del circuito necessarie sulla QPU per , che chiamiamo costo di campionamento o overhead di campionamento. Poiché cresce esponenzialmente, il PEC è spesso limitato a circuiti poco profondi o con pochi qubit. Scopri di più sul PEC in Probabilistic error cancellation with sparse Pauli-Lindblad models on noisy quantum processors.
Se riusciamo a identificare gli errori che non necessitano di mitigazione, possiamo diminuire questo costo di campionamento in modo esponenziale. Un primo passo in questa direzione è l'implementazione della mitigazione degli errori a consapevolezza locale, che utilizza un "cono di luce" convenzionale rapidamente calcolabile per ridurre l'overhead del PEC limitando la sensibilità di un osservabile agli errori in tutto il circuito, estendendo la fattibilità del PEC a scale più grandi per alcuni problemi. Gli errori al di fuori di questo cono di luce non possono influenzare il risultato misurato e possono quindi essere esclusi dalla procedura di cancellazione degli errori. Questa esclusione diminuisce l'overhead di campionamento, in alcuni casi in modo sostanziale, senza introdurre bias aggiuntivo. In particolare, per misurare un osservabile locale di un circuito a profondità fissa, l'overhead di campionamento richiesto alla fine raggiunge un plateau quando si scala il numero di qubit nel circuito (vedi Fig. 2b in Locality and error mitigation of quantum circuits).
I shaded lightcone (SLC) vanno oltre, utilizzando simulazioni classiche per limitare più strettamente la sensibilità agli errori in tutto il circuito. Questo scambia parte del tempo di QPU con tempo di CPU e riduce l'overhead di campionamento necessario per rinormalizzare il bias. Invece di un taglio netto, a ogni potenziale errore nel circuito viene assegnata una "sfumatura" graduata che limita superiormente la suscettibilità del valore di aspettazione dell'osservabile a quell'errore. (Per i limiti propagati all'indietro introdotti di seguito, questa suscettibilità dipende anche dallo stato iniziale del circuito — lo stato tutto-zero nei workflow tipici, e in questo tutorial.) Questa caratterizzazione raffinata consente applicazioni più efficienti e mirate del PEC con varianza ridotta, dando all'utente la possibilità di regolare in modo controllabile il bias nella stima dell'osservabile. Consulta Lightcone shading for classically accelerated quantum error mitigation per maggiori dettagli.
Il nostro workflow per l'addon SLC sfrutta la libreria samplomatic insieme alle classi QuantumProgram ed Executor aggiunte a Qiskit Runtime in qiskit-ibm-runtime v0.47.0, consentendo agli utenti di avere un controllo più modulare delle impostazioni di esecuzione per la soppressione e la mitigazione degli errori pur mantenendo facilità d'uso. Leggi di più nella guida directed execution model.
Panoramica del workflow di mitigazione degli errori SLC
Per modellare il rumore della QPU, utilizziamo un modello di rumore sparso Pauli-Lindblad con tassi di errore di Pauli a uno e due qubit, generati localmente su ogni qubit e bordo del dispositivo. Con questa scelta, il workflow di mitigazione degli errori SLC presentato in questo tutorial è il seguente:
a. CPU — Limita l'impatto per errore dei errori di Pauli a uno e due qubit
-
Propagazione in avanti (limita l'effetto sull'osservabile). Propaga ogni errore fino alla fine del circuito e calcola il suo commutatore con l'osservabile.
-
Tronca i termini dell'operatore durante l'evoluzione per mantenere trattabile il calcolo.
-
Restringi ulteriormente questi limiti con una retropropagazione approssimata dell'osservabile basata sui limiti di velocità quantistici.
-
-
Propagazione all'indietro (limita l'effetto sullo stato iniziale). Propaga ogni errore fino all'inizio del circuito e calcola il suo commutatore con lo stato iniziale.
b. QPU — Apprendi i tassi di rumore. Usa NoiseLearnerV3 per stimare i tassi del modello di rumore Pauli-Lindblad.
c. CPU — Dai priorità alla mitigazione
-
Aggiorna i limiti uniti con i tassi di rumore appresi. Combina i limiti in avanti e all'indietro precedentemente calcolati e aggiornali con i tassi di rumore appresi.
-
Classifica le componenti di rumore da mitigare utilizzando i limiti calcolati e i tassi appresi. Dai priorità a ogni possibile errore di rumore in base al suo impatto stimato sul bias e al costo associato per correggerlo.
d. QPU — Inserisci l'anti-rumore ed esegui. Esegui il circuito di interesse con anti-rumore (rumore inverso) specificato usando le annotazioni Box.
e. CPU — Stima l'osservabile. Calcola il valore di aspettazione, applicando una post-selezione basata sulla misura per ridurre l'impatto del rumore non markoviano.
Panoramica dell'apprendimento del rumore
L'apprendimento del rumore è un passo comune in diversi metodi di mitigazione degli errori, eseguito dal noise learner; appare anche nel tutorial sull'amplificazione probabilistica degli errori. In NoiseLearnerV3, puoi identificare specificamente i livelli di rumore da apprendere come oggetti CircuitInstruction, in modo da poter calcolare i limiti SLC desiderati per ogni livello nel modo descritto sopra. Il modello Pauli-Lindblad appreso fornisce i coefficienti da utilizzare nella prioritizzazione PEC+SLC. Il modo in cui i gate vengono raccolti in livelli può essere determinato utilizzando le funzioni di convenienza generate_boxing_pass_manager e find_unique_box_instructions, e poi inseriti nella funzione di utilità SLC generate_noise_model_paulis, come descritto nel Passo 2 seguente.
| Parte 1 | Parte 2 | Parte 3 |
|---|---|---|
| Applica il Pauli twirl ai livelli di gate a due qubit | Ripeti coppie identità di livelli e apprendi il rumore | Deriva una fedeltà (errore per ogni canale di rumore) |
![]() | ![]() |
Panoramica della post-elaborazione
Dopo aver eseguito su hardware quantistico utilizzando il framework samplomatic ed Executor, convertiamo le nostre misurazioni di bitstring nel valore dell'osservabile desiderato. In questo tutorial, dimostriamo il workflow su un circuito Ising a specchio (costruito nel Passo 1 seguente), per il quale il valore di aspettazione ideale dell'osservabile misurato è 1 perché la seconda metà del circuito annulla la prima. Quando calcoliamo il valore dell'osservabile con la funzione executor_expectation_values, applichiamo alcune tecniche di post-elaborazione che riducono l'impatto del rumore. Queste includono la rimozione degli shot influenzati dal rumore non markoviano, la mitigazione dell'errore di readout, e la considerazione dei dettagli della nostra implementazione del PEC. I dettagli sono discussi nel Passo 4 seguente.
Requisiti
Prima di iniziare questo tutorial, assicurati di avere installato quanto segue:
-
Qiskit SDK v2.2 o successivo, con supporto per la visualizzazione
-
Qiskit Runtime v0.47 o successivo (
pip install qiskit-ibm-runtime) -
Shaded lightcones Qiskit addon v0.1 o successivo (
pip install qiskit-addon-slc) -
Qiskit addon utils v0.3 o successivo (
pip install qiskit-addon-utils) -
Samplomatic v0.13 o successivo (
pip install samplomatic)
Configurazione
Per prima cosa, importa i pacchetti e le funzioni necessari per eseguire questo notebook.
# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-addon-slc qiskit-addon-utils qiskit-ibm-runtime samplomatic
from multiprocessing import set_start_method
# Setting this value prevents itertools.starmap deadlock on UNIX systems
set_start_method("spawn")
# Needed to prevent PySCF from parallelizing internally (SLC only)
%set_env OMP_NUM_THREADS=1
env: OMP_NUM_THREADS=1
import numpy as np
from matplotlib import pyplot as plt
from qiskit import QuantumCircuit
from qiskit.quantum_info import SparsePauliOp
from qiskit.transpiler import generate_preset_pass_manager, PassManager
from qiskit_ibm_runtime import (
QiskitRuntimeService,
QuantumProgram,
Executor,
NoiseLearnerV3,
)
import samplomatic
from samplomatic.utils import find_unique_box_instructions
from samplomatic.transpiler import generate_boxing_pass_manager
from qiskit_addon_utils.exp_vals.measurement_bases import (
get_measurement_bases,
)
from qiskit_addon_utils.exp_vals.expectation_values import (
executor_expectation_values,
)
from qiskit_addon_utils.noise_management import (
gamma_from_noisy_boxes,
trex_factors,
)
from qiskit_addon_utils.noise_management.post_selection import PostSelector
from qiskit_addon_utils.noise_management.post_selection.transpiler.passes import (
AddPostSelectionMeasures,
AddSpectatorMeasures,
)
from qiskit_addon_slc.bounds import (
compute_backward_bounds,
compute_forward_bounds,
compute_local_scales,
merge_bounds,
tighten_with_speed_limit,
)
from qiskit_addon_slc.utils import (
generate_noise_model_paulis,
map_modifier_ref_to_ref,
)
from qiskit_addon_slc.visualization import draw_shaded_lightcone
Esempio di simulatore su piccola scala
Come altri metodi di mitigazione degli errori basati sull'apprendimento, il PEC con shaded lightcones mitiga il rumore fisico di un processore quantistico specifico, quindi dipende da servizi hardware senza un analogo significativo su un simulatore ideale:
-
NoiseLearnerV3caratterizza sperimentalmente il canale di rumore sparso Pauli-Lindblad su ogni livello unico a due qubit. Su un simulatore senza rumore non c'è rumore da annullare. -
Il primitivo
Executorcampiona i circuiti twirlati e con iniezione di anti-rumore generati dasamplomaticsu un backend.
Il calcolo dei limiti dello shaded lightcone è classico, ma è significativo solo relativamente ai tassi di rumore hardware appresi, che stabiliscono il budget di mitigazione e l'overhead di campionamento. Per questi motivi saltiamo l'esempio del simulatore su piccola scala e dimostriamo l'intero workflow PEC+SLC direttamente su hardware, con ogni passo del pattern Qiskit descritto in dettaglio di seguito.
Esempio hardware su larga scala
Eseguiamo il workflow PEC+SLC completo su un circuito Ising a specchio a 20 qubit eseguito su hardware IBM Quantum®, seguendo i quattro passi di un pattern Qiskit.
Passo 1: Mappa il problema
La nostra dimostrazione è impostata come segue:
-
Scegliamo di eseguire l'evoluzione temporale sotto un'Hamiltoniana a catena di Ising 1D. La catena di Ising 1D fornisce una struttura di circuito piacevolmente densa, il che è conveniente per mostrare implementazioni PEC.
-
Scegliamo di misurare l'osservabile , che contiene una componente e una componente . Questo è conveniente per evidenziare come i limiti SLC trattano diversamente diversi tipi di errori.
-
Vogliamo che il valore di aspettazione ideale di questo osservabile sia esattamente 1, in modo che la qualità delle stime mitigate sia facile da giudicare. Riflettere il circuito ci porta gran parte della strada: per ogni gate nella seconda metà del circuito c'è un gate inverso nella prima metà, in modo che il circuito ideale agisca come l'identità.
-
Infine, ottenere il valore di aspettazione ideale di 1 richiede che il circuito inizi — e quindi, grazie alla specchiatura, idealmente finisca anche — in un autostato +1 dell'osservabile. Poiché l'osservabile contiene una componente non-, e il primitivo
Executortiene conto della base di misura desiderata alla fine del circuito, anteponiamo i gate di preparazione della base corrispondenti all'inizio del circuito a specchio con una funzioneprepare_basis. Usiamo la funzioneget_measurement_basesper identificare quali gate sono necessari e dove posizionarli, tenendo traccia delle sottigliezze dell'indice dei qubit derivanti dalle convenzioni di annotazionebox, come discusso nella sezione sulla preparazione delle misurazioni delle basi canoniche.
# Width of the Ising chain (a free parameter of this demonstration)
num_qubits = 20
# The observable to measure: one X component (on qubit 6) and one Z component
# (on qubit 13), as motivated in "Step 1: Map the problem" above
target_obs_sparse = [("XZ", [6, 13], 1.0)]
observable = SparsePauliOp.from_sparse_list(
target_obs_sparse, num_qubits=num_qubits
)
# Determine the measurement basis each qubit requires for this observable.
# The reverser is used during post-processing in Step 4.
bases_virt, reverser_virt = get_measurement_bases(observable)
# Free parameters of the demonstration circuit: the number of Trotter steps
# sets the circuit depth, and rx_angle is the transverse-field rotation angle
# applied in each step (pi/4 makes the dynamics non-Clifford)
num_trotter_steps = 10
rx_angle = np.pi / 4
def construct_ising_circuit(
num_qubits: int,
num_trotter_steps: int,
rx_angle: float,
) -> QuantumCircuit:
circuit = QuantumCircuit(num_qubits)
for _ in range(num_trotter_steps):
circuit.rx(rx_angle, range(num_qubits))
for first_qubit in (1, 2):
for idx in range(first_qubit, num_qubits, 2):
# equivalent to Rzz(-pi/2):
circuit.sdg([idx - 1, idx])
circuit.cz(idx - 1, idx)
return circuit
def prepare_basis(
circuit: QuantumCircuit, basis: list[int]
) -> QuantumCircuit:
# basis is a list of integer values from 0 to 3. These map to the basis measurement as:
# 0 = I; 1 = Z; 2 = X; 3 = Y
assert len(basis) == circuit.num_qubits
out_circ = circuit.copy_empty_like()
for qb, bas in enumerate(basis):
if bas == 2:
out_circ.h(qb)
elif bas == 3:
out_circ.rx(-np.pi / 2, qb)
out_circ.barrier()
out_circ.compose(circuit, inplace=True)
return out_circ
def mirror_circuit(circuit: QuantumCircuit) -> QuantumCircuit:
mirror_circ = circuit.copy_empty_like()
mirror_circ.compose(circuit.inverse(), inplace=True)
mirror_circ.barrier()
mirror_circ.compose(circuit, inplace=True)
mirror_circ.measure_active()
return mirror_circ
# Instantiate the mirrored circuit and prepend the basis-preparation gates
circuit = construct_ising_circuit(num_qubits, num_trotter_steps, rx_angle)
mirrored_circuit = mirror_circuit(circuit)
mirrored_circuit = prepare_basis(mirrored_circuit, bases_virt[0])
mirrored_circuit.draw(
"mpl", fold=-1, scale=0.3, idle_wires=False, measure_arrows=False
)

Passo 2: Ottimizza
Ottimizziamo i dettagli associati al circuito da eseguire, all'osservabile da misurare e ai parametri di apprendimento del rumore. Come punto di partenza, selezioniamo il processore Heron meno occupato e lo istanziamo con i gate frazionari attivati. Questi gate frazionari consentono una maggiore sensibilità in alcuni dei nostri filtri di post-selezione.
# Initialize IBM Quantum Compute Service using your saved credentials
service = QiskitRuntimeService()
# Select the least busy Heron device. This tutorial targets Heron processors:
# the usage estimate and the non-Markovian-noise post-selection discussed in
# Step 4 are specific to this processor family.
backend = service.least_busy(
operational=True,
simulator=False,
filters=lambda b: b.processor_type["family"] == "Heron",
)
# Re-fetch with fractional gates enabled (least_busy does not forward this)
# Fractional gates are enabled so the non-Clifford Rx rotations are supported natively.
backend = service.backend(backend.name, use_fractional_gates=True)
print(f"Selected backend: {backend.name}")
Selected backend: ibm_kingston
Per prima cosa, tradurremo il nostro circuito in istruzioni ISA, come richiesto per l'esecuzione sulle nostre QPU. La scelta dei qubit fisici su cui eseguire ha un grande impatto sulla qualità dei risultati, perché i tempi di coerenza e le fedeltà dei gate variano attraverso un dispositivo e derivano nel tempo. Per un esperimento accurato, può valere la pena selezionare manualmente una catena di qubit informata da rapidi esperimenti di benchmarking (vedi, ad esempio, il tutorial Real-time benchmarking for qubit selection). Ai fini di questo tutorial, ci affidiamo al transpiler di Qiskit per selezionare un layout basato sulla mappa di accoppiamento del backend e sui suoi dati di calibrazione più recenti.
isa_pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuit = isa_pm.run(mirrored_circuit)
# The chain of physical qubits selected by the transpiler
layout = isa_circuit.layout.final_index_layout()
print(f"Selected layout: {layout}")
isa_observable = observable.apply_layout(
layout, num_qubits=isa_circuit.num_qubits
)
Selected layout: [71, 58, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 36, 21, 22, 23, 16, 3, 2]
wire_order = layout + [
q for q in range(isa_circuit.num_qubits) if q not in layout
]
isa_circuit.draw(
"mpl",
fold=-1,
scale=0.3,
idle_wires=False,
wire_order=wire_order,
measure_arrows=False,
)

Box del circuito
Per facilitare l'implementazione, utilizziamo il pass di transpilazione generate_boxing_pass_manager, che colloca le istruzioni del circuito in box annotati. Questi box indicano chiaramente dove, nel caso del PEC, dovrebbe essere iniettato l'anti-rumore nel circuito. Per dettagli sulle impostazioni, consulta la documentazione di Samplomatic.
Nota che il workflow SLC richiede l'uso di inject_noise_strategy="individual_modification" più avanti nel processo perché questo ci consente di identificare in modo univoco le annotazioni InjectNoise delle istruzioni BoxOp nel circuito. (I box senza un'annotazione InjectNoise non possono essere identificati in questo modo.)
I calcoli dei limiti SLC attualmente presuppongono che le annotazioni InjectNoise siano posizionate con site="after" (vedi qiskit-addon-slc#33). Poiché il valore predefinito di questa impostazione è cambiato nelle versioni recenti di samplomatic (vedi samplomatic#372), impostiamo esplicitamente inject_noise_site="after" di seguito.
La funzione find_unique_box_instructions itera attraverso il circuito boxato fornito e identifica quelli che hanno livelli a due qubit (2Q) o misurazioni unici, allo scopo di apprendimento del rumore e iniezione del rumore.
# Box circuit with Twirl and InjectNoise annotations
boxes_pm = generate_boxing_pass_manager(
twirling_strategy="active",
inject_noise_strategy="individual_modification",
inject_noise_site="after",
inject_noise_targets="gates",
measure_annotations="all",
)
boxed_circuit = boxes_pm.run(isa_circuit)
# Find the unique instructions (layers) from boxed circuit
unique_2q_instructions = find_unique_box_instructions(
boxed_circuit, normalize_annotations=None, undress_boxes=True
)
boxed_circuit.draw(
"mpl",
fold=-1,
scale=0.3,
idle_wires=False,
wire_order=wire_order,
measure_arrows=False,
)

Prepara le misurazioni delle basi canoniche
Nota che dobbiamo prestare particolare attenzione a tenere traccia dell'ordinamento dei qubit. Di seguito, introduciamo la nozione di canonical_qubits come mezzo per aggiornare opportunamente l'ordinamento dei qubit quando lo forniamo a Executor, come risultato del modo in cui l'ordine dei qubit viene catturato quando si boxano i circuiti e si trovano le istruzioni uniche. Consulta la documentazione Qubit ordering convention per i dettagli.
# Determine the canonical qubits order
meas_box = boxed_circuit.data[-1]
canonical_qubits = [
idx
for idx, qubit in enumerate(boxed_circuit.qubits)
if qubit in meas_box.qubits
]
# map canonical qubit to physical (isa) qubit
c_2_p = {c: p for c, p in enumerate(canonical_qubits)}
# map physical (isa) qubit to virtual qubit (index in original circuit)
p_2_v = {p: v for v, p in enumerate(layout)}
# compute map between virtual and canonical qubit indices.
c_2_v = {c: p_2_v[p] for c, p in c_2_p.items()}
assert len(c_2_v) == num_qubits
bases_canon = [
np.array([base_i[c_2_v[c]] for c in range(num_qubits)], dtype=np.uint8)
for base_i in bases_virt
]
Workflow per lo shading del lightcone, l'apprendimento del rumore e l'iniezione dell'anti-rumore
In questo tutorial, eseguiamo i calcoli dei limiti SLC prima che l'apprendimento del rumore sia completato, in modo che il Circuit da mitigare venga eseguito il più vicino possibile nel tempo al modello di rumore appreso. In linea di principio questo workflow può essere ulteriormente parallelizzato: un job di apprendimento del rumore può essere eseguito mentre, in parallelo, vengono stimati i limiti di rumore. Per un Circuit quantistico arbitrario il calcolo del limite di rumore può scalare con una dipendenza debolmente esponenziale, quindi parallelizzare il calcolo dei limiti (ad esempio, su molti core CPU) produce limiti più stretti per un dato budget di tempo di calcolo, e le esecuzioni sulla QPU e i calcoli dei limiti possono essi stessi essere parallelizzati per il workflow più efficiente.
Predici i Pauli del modello di rumore da apprendere
Prima di eseguire qualsiasi apprendimento del rumore su hardware, enumeriamo prima quali termini di errore di Pauli conterrà il nostro modello di rumore. Ricorda che modelliamo il rumore di ogni livello unico come un modello sparso Pauli-Lindblad con un tasso di errore per ogni Pauli di peso uno (, o ) su ogni qubit attivo, e uno per ogni Pauli di peso due su ogni coppia connessa di qubit attivi. La funzione generate_noise_model_paulis passa attraverso ogni livello boxato unico del circuito fornito e genera esattamente questo elenco di termini di Pauli, tenendo conto della connettività dei qubit del circuito.
In altre parole, questo passo predice la struttura del modello di rumore che NoiseLearnerV3 riempirà in seguito con i tassi appresi. Conoscere questa struttura in anticipo è ciò che ci consente di calcolare un limite in avanti e uno all'indietro per ogni potenziale errore prima ancora che l'apprendimento del rumore sia stato eseguito.
noise_model_paulis = generate_noise_model_paulis(
unique_2q_instructions, backend.coupling_map, boxed_circuit
)
noise_model_rates = {ref: None for ref in noise_model_paulis}
a. Calcola i limiti in avanti
La funzione compute_forward_bounds valuta le relazioni di commutazione tra i gate in ogni livello e i termini di Pauli generati sopra in termini di come gli errori di propagazione in avanti influenzano l'osservabile desiderato . Per i gate che commutano con i termini di Pauli, non viene fatto nulla. Per i gate Clifford, vengono spinti verso l'inizio del circuito. Per i gate non-Clifford, approssimiamo la loro influenza sugli osservabili target da prioritizzare successivamente per la cancellazione del rumore (dopo che tutti i limiti sono stati uniti). Questo limite si ottiene applicando prima la norma L2 (ovvero, la radice quadrata della somma dei quadrati dei coefficienti dei termini di Pauli rilevanti). Quando sono coinvolti troppi termini di qubit, si ripiega su un limite più lasco che usa la disuguaglianza triangolare.
Imposta i parametri di calcolo dei limiti
Le seguenti impostazioni controllano i calcoli classici dei limiti:
-
slc_atol: la tolleranza assoluta dei calcoli. I termini dell'operatore con coefficienti al di sotto di questa soglia sono considerati trascurabili e scartati. -
slc_eigval_max_qubits: il numero massimo di qubit per cui viene valutato il limite più stretto, basato sugli autovalori (norma L2). Gli operatori supportati su più qubit di questo ripiegano sul limite più lasco della disuguaglianza triangolare descritto sopra. -
slc_evolution_max_terms: il numero massimo di termini di Pauli mantenuti durante la propagazione degli operatori attraverso il circuito. Troncare i termini più piccoli mantiene il calcolo trattabile al costo di limiti leggermente più laschi. -
slc_num_processes: il numero di processi CPU su cui viene parallelizzato il calcolo dei limiti. -
slc_timeout: il budget di tempo (in secondi) per il calcolo dei limiti. Ai livelli non raggiunti entro questo budget viene assegnato il limite banale di 2.
In questo tutorial, il timeout viene raggiunto di proposito: manteniamo slc_timeout deliberatamente piccolo affinché il notebook venga eseguito rapidamente, e per dimostrare che raggiungere il timeout è sicuro. Le sfumature calcolate sono limiti superiori in ogni fase, quindi raggiungere il timeout rende i limiti solo più larghi (banali) per i livelli non raggiunti; non sottostima mai l'effetto di un errore. Questo appare nelle visualizzazioni sottostanti come i livelli più a sinistra riempiti interamente con il limite banale. Aumentare il timeout (o il numero di processi) restringe i limiti e riduce ulteriormente l'overhead di campionamento.
slc_atol = 1e-8
slc_eigval_max_qubits = 18
slc_evolution_max_terms = 1000
slc_num_processes = 8
slc_timeout = 60
forward_bounds = compute_forward_bounds(
boxed_circuit,
noise_model_paulis,
isa_observable,
evolution_max_terms=slc_evolution_max_terms,
eigval_max_qubits=slc_eigval_max_qubits,
atol=slc_atol,
num_processes=slc_num_processes,
timeout=slc_timeout,
)
Bounds computation timed out.
Visualizza l'SLC per l'ispezione manuale
Puoi interpretare il comportamento dei limiti sfumati esaminando come le misurazioni e i termini di Pauli interagiscono con gli errori locali. Questi pattern sono caratteristici di questo problema di evoluzione temporale dell'Hamiltoniana di Ising kicked e appaiono anche nell'articolo Lightcone Shading for Classically Accelerated Quantum Error Mitigation, con diverse caratteristiche rivelatrici:
-
Possiamo distinguere chiaramente i due coni derivanti dai due Pauli non-identità nell'osservabile.
-
Possiamo vedere che la misura X sul qubit 6 commuta con l'errore X nel livello più a destra.
-
Possiamo vedere che il Pauli Z sul qubit 13 commuta con l'errore Z nel livello più a destra.
-
Quando raggiungiamo il timeout specificato sopra, i livelli rimanenti a sinistra vengono riempiti interamente con limiti banali di due.
for p in "XYZ":
display(
draw_shaded_lightcone(
boxed_circuit,
forward_bounds,
noise_model_paulis,
pauli_filter=p,
scale=0.15,
fold=-1,
idle_wires=False,
wire_order=wire_order,
measure_arrows=False,
)
)



b. Restringi i limiti in avanti con i limiti di velocità quantistici
Successivamente restringiamo i limiti utilizzando la funzione tighten_with_speed_limit. La diffusione geometrica del lightcone dell'osservabile è già completamente presa in considerazione nei limiti in avanti calcolati sopra. Ciò che questa funzione aggiunge è un passo di auto-consistenza che sfrutta la velocità limitata con cui l'informazione può fluire attraverso il circuito — il lightcone cresce al massimo di un qubit per livello di gate a due qubit — per restringere i limiti precedentemente calcolati l'uno rispetto all'altro. L'informazione fluisce in entrambe le direzioni:
-
Dai limiti del commutatore ai limiti sull'osservabile retropropagato: se l'osservabile commuta con un errore su un qubit, allora non può avere molta componente o lì.
-
Dai limiti sull'osservabile retropropagato ai limiti del commutatore: se l'osservabile ha poca componente e su un qubit, allora deve quasi commutare con lì.
forward_bounds_tighter = tighten_with_speed_limit(
forward_bounds, boxed_circuit, noise_model_paulis, isa_observable
)
Visualizza l'SLC per l'ispezione manuale
In linea di principio, questo restringimento fornisce una transizione più fluida tra i limiti calcolati e i limiti banali assegnati ai livelli oltre il timeout. In questo esempio l'effetto è appena visibile, perché i lightcone si sono già diffusi fino ai bordi del circuito quando viene raggiunto il timeout.
for p in "XYZ":
display(
draw_shaded_lightcone(
boxed_circuit,
forward_bounds_tighter,
noise_model_paulis,
pauli_filter=p,
scale=0.15,
fold=-1,
idle_wires=False,
wire_order=wire_order,
measure_arrows=False,
)
)



c. Calcola i limiti all'indietro
Questa parte della predizione del rumore valuta come un errore in un particolare livello possa influenzare lo stato di input . La funzione compute_backward_bounds prima inverte il circuito e rimuove i gate di misura, e poi procede con un'analisi simile a quella fatta per i calcoli dei limiti in avanti.
backward_bounds = compute_backward_bounds(
boxed_circuit,
noise_model_paulis,
evolution_max_terms=slc_evolution_max_terms,
num_processes=slc_num_processes,
timeout=slc_timeout,
)
Visualizza l'SLC per l'ispezione manuale
Dal calcolo dei limiti all'indietro, possiamo vedere come la struttura dello stato iniziale governa il comportamento iniziale della propagazione degli errori:
-
Possiamo vedere chiaramente come gli errori Z commutano inizialmente con lo stato iniziale |0⟩.
-
Solo sul qubit 6, dove inizializziamo l'autostato +1 della base X, un errore Z non riesce a commutare, mentre un errore X commuta.
for p in "XYZ":
display(
draw_shaded_lightcone(
boxed_circuit,
backward_bounds,
noise_model_paulis,
pauli_filter=p,
scale=0.15,
fold=-1,
idle_wires=False,
wire_order=wire_order,
measure_arrows=False,
)
)



Anteprima dei limiti uniti senza tassi di rumore appresi
La funzione merged_bounds determina il punto nel circuito in cui passare dai limiti all'indietro ai limiti in avanti minimizza il bias totale stimato sull'osservabile desiderato. Questo bias viene calcolato come la somma dei contributi dei limiti all'indietro per tutte le posizioni di rumore prima di quel punto, più i contributi dei limiti in avanti per tutte le posizioni di rumore dopo di esso. Attualmente, questo viene fatto in modo uniforme per tutti i qubit.
Il punto ottimale in cui passare dai limiti all'indietro a quelli in avanti dipende dai tassi di rumore appresi. In questa fase non abbiamo ancora appreso alcun tasso: noise_model_rates è stato inizializzato a None per ogni livello, cosa che merge_bounds tratta come tassi di rumore uniformi. I limiti uniti mostrati qui sono quindi solo un'anteprima; li ricalcoliamo con i tassi effettivamente appresi nel Passo 3.
merged_bounds = merge_bounds(
boxed_circuit,
forward_bounds_tighter,
backward_bounds,
noise_model_rates,
)
Missing noise rates. Partitioning backward/forward commutator bounds by assuming uniform error rates.
Optimal spacetime partitioning not implemented!Just partitioning list of noisy boxes.
Visualizza l'SLC per l'ispezione manuale
Dopo aver unito i limiti all'indietro e i limiti in avanti restretti, il comportamento degli SLC combinati diventa chiaro:
-
La funzione sopra ci dice che viene scelta una partizione in corrispondenza della quale avviene il passaggio dai limiti all'indietro ai limiti in avanti restretti.
-
Possiamo vedere di seguito che gli SLC ora contengono limiti all'indietro parziali e limiti in avanti restretti parziali.
for p in "XYZ":
display(
draw_shaded_lightcone(
boxed_circuit,
merged_bounds,
noise_model_paulis,
pauli_filter=p,
scale=0.15,
fold=-1,
idle_wires=False,
wire_order=wire_order,
measure_arrows=False,
)
)



Passo 3: Esegui
In questa sezione iniziamo la parte del workflow che utilizza un dispositivo quantistico reale. Per questo metodo di mitigazione degli errori basato sull'apprendimento, ci sono due passi:
-
Apprendi il rumore usando
NoiseLearnerV3. -
Esegui un circuito di mitigazione degli errori con il framework
samplomaticedExecutor.
Con gli errori limitati dal nostro circuito quantistico, apprendiamo i tassi di rumore associati per dare priorità al nostro budget di errore, determinare l'overhead di campionamento ed eseguire su una QPU.
a. Apprendi i tassi di rumore
Il noise learner caratterizza i processi di rumore che influenzano i gate in uno o più circuiti di interesse, basandosi sul modello di rumore sparso Pauli-Lindblad. Il metodo run() avvia un job di apprendimento del rumore per i livelli unici a due qubit forniti, utilizzando le opzioni specificate nella configurazione del noise learner. Queste opzioni controllano la strategia di Pauli-twirling, il numero di randomizzazioni e shot, le profondità di apprendimento e la post-selezione.
Scegliamo anche le profondità di apprendimento deliberatamente. Una scoperta pratica per la mitigazione basata sull'apprendimento con samplomatic è che è altamente vantaggioso che la profondità di apprendimento più profonda corrisponda alla profondità del circuito che si desidera mitigare. Poiché i layer_pair_depths di NLv3 sono misurati in coppie di livelli (un livello più il suo inverso), impostiamo il valore più profondo alla metà della profondità del livello a due qubit del circuito.
post_selection_enabled = True
# Match the deepest noise-learning depth to the depth of the circuit being
# mitigated. NLv3 ``layer_pair_depths`` are measured in layer pairs (a layer
# plus its inverse), so the deepest value is half the circuit's two-qubit-layer
# depth. Learning to this depth markedly improves the quality of the mitigation.
#
# We measure the two-qubit-layer depth on the pre-boxed ISA circuit: after
# boxing, every two-qubit gate is hidden inside a full-width ``BoxOp``, so a
# ``num_qubits == 2`` filter on ``boxed_circuit`` matches nothing (and
# ``QuantumCircuit.depth`` does not recurse into boxes).
depth_2q = isa_circuit.depth(lambda instr: instr.operation.num_qubits == 2)
max_layer_pair_depth = depth_2q // 2 # dividing by 2 since we want pairs
# Use a fixed schedule of learning depths, but drop any that exceed the circuit's
# depth and always cap the deepest value at ``max_layer_pair_depth`` so we never
# learn deeper than the circuit being mitigated.
candidate_depths = [1, 2, 4, 8, 12, 16, 24, 32, 40, 48]
layer_pair_depths = sorted(
{d for d in candidate_depths if d < max_layer_pair_depth}
| {max_layer_pair_depth}
)
noise_learner_options = {
"num_randomizations": 64,
"shots_per_randomization": 128,
"layer_pair_depths": layer_pair_depths,
"post_selection": {
"enable": post_selection_enabled,
"strategy": "edge",
"x_pulse_type": "rx",
},
"environment": {"job_tags": ["TUT_SLC"]},
}
noise_learner = NoiseLearnerV3(backend, noise_learner_options)
noise_learner_job = noise_learner.run(unique_2q_instructions)
noise_learner_result = noise_learner_job.result()
if post_selection_enabled:
print(
"Minimum fraction of shots kept for noise learning experiments: ",
end="",
)
print(
f"{min([min(d.values()) for d in [nlr.metadata['post_selection']['fraction_kept'] for nlr in noise_learner_result[:2]]]):.2f}"
)
Minimum fraction of shots kept for noise learning experiments: 0.71
# Get a dict mapping each InjectNoise.ref to its learned PauliLindbladMap
refs_2_plm = noise_learner_result.to_dict(
unique_2q_instructions, require_refs=False
)
b.i. Aggiorna i limiti uniti con i tassi di rumore effettivamente appresi
Ora che il modello di rumore specifico è stato appreso, possiamo applicare i tassi di rumore appresi ai limiti di rumore previsti e ottenere una determinazione finale di quali limiti hanno il maggiore impatto sulla minimizzazione del bias.
merged_bounds = merge_bounds(
boxed_circuit,
forward_bounds_tighter,
backward_bounds,
refs_2_plm,
)
Optimal spacetime partitioning not implemented!Just partitioning list of noisy boxes.
b.ii. Calcola i local_scales per l'esecuzione hardware
compute_local_scales esamina ogni possibile errore di rumore nel circuito e stima quanto quell'errore potrebbe distorcere la misurazione finale, così come quanto sarebbe costoso correggerlo. Successivamente classifica gli errori in base a quanto valga la pena mitigarli e seleziona il sottoinsieme che riduce il bias il più possibile, rimanendo entro il budget di costo di campionamento consentito (o raggiungendo un'accuratezza desiderata). Il risultato è un insieme di fattori di scala che indicano quali errori verranno mitigati attivamente e quali verranno lasciati non mitigati (local_scales), insieme all'overhead totale del costo di campionamento previsto (sampling_costs) e al bias residuo rimanente (residual_bias_bound).
La capacità di controllare il bias residuo desiderato è una caratteristica critica dell'implementazione SLC del PEC. Mentre nell'implementazione originale, l'overhead di campionamento puntava sempre a un bias zero, possiamo regolare l'overhead di campionamento richiesto con un compromesso nel bias residuo atteso. Questo aiuta l'utente a rimanere entro un budget di campionamento fisso, il che può essere particolarmente utile quando si prototipa inizialmente un workflow.
id_map = map_modifier_ref_to_ref(boxed_circuit)
summed_rates = 0.0
for box_id, noise_id in id_map.items():
learned_plm = refs_2_plm[noise_id]
summed_rates += np.sum(learned_plm.rates)
# print(f"{box_id}:\tgamma = {np.exp(2 * summed_rates):1.6e}\tsampling cost = {np.exp(4 * summed_rates):1.6e}")
total_gamma = np.exp(2 * summed_rates)
print(
f"Full PEC gamma={total_gamma}, sampling cost (gamma^2) = {total_gamma**2}"
)
Full PEC gamma=12.750876317157042, sampling cost (gamma^2) = 162.58484685543633
biases = []
costs = []
for bias in [0.0] + np.arange(0.001, 0.102, 0.01).tolist():
_, cost_, bias_ = compute_local_scales(
boxed_circuit,
merged_bounds,
refs_2_plm,
sampling_cost_budget=np.inf,
bias_tolerance=bias,
)
biases.append(bias_)
costs.append(cost_)
Bilancia l'overhead di campionamento rispetto al bias residuo
xticks = np.arange(0, 11)
fig, ax = plt.subplots()
ax.scatter(
[0], [total_gamma**2], marker="D", c="tab:orange", label="full PEC"
)
ax.plot(
100 * np.array(biases),
np.array(costs),
"o-",
c="tab:blue",
label="PEC+SLC",
)
ax.set_yscale("log")
ax.set_xticks(xticks, [f"{x:.1f}" for x in xticks])
ax.set_xlabel("Remaining bias [%]")
ax.set_ylabel(r"Sampling overhead, $\gamma^2$")
ax.grid()
ax.legend()
fig.suptitle("PEC sampling overhead reduction due to SLC")
Text(0.5, 0.98, 'PEC sampling overhead reduction due to SLC')
chosen_bias_thres = 0.1
local_scales, sampling_cost, residual_bias_bound = compute_local_scales(
boxed_circuit,
merged_bounds,
refs_2_plm,
sampling_cost_budget=np.inf,
bias_tolerance=chosen_bias_thres,
)
print(
f"PEC+SLC sampling cost (gamma^2) = {sampling_cost} "
f"w/ remaining bias = {100 * residual_bias_bound:.1f}%"
)
PEC+SLC sampling cost (gamma^2) = 48.13646509622307 w/ remaining bias = 10.0%
c. Esegui il circuito di interesse con anti-rumore
c.i. Prepara il circuito modello utilizzando samplex
Il samplex è un output del metodo build di Samplomatic, che codifica tutte le informazioni necessarie per generare parametri randomizzati per template_circuit. Questi vengono poi utilizzati per impostare gli oggetti QuantumProgram, che a loro volta vengono eseguiti su una QPU con il primitivo Executor. Ogni QuantumProgram può contenere diversi elementi, che puoi immaginare come una coppia di template e samplex.
Consulta il tutorial Hello samplomatic per i dettagli.
# Build template circuit and samplex for later use with the "Executor"
template_circuit, samplex = samplomatic.build(boxed_circuit)
# Set up postselection if it's been enabled
if post_selection_enabled:
# Set up post selection PM (to add PS instructions)
post_selection_pm = PassManager(
[
AddSpectatorMeasures(backend.coupling_map),
AddPostSelectionMeasures(x_pulse_type="rx"),
]
)
final_template_circuit = post_selection_pm.run(template_circuit)
else:
final_template_circuit = template_circuit
c.ii. Imposta il QuantumProgram
num_randomizations = 4096
shots_per_randomization = 64
chunk_size = 256
# Set up QuantumProgram
program = QuantumProgram(shots=shots_per_randomization, noise_maps=refs_2_plm)
# no EM
# Collect up a dict of the other arguments that need to be bound to samplex_inputs
samplex_inputs = {
f"noise_scales.{ref}": float(0) for ref in local_scales.keys()
}
samplex_inputs |= {"basis_changes": {"basis0": bases_canon[0]}}
# Convert samplex_inputs into a dict to pass to QuantumProgram
samplex_arguments = (
samplex.inputs().bind(**samplex_inputs).make_broadcastable()
)
program.append_samplex_item(
circuit=final_template_circuit,
samplex=samplex,
samplex_arguments=samplex_arguments,
shape=(num_randomizations,),
chunk_size=chunk_size,
)
# plain PEC
# Collect a dict of the other arguments that need to be bound to samplex_inputs
samplex_inputs = {
f"noise_scales.{ref}": float(-1) for ref in local_scales.keys()
}
samplex_inputs |= {"basis_changes": {"basis0": bases_canon[0]}}
# Convert samplex_inputs into a dict to pass to QuantumProgram
samplex_arguments = (
samplex.inputs().bind(**samplex_inputs).make_broadcastable()
)
program.append_samplex_item(
circuit=final_template_circuit,
samplex=samplex,
samplex_arguments=samplex_arguments,
shape=(num_randomizations,),
chunk_size=chunk_size,
)
# PEC+SLC
# Collect a dict of the other arguments that need to be bound to samplex_inputs
samplex_inputs = {
f"noise_scales.{ref}": float(-1) for ref in local_scales.keys()
}
samplex_inputs |= {"basis_changes": {"basis0": bases_canon[0]}}
samplex_inputs |= {"local_scales": local_scales}
# Convert samplex_inputs into a dict to pass to QuantumProgram
samplex_arguments = (
samplex.inputs().bind(**samplex_inputs).make_broadcastable()
)
program.append_samplex_item(
circuit=final_template_circuit,
samplex=samplex,
samplex_arguments=samplex_arguments,
shape=(num_randomizations,),
chunk_size=chunk_size,
)
c.iii. Esegui il programma con il primitivo Executor
executor = Executor(backend)
job_exec = executor.run(program)
results_exec = job_exec.result()
Passo 4: Post-elaborazione
Mentre calcoliamo il valore di aspettazione finale di interesse utilizzando executor_expectation_values, implementiamo alcune tecniche di post-elaborazione per contribuire a garantire di ottenere risultati della migliore qualità possibile. Per prima cosa, applichiamo la nostra estinzione dell'errore di readout twirlato (TREX), che tiene conto di eventuali errori che si verificano durante il processo di readout. Poi, correggiamo gli errori dovuti al rumore non markoviano sui nostri backend Heron utilizzando un metodo di post-selezione. Questo metodo misura i qubit attivi e spettatori, poi applica una rotazione lenta a ogni qubit, e poi misura di nuovo. Nei casi in cui le due misurazioni non confermano un qubit invertito come previsto, questi shot vengono scartati applicando una mask dal PostSelector. Nel calcolo della maschera, si può impostare una strategia specifica per filtrare in base a nodi a singolo qubit o bordi spettatori vicini, il che può influenzare sia il numero di shot filtrati sia la qualità dei risultati.
measurement_noise_map = noise_learner_result[2].to_pauli_lindblad_map()
trex_scale_factors = trex_factors(measurement_noise_map, reverser_virt)
post_selection_strategy = "node"
def post_process_conv(datum, steps=16, gamma=None, ps=False, trex=False):
meas = datum["meas"]
flips = datum["measurement_flips.meas"]
signs = datum.get("pauli_signs", None)
meas_basis_axis = None
avg_axis = 0
mask = None
if ps and post_selection_enabled:
# Post-select the results
post_selector = PostSelector.from_circuit(
circuit=final_template_circuit, coupling_map=backend.coupling_map
)
# Compute the ps mask for filtering results
mask = post_selector.compute_mask(
datum, strategy=post_selection_strategy
)
# Compute fraction of shots kept from post selection
total_num_shots = num_randomizations * shots_per_randomization
ps_ratio = np.sum(mask) * 100 / total_num_shots / len(bases_canon)
print(
f"With {post_selection_strategy}-based post selection ({ps_ratio:.1f}% of shots kept):"
)
results = []
for i in range(steps, num_randomizations + 1, steps):
# Compute mitigated expvals w/out post-selection
res = executor_expectation_values(
meas[:i],
reverser_virt,
meas_basis_axis,
avg_axis=avg_axis,
measurement_flips=flips[:i],
pauli_signs=signs[:i] if signs is not None else None,
postselect_mask=mask[:i] if mask is not None else None,
rescale_factors=trex_scale_factors if trex else None,
gamma_factor=gamma,
)
results.append(res[0])
return results
gamma_pec = gamma_from_noisy_boxes(refs_2_plm, id_map)
gamma_slc = gamma_from_noisy_boxes(refs_2_plm, id_map, local_scales)
steps = 16
results = {}
for label, result_idx, gamma, use_ps, use_trex in [
("PEC", 1, gamma_pec, True, True),
("PEC+SLC", 2, gamma_slc, True, True),
("Unmitigated", 0, None, False, False),
]:
res = post_process_conv(
results_exec[result_idx],
steps=steps,
gamma=gamma,
ps=use_ps,
trex=use_trex,
)
results[label] = res
With node-based post selection (24.1% of shots kept):
With node-based post selection (24.2% of shots kept):
Dall'esame dei risultati sperimentali, possiamo confrontare direttamente il comportamento dei diversi approcci: PEC, PEC combinato con SLC, e la baseline non mitigata. Alcuni dettagli specifici da evidenziare:
-
Il risultato non mitigato si trova ben al di fuori della fascia di bias del 10% (vicino a 0,35) e non è influenzato dal numero di randomizzazioni.
-
Su questo dispositivo, il PEC completo comporta un overhead di campionamento di . Dopo ampie oscillazioni iniziali, la stima del PEC semplice recupera il segnale e si assesta intorno a 1,1, superando leggermente il valore esatto. Nota che la fascia di bias del 10% si applica solo allo stimatore PEC+SLC: il PEC semplice è in linea di principio non distorto, quindi questa deviazione residua non è coperta da una garanzia di bias ma riflette invece fluttuazioni statistiche e imperfezioni nel modello di rumore appreso, il che è ragionevole per questa dimensione del problema.
-
SLC riduce ulteriormente l'overhead di circa ~3,4 volte (a , per un limite di bias residuo di circa il 10%). Anche PEC+SLC fluttua fortemente a poche randomizzazioni, raggiungendo inizialmente persino la regione non fisica, ma la sua stima converge costantemente verso il valore esatto, assestandosi vicino a 1,0 ben all'interno della fascia. Eguaglia o supera l'accuratezza del PEC semplice a circa un terzo del costo di campionamento, dimostrando il vantaggio dello shading del lightcone.
-
Le barre di errore di entrambe le stime mitigate si contraggono all'accumularsi delle randomizzazioni; a un numero fisso di randomizzazioni, lo stimatore PEC+SLC ha la varianza statistica minore, coerentemente con il suo minore.
Nota che i tassi di rumore appresi (e quindi gli overhead e le stime mitigate) dipendono dal backend e dalla sua calibrazione al momento dell'esecuzione, quindi la riesecuzione di questo notebook può produrre valori quantitativamente diversi.
fig, ax = plt.subplots(1, 1, figsize=(12, 6))
ax.axhline(1.0, color="black", label="Exact")
ax.fill_between(
[-50, 4100], -10, 0, color="grey", alpha=0.25, label="Unphysical"
)
ax.fill_between([-50, 4100], 1, 10, color="grey", alpha=0.25)
ax.fill_between(
[-50, 4100], 0.9, 1.1, color="red", alpha=0.25, label="10% bias"
)
for label, res in results.items():
ax.errorbar(
list(range(steps, num_randomizations + 1, steps)),
[r[0] for r in res],
yerr=[r[1] for r in res],
alpha=0.75,
marker="o",
linestyle="",
markerfacecolor="none",
label=label,
)
ax.set_ylabel(r"$\langle X_{6}Z_{13}\rangle$")
ax.set_xlabel("# randomizations")
ax.grid()
ax.legend(ncols=2)
ax.set_ylim([-0.1, 2.0])
ax.set_xlim([-50, 4100])
(-50.0, 4100.0)

Passi successivi
Se hai trovato interessante questo lavoro, potrebbero interessarti i seguenti materiali:
- Combina opzioni di mitigazione degli errori con la primitiva Estimator
- Mitigazione degli errori su scala di utilità con amplificazione probabilistica degli errori
- La documentazione di
qiskit-addon-slcesamplomatic - Lightcone shading per la mitigazione degli errori quantistici accelerata classicamente

