Anularea probabilistică a erorilor cu conuri de lumină umbrite
Estimare de utilizare: 10 minute pe un procesor Heron (NOTĂ: Aceasta este doar o estimare. Timpul tău de execuție poate varia.)
Rezultate de învățare
-
Ce este anularea probabilistică a erorilor (PEC) și de ce costul său de eșantionare crește exponențial cu zgomotul total ce acționează asupra circuitului
-
Cum conurile de lumină umbrite (SLC) limitează contribuția fiecărui termen de zgomot la observabila țintă, astfel încât să poți cheltui bugetul de atenuare acolo unde contează — și cum, dincolo de reducerea costului fără bias oferită deja de un con de lumină pur geometric, umbrirea îți permite să schimbi un bias rezidual limitat pentru un cost de eșantionare și mai mic
-
Cum să înveți zgomotul de strat cu
NoiseLearnerV3și să injectezi anti-zgomot prinsamplomaticși primitivaExecutor -
Cum să combini PEC și PEC+SLC cu TREX și postselecție pentru a estima o valoare de așteptare pe hardware
Cerințe preliminare
-
Fluxul de lucru Qiskit patterns
-
Utilizarea primitivei Estimator pentru a calcula valori de așteptare ale unei observabile
-
Tehnici de atenuare a erorilor precum twirling-ul Pauli și TREX, acoperite în Combinarea opțiunilor de atenuare a erorilor cu primitiva Estimator
Context
Acest tutorial demonstrează cum să atenuezi erorile folosind addon-ul de conuri de lumină umbrite (SLC). Acest addon este o evoluție a tehnicii de anulare probabilistică a erorilor (PEC), în care un utilizator învață zgomotul straturilor unice dintr-un circuit și apoi anulează zgomotul aplicând porți cu un singur qubit și tehnici de post-procesare. Comparativ cu alte metode, PEC oferă limite mai robuste asupra biasului rezultatului atenuat, dar tinde să sufere de un cost mai mare în ceea ce privește timpul de QPU. În timpul PEC, pentru a compensa atenuarea valorii de așteptare cauzată de zgomot, rezultatul mediu este rescalat printr-un factor , unde este rata de zgomot învățată a erorii Pauli la stratul din circuit. Această rescalare crește varianța cu un factor și, prin urmare, multiplică și numărul de execuții de circuit necesare pe QPU cu , ceea ce numim cost de eșantionare sau overhead de eșantionare. Deoarece crește exponențial, PEC este adesea limitat la circuite puțin adânci sau cu puțini qubiți. Află mai multe despre PEC în Anularea probabilistică a erorilor cu modele Pauli-Lindblad rare pe procesoare cuantice zgomotoase.
Dacă putem identifica erori care nu trebuie atenuate, putem reduce exponențial acest cost de eșantionare. Un prim pas în această direcție este implementarea atenuării erorilor conștiente de localitate, care folosește un "con de lumină" convențional, calculabil rapid, pentru a reduce costul PEC prin limitarea sensibilității unei observabile la erori pe parcursul circuitului, extinzând fezabilitatea PEC la scări mai mari pentru unele probleme. Erorile din afara acestui con de lumină nu pot afecta rezultatul măsurat și, prin urmare, pot fi excluse din procedura de anulare a erorilor. Această excludere reduce costul de eșantionare, în unele cazuri substanțial, fără a introduce bias suplimentar. În particular, pentru măsurarea unei observabile locale a unui circuit cu adâncime fixă, costul de eșantionare necesar ajunge în cele din urmă la un platou pe măsură ce scalăm numărul de qubiți din circuit (vezi Fig. 2b în Localitate și atenuarea erorilor circuitelor cuantice).
Conurile de lumină umbrite (SLC) merg mai departe, folosind simulări clasice pentru a limita mai strict sensibilitatea la erori pe parcursul circuitului. Aceasta schimbă o parte din timpul de QPU cu timp de CPU și reduce costul de eșantionare necesar pentru a renormaliza biasul. În loc de o limită dură, fiecare eroare potențială din circuit primește o "umbră" graduală care limitează superior sensibilitatea valorii de așteptare a observabilei la acea eroare. (Pentru limitele propagate înapoi introduse mai jos, această sensibilitate depinde și de starea inițială a circuitului — starea totul-zero în fluxurile de lucru tipice și în acest tutorial.) Această caracterizare rafinată permite aplicații mai eficiente și țintite ale PEC cu varianță redusă, oferind în același timp utilizatorului posibilitatea de a regla controlabil biasul în estimarea observabilei. Vezi Umbrirea conurilor de lumină pentru atenuarea erorilor cuantice accelerată clasic pentru mai multe detalii.
Fluxul nostru de lucru pentru addon-ul SLC valorifică biblioteca samplomatic împreună cu clasele QuantumProgram și Executor adăugate la Qiskit Runtime în qiskit-ibm-runtime v0.47.0, permițând utilizatorilor un control mai modular al setărilor de execuție pentru suprimarea și atenuarea erorilor, păstrând în același timp ușurința de utilizare. Citește mai multe în ghidul modelul de execuție dirijată.
Fluxul de lucru de atenuare a erorilor SLC pe scurt
Pentru modelarea zgomotului QPU-ului, folosim un model de zgomot Pauli-Lindblad rar cu rate de eroare Pauli pentru unul și doi qubiți, generate local pe fiecare qubit și muchie a dispozitivului. Cu această alegere, fluxul de lucru de atenuare a erorilor SLC prezentat în acest tutorial este următorul:
a. CPU — Limitarea impactului per eroare al erorilor Pauli pentru unul și doi qubiți
-
Propagare înainte (limitarea efectului asupra observabilei). Propagă fiecare eroare până la sfârșitul circuitului și calculează comutatorul său cu observabila.
-
Trunchiază termenii operatorului în timpul evoluției pentru a menține calculul tratabil.
-
Strânge suplimentar aceste limite printr-o propagare inversă slabă a observabilei bazată pe limite de viteză cuantică.
-
-
Propagare înapoi (limitarea efectului asupra stării inițiale). Propagă fiecare eroare până la începutul circuitului și calculează comutatorul său cu starea inițială.
b. QPU — Învață ratele de zgomot. Folosește NoiseLearnerV3 pentru a estima ratele modelului de zgomot Pauli-Lindblad.
c. CPU — Prioritizează atenuarea
-
Actualizează limitele combinate cu ratele de zgomot învățate. Combină limitele înainte și înapoi calculate anterior și le actualizează cu ratele de zgomot învățate.
-
Clasifică componentele de zgomot de atenuat folosind limitele calculate și ratele învățate. Prioritizează fiecare eroare de zgomot posibilă în funcție de impactul estimat asupra biasului și de costul asociat corectării.
d. QPU — Inserează anti-zgomot și rulează. Execută circuitul de interes cu anti-zgomot (zgomot invers) specificat folosind adnotări Box.
e. CPU — Estimează observabila. Calculează valoarea de așteptare, aplicând postselecție bazată pe măsurători pentru a reduce impactul zgomotului non-Markovian.
Prezentare generală a învățării zgomotului
Învățarea zgomotului este un pas comun în mai multe metode de atenuare a erorilor, realizat de noise learner; apare de asemenea în tutorialul de amplificare probabilistică a erorilor. În NoiseLearnerV3, poți identifica în mod specific straturile de zgomot care trebuie învățate ca obiecte CircuitInstruction, astfel încât să poți calcula limitele de zgomot SLC dorite pentru fiecare strat în modul descris mai sus. Modelul Pauli-Lindblad învățat furnizează coeficienți de utilizat în prioritizarea PEC+SLC. Modul în care porțile sunt colectate în straturi poate fi determinat folosind funcțiile convenabile generate_boxing_pass_manager și find_unique_box_instructions, apoi introduse în funcția utilitară SLC generate_noise_model_paulis, așa cum este descris în Pasul 2 de mai jos.
| Partea 1 | Partea 2 | Partea 3 |
|---|---|---|
| Twirling Pauli al straturilor de porți cu doi qubiți | Repetă perechi de straturi identitate și învață zgomotul | Derivă o fidelitate (eroare pentru fiecare canal de zgomot) |
![]() | ![]() |
Prezentare generală a post-procesării
După execuția pe hardware cuantic folosind framework-ul samplomatic și Executor, convertim măsurătorile noastre de șiruri de biți în valoarea observabilei dorite. În acest tutorial, demonstrăm fluxul de lucru pe un circuit Ising oglindit (construit în Pasul 1 de mai jos), pentru care valoarea de așteptare ideală a observabilei măsurate este 1, deoarece a doua jumătate a circuitului anulează prima. Când calculăm valoarea observabilei cu funcția executor_expectation_values, aplicăm câteva tehnici de post-procesare care reduc impactul zgomotului. Acestea includ eliminarea shot-urilor afectate de zgomot non-Markovian, atenuarea erorii de citire și luarea în considerare a detaliilor implementării noastre PEC. Detaliile sunt discutate în Pasul 4 de mai jos.
Cerințe
Înainte de a începe acest tutorial, asigură-te că ai instalate următoarele:
-
Qiskit SDK v2.2 sau o versiune ulterioară, cu suport pentru vizualizare
-
Qiskit Runtime v0.47 sau o versiune ulterioară (
pip install qiskit-ibm-runtime) -
Shaded lightcones Qiskit addon v0.1 sau o versiune ulterioară (
pip install qiskit-addon-slc) -
Qiskit addon utils v0.3 sau o versiune ulterioară (
pip install qiskit-addon-utils) -
Samplomatic v0.13 sau o versiune ulterioară (
pip install samplomatic)
Configurare
Mai întâi, importă pachetele și funcțiile necesare pentru a rula acest 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
Exemplu de simulator la scară mică
La fel ca alte metode de atenuare a erorilor bazate pe învățare, PEC cu shaded lightcones atenuează zgomotul fizic al unui procesor cuantic specific, deci depinde de servicii hardware fără un analog semnificativ pe un simulator ideal:
-
NoiseLearnerV3caracterizează experimental canalul de zgomot sparse Pauli-Lindblad pe fiecare strat unic de doi qubiți. Pe un simulator fără zgomot nu există niciun zgomot de anulat. -
Primitiva
Executoreșantionează circuitele twirled, cu anti-zgomot injectat, generate desamplomaticpe un backend.
Calculul limitelor shaded-lightcone este clasic, dar este relevant doar în raport cu ratele de zgomot hardware învățate, care stabilesc bugetul de atenuare și overhead-ul de eșantionare. Din aceste motive, sărim peste exemplul de simulator la scară mică și demonstrăm întregul flux de lucru PEC+SLC direct pe hardware, cu fiecare pas al modelului Qiskit detaliat mai jos.
Exemplu hardware la scară mare
Rulăm întregul flux de lucru PEC+SLC pe un circuit Ising oglindit de 20 de qubiți executat pe hardware IBM Quantum®, urmând cei patru pași ai unui model Qiskit.
Pasul 1: Mapează problema
Demonstrația noastră este configurată după cum urmează:
-
Alegem să efectuăm evoluția temporală conform unui hamiltonian lanț Ising 1D. Lanțul Ising 1D oferă o structură de circuit plăcut de densă, ceea ce este convenabil pentru a prezenta implementările PEC.
-
Alegem să măsurăm observabila , care conține o componentă și una . Acest lucru este convenabil pentru a evidenția modul în care limitele SLC tratează diferit diferitele tipuri de erori.
-
Vrem ca valoarea de așteptare ideală a acestei observabile să fie exact 1, astfel încât calitatea estimărilor atenuate să fie ușor de evaluat. Oglindirea circuitului ne apropie de acest obiectiv: pentru fiecare poartă din a doua jumătate a circuitului există o poartă inversă în prima jumătate, astfel încât circuitul ideal acționează ca identitatea.
-
În cele din urmă, obținerea valorii de așteptare ideale de 1 necesită ca circuitul să înceapă — și, prin urmare, datorită oglindirii, să se termine ideal și el — într-o autostare +1 a observabilei. Deoarece observabila conține o componentă non-, iar primitiva
Executorține cont de baza de măsurare dorită la finalul circuitului, adăugăm la începutul circuitului oglindă porțile de pregătire a bazei corespunzătoare cu o funcțieprepare_basis. Folosim funcțiaget_measurement_basespentru a identifica ce porți sunt necesare și unde trebuie plasate, ținând cont în același timp de subtilitățile indicilor de qubiți care apar din convențiile de adnotarebox, așa cum este discutat în secțiunea despre pregătirea măsurătorilor de bază canonice.
# 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
)

Pasul 2: Optimizează
Optimizăm detaliile asociate circuitului care va fi rulat, observabilei care va fi măsurată și parametrilor de învățare a zgomotului. Ca punct de plecare, selectăm procesorul Heron cel mai puțin ocupat și îl instanțiem cu porțile fracționale activate. Aceste porți fracționale permit o sensibilitate mai mare în unele dintre filtrele noastre de post-selecție.
# 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
Mai întâi, vom transpila circuitul nostru în instrucțiuni ISA, așa cum este necesar pentru execuția pe QPU-urile noastre. Alegerea qubiților fizici pe care se rulează are un impact major asupra calității rezultatelor, deoarece timpii de coerență și fidelitățile porților variază pe un dispozitiv și se schimbă în timp. Pentru un experiment atent, poate merita să selectezi manual un lanț de qubiți pe baza unor experimente rapide de benchmarking (vezi, de exemplu, tutorialul Real-time benchmarking for qubit selection). În scopul acestui tutorial, ne bazăm pe transpiler-ul Qiskit pentru a selecta o dispunere pe baza hărții de cuplare a backend-ului și a celor mai recente date de calibrare ale acestuia.
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,
)

Încadrează circuitul în casete
Pentru ușurința implementării, utilizăm pasul de transpilare generate_boxing_pass_manager, care plasează instrucțiunile circuitului în casete adnotate. Aceste casete indică clar unde, în cazul PEC, ar trebui injectat anti-zgomotul în circuit. Pentru detalii despre setări, consultă documentația Samplomatic.
Reține că fluxul de lucru SLC necesită utilizarea inject_noise_strategy="individual_modification" mai târziu în proces, deoarece acest lucru ne permite să identificăm în mod unic adnotările InjectNoise ale instrucțiunilor BoxOp din circuit. (Casetele fără o adnotare InjectNoise nu pot fi identificate în acest fel.)
Calculele limitelor SLC presupun în prezent că adnotările InjectNoise sunt plasate cu site="after" (vezi qiskit-addon-slc#33). Deoarece valoarea implicită a acestei setări s-a schimbat în versiunile recente ale samplomatic (vezi samplomatic#372), setăm explicit inject_noise_site="after" mai jos.
Funcția find_unique_box_instructions parcurge circuitul încadrat în casete furnizat și identifică pe cele care au straturi de doi qubiți (2Q) sau măsurători unice, în scopul învățării și injectării zgomotului.
# 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,
)

Pregătește măsurătorile de bază canonice
Reține că trebuie să avem o grijă deosebită pentru a ține evidența ordinii qubiților. Mai jos, introducem noțiunea de canonical_qubits ca mijloc de a actualiza corespunzător ordinea qubiților atunci când o furnizăm către Executor, ca urmare a modului în care este capturată ordinea qubiților atunci când se încadrează circuitele în casete și se găsesc instrucțiuni unice. Vezi documentația Qubit ordering convention pentru detalii.
# 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
]
Flux de lucru pentru shading lightcone, învățarea zgomotului și injectarea anti-zgomotului
În acest tutorial, rulăm calculele limitelor SLC înainte ca învățarea zgomotului să se finalizeze, astfel încât circuitul care urmează să fie atenuat este rulat cât mai aproape în timp posibil de modelul de zgomot învățat. În principiu, acest flux de lucru poate fi paralelizat în continuare: un job de învățare a zgomotului poate rula în timp ce, în paralel, limitele zgomotului sunt estimate. Pentru un circuit cuantic arbitrar, calculul limitei de zgomot poate scala cu o dependență slab exponențială, astfel încât paralelizarea calculului limitei (de exemplu, pe mai multe nuclee CPU) produce limite mai stricte pentru un buget de timp de calcul dat, iar execuțiile QPU și calculele limitelor pot fi ele însele paralelizate pentru cel mai eficient flux de lucru.
Prezice Paulii modelului de zgomot ce urmează a fi învățați
Înainte de a rula orice învățare a zgomotului pe hardware, mai întâi enumerăm ce termeni de eroare Pauli va conține modelul nostru de zgomot. Amintește-ți că modelăm zgomotul fiecărui strat unic ca un model sparse Pauli-Lindblad cu o rată de eroare pentru fiecare Pauli de pondere unu (, sau ) pe fiecare qubit activ și una pentru fiecare Pauli de pondere doi pe fiecare pereche conectată de qubiți activi. Funcția generate_noise_model_paulis parcurge fiecare strat unic încadrat în casete al circuitului furnizat și generează exact această listă de termeni Pauli, ținând cont de conectivitatea qubiților circuitului.
Cu alte cuvinte, acest pas prezice structura modelului de zgomot pe care NoiseLearnerV3 o va completa ulterior cu ratele învățate. Cunoașterea acestei structuri în avans este ceea ce ne permite să calculăm o limită înainte și una înapoi pentru fiecare eroare potențială înainte ca învățarea zgomotului să fi fost măcar rulată.
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. Calculează limitele înainte
Funcția compute_forward_bounds evaluează relațiile de comutare dintre porțile din fiecare strat și termenii Pauli generați mai sus, în funcție de modul în care erorile de propagare înainte afectează observabila dorită . Pentru porțile care comută cu termenii Pauli, nu se face nimic. Pentru porțile Clifford, acestea sunt împinse spre începutul circuitului. Pentru porțile non-Clifford, aproximăm influența lor asupra observabilelor țintă pentru a fi prioritizate ulterior pentru anularea zgomotului (după ce toate limitele au fost combinate). Această limită se obține aplicând mai întâi norma L2 (adică rădăcina pătrată a sumei pătratelor coeficienților termenilor Pauli relevanți). Când sunt implicați prea mulți termeni de qubiți, revenim la o limită mai laxă care utilizează inegalitatea triunghiului.
Setează parametrii de calcul al limitelor
Următoarele setări controlează calculele clasice ale limitelor:
-
slc_atol: toleranța absolută a calculelor. Termenii operatorului cu coeficienți sub acest prag sunt tratați ca neglijabili și eliminați. -
slc_eigval_max_qubits: numărul maxim de qubiți pentru care se evaluează limita mai strictă, bazată pe valori proprii (norma L2). Operatorii care se sprijină pe mai mulți qubiți decât aceasta revin la limita mai laxă a inegalității triunghiului descrisă mai sus. -
slc_evolution_max_terms: numărul maxim de termeni Pauli păstrați în timpul propagării operatorilor prin circuit. Trunchierea celor mai mici termeni menține calculul tractabil, cu prețul unor limite ușor mai laxe. -
slc_num_processes: numărul de procese CPU pe care este paralelizat calculul limitelor. -
slc_timeout: bugetul de timp (în secunde) pentru calculul limitelor. Oricăror straturi neatinse în acest buget li se atribuie limita trivială de 2.
În acest tutorial, timeout-ul este atins intenționat: păstrăm slc_timeout deliberat mic pentru ca notebook-ul să ruleze rapid și pentru a demonstra că atingerea timeout-ului este sigură. Nuanțele calculate sunt limite superioare la fiecare etapă, astfel încât atingerea timeout-ului doar face limitele mai laxe (triviale) pentru straturile care nu au fost atinse; nu subestimează niciodată efectul unei erori. Acest lucru apare în vizualizările de mai jos ca straturile din stânga fiind complet umplute cu limita trivială. Creșterea timeout-ului (sau a numărului de procese) strânge limitele și reduce și mai mult overhead-ul de eșantionare.
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.
Vizualizează SLC pentru inspecție manuală
Poți interpreta comportamentul limitelor shaded examinând modul în care măsurătorile și termenii Pauli interacționează cu erorile locale. Aceste tipare sunt caracteristice acestei probleme de evoluție temporală a hamiltonianului Ising kicked și apar și în lucrarea Lightcone Shading for Classically Accelerated Quantum Error Mitigation, cu mai multe caracteristici distinctive:
-
Putem distinge clar cele două conuri care apar din cele două Pauli non-identitate din observabilă.
-
Putem vedea că măsurarea X pe qubitul 6 comută cu eroarea X din stratul cel mai din dreapta.
-
Putem vedea că Pauli Z de pe qubitul 13 comută cu eroarea Z din stratul cel mai din dreapta.
-
Când atingem timeout-ul specificat mai sus, straturile rămase spre stânga sunt umplute în întregime cu limite triviale de doi.
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. Strânge limitele înainte cu limite de viteză cuantică
În continuare strângem limitele folosind funcția tighten_with_speed_limit. Răspândirea geometrică a lightcone-ului observabilei este deja luată complet în calcul în limitele înainte calculate mai sus. Ceea ce adaugă această funcție este o trecere de auto-consistență care valorifică viteza limitată cu care informația poate curge prin circuit — lightcone-ul crește cu cel mult un qubit per strat de porți cu doi qubiți — pentru a strânge limitele calculate anterior una față de cealaltă. Informația circulă în ambele direcții:
-
De la limitele comutatorului la limitele asupra observabilei retropropagate: dacă observabila comută cu o eroare pe un qubit, atunci nu poate avea acolo o componentă sau mare.
-
De la limitele asupra observabilei retropropagate la limitele comutatorului: dacă observabila are o componentă și mică pe un qubit, atunci trebuie să comute aproape complet cu acolo.
forward_bounds_tighter = tighten_with_speed_limit(
forward_bounds, boxed_circuit, noise_model_paulis, isa_observable
)
Vizualizează SLC pentru inspecție manuală
În principiu, această strângere oferă o tranziție mai lină între limitele calculate și limitele triviale atribuite straturilor de dincolo de timeout. În acest exemplu efectul este abia vizibil, deoarece lightcone-urile s-au răspândit deja spre marginile circuitului până când se atinge timeout-ul.
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. Calculează limitele înapoi
Această parte a predicției zgomotului evaluează modul în care o eroare la un anumit strat poate afecta starea de intrare . Funcția compute_backward_bounds inversează mai întâi circuitul și elimină porțile de măsurare, apoi continuă cu o analiză similară celei efectuate pentru calculele limitelor înainte.
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,
)
Vizualizează SLC pentru inspecție manuală
Din calculul limitelor înapoi, putem vedea cum structura stării inițiale guvernează comportamentul timpuriu al propagării erorii:
-
Putem vedea clar cum erorile Z comută inițial cu starea inițială |0⟩.
-
Doar pe qubitul 6, unde inițializăm autostarea +1 a bazei X, o eroare Z nu reușește să comute, în timp ce o eroare X comută.
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,
)
)



Previzualizează limitele combinate fără ratele de zgomot învățate
Funcția merged_bounds determină punctul din circuit unde trecerea de la limitele înapoi la limitele înainte minimizează polarizarea totală estimată asupra observabilei dorite. Această polarizare se calculează ca suma contribuțiilor limitelor înapoi pentru toate locațiile de zgomot dinainte de acel punct, plus contribuțiile limitelor înainte pentru toate locațiile de zgomot de după el. În prezent, acest lucru se face uniform pentru toți qubiții.
Punctul optim la care se trece de la limitele înapoi la cele înainte depinde de ratele de zgomot învățate. În această etapă nu am învățat încă nicio rată: noise_model_rates a fost inițializat cu None pentru fiecare strat, ceea ce merge_bounds tratează ca rate de zgomot uniforme. Limitele combinate afișate aici sunt așadar doar o previzualizare; le recalculăm cu ratele efectiv învățate în Pasul 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.
Vizualizează SLC pentru inspecție manuală
După combinarea limitelor înapoi și a limitelor înainte strânse, comportamentul SLC-urilor combinate devine clar:
-
Funcția de mai sus ne indică că se alege o partiție la care are loc trecerea de la limitele înapoi la limitele înainte strânse.
-
Putem vedea mai jos că SLC-urile conțin acum limite parțiale înapoi și limite parțiale înainte strânse.
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,
)
)



Pasul 3: Execută
În această secțiune începem partea fluxului de lucru care utilizează un dispozitiv cuantic real. Pentru această metodă de atenuare a erorilor bazată pe învățare, există doi pași:
-
Învață zgomotul folosind
NoiseLearnerV3. -
Execută un circuit de atenuare a erorilor cu framework-ul
samplomaticșiExecutor.
Cu erorile mărginite din circuitul nostru cuantic, învățăm ratele de zgomot asociate pentru a prioritiza bugetul nostru de erori, determinăm overhead-ul de eșantionare și executăm pe un QPU.
a. Învață ratele de zgomot
Noise learner-ul caracterizează procesele de zgomot care afectează porțile dintr-unul sau mai multe circuite de interes, pe baza modelului de zgomot sparse Pauli-Lindblad. Metoda run() lansează un job de învățare a zgomotului pentru straturile unice de doi qubiți furnizate, folosind opțiunile specificate în configurația noise-learner-ului. Aceste opțiuni controlează strategia de Pauli-twirling, numărul de randomizări și de shot-uri, adâncimile de învățare și post-selecția.
Alegem, de asemenea, adâncimile de învățare în mod deliberat. O constatare practică pentru atenuarea bazată pe învățare cu samplomatic este că este foarte benefic ca cea mai mare adâncime de învățare să corespundă adâncimii circuitului pe care vrei să-l atenuezi. Deoarece layer_pair_depths din NLv3 se măsoară în perechi de straturi (un strat plus inversul său), setăm valoarea cea mai mare la jumătate din adâncimea stratului de doi qubiți a circuitului.
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. Actualizează limitele combinate cu ratele de zgomot efectiv învățate
Acum că modelul specific de zgomot a fost învățat, putem aplica ratele de zgomot învățate limitelor de zgomot prezise și obținem o determinare finală a limitelor care au cel mai mare impact asupra minimizării polarizării.
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. Calculează local_scales pentru execuția hardware
compute_local_scales analizează fiecare eroare de zgomot posibilă din circuit și estimează cât de mult ar putea polariza acea eroare măsurătoarea finală, precum și cât de costisitoare ar fi corectarea ei. Apoi clasifică erorile în funcție de cât de merituoasă este atenuarea lor și selectează subsetul care reduce polarizarea cât mai mult posibil, rămânând în același timp în cadrul bugetului permis de cost de eșantionare (sau atingând o acuratețe dorită). Rezultatul este un set de factori de scalare care indică care erori vor fi atenuate activ și care vor rămâne neatenuate (local_scales), împreună cu overhead-ul total de cost de eșantionare prezis (sampling_costs) și polarizarea rămasă (residual_bias_bound).
Capacitatea de a controla polarizarea rămasă dorită este o caracteristică critică a implementării SLC a PEC. În timp ce în implementarea originală, overhead-ul de eșantionare viza întotdeauna o polarizare zero, noi putem regla overhead-ul de eșantionare necesar printr-un compromis cu polarizarea rămasă așteptată. Acest lucru ajută utilizatorul să rămână în cadrul unui buget fix de eșantionare, ceea ce poate fi deosebit de util atunci când se prototipează inițial un flux de lucru.
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_)
Compromite overhead-ul de eșantionare în favoarea polarizării reziduale
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. Execută circuitul de interes cu anti-zgomot
c.i. Pregătește circuitul șablon folosind samplex
samplex-ul este o ieșire a metodei build din Samplomatic, care codifică toate informațiile necesare pentru a genera parametri randomizați pentru template_circuit. Aceștia sunt apoi folosiți pentru a configura obiectele QuantumProgram, care sunt la rândul lor rulate pe un QPU cu primitiva Executor. Fiecare QuantumProgram poate conține mai multe elemente, pe care le poți concepe ca o pereche de template și samplex.
Vezi tutorialul Hello samplomatic pentru detalii.
# 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. Configurează 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. Execută programul cu primitiva Executor
executor = Executor(backend)
job_exec = executor.run(program)
results_exec = job_exec.result()
Pasul 4: Post-procesează
Pe măsură ce calculăm valoarea de așteptare finală de interes folosind executor_expectation_values, implementăm câteva tehnici de post-procesare pentru a ne asigura că obținem rezultate de cea mai bună calitate posibilă. Mai întâi, aplicăm eliminarea twirled a erorii de citire (TREX), care ține cont de orice erori apărute în timpul procesului de citire. Apoi, corectăm erorile datorate zgomotului non-Markovian de pe backend-urile noastre Heron folosind o metodă de post-selecție. Această metodă măsoară qubiții activi și spectatori, apoi aplică o rotație lentă fiecărui qubit și apoi măsoară din nou. În cazurile în care cele două măsurători nu confirmă un qubit inversat conform așteptărilor, aceste shot-uri sunt eliminate prin aplicarea unei mask din PostSelector. În cadrul calculului măștii, poate fi setată o strategie specifică pentru filtrarea pe baza nodurilor cu un singur qubit sau a muchiilor spectatoare vecine, ceea ce poate influența atât numărul de shot-uri filtrate, cât și calitatea rezultatelor.
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):
Din examinarea rezultatelor experimentale, putem compara direct comportamentul diferitelor abordări: PEC, PEC combinat cu SLC și referința neatenuată. Câteva detalii specifice de evidențiat:
-
Rezultatul neatenuat se situează cu mult în afara benzii de polarizare de 10% (aproape de 0,35) și nu este afectat de numărul de randomizări.
-
Pe acest dispozitiv, PEC complet are un overhead de eșantionare de . După oscilații mari inițiale, estimarea PEC simplu recuperează semnalul și se stabilizează în jurul valorii de 1,1, depășind ușor valoarea exactă. Reține că banda de polarizare de 10% se aplică doar estimatorului PEC+SLC: PEC simplu este în principiu nepolarizat, deci această deviație reziduală nu este acoperită de o garanție de polarizare, ci reflectă mai degrabă fluctuații statistice și imperfecțiuni ale modelului de zgomot învățat, ceea ce este rezonabil la această dimensiune a problemei.
-
SLC reduce overhead-ul cu încă un factor de ~3,4 (la , pentru o limită de polarizare reziduală de aproximativ 10%). PEC+SLC fluctuează de asemenea puternic la puține randomizări, ajungând inițial chiar în regiunea nefizică, dar estimarea sa converge constant spre valoarea exactă, stabilizându-se în jurul valorii de 1,0, bine în interiorul benzii. Aceasta egalează sau depășește acuratețea PEC simplu la aproximativ o treime din costul de eșantionare, demonstrând beneficiul shading-ului lightcone.
-
Barele de eroare ale ambelor estimări atenuate se restrâng pe măsură ce se acumulează randomizări; la un număr fix de randomizări, estimatorul PEC+SLC are varianța statistică mai mică, în concordanță cu -ul său mai mic.
Reține că ratele de zgomot învățate (și, prin urmare, overhead-urile și estimările atenuate) depind de backend și de calibrarea acestuia la momentul execuției, așa că rularea din nou a acestui notebook poate produce valori diferite din punct de vedere cantitativ.
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)

Pași următori
Dacă ai găsit interesantă această lucrare, s-ar putea să te intereseze următoarele materiale:

