Sari la conținutul principal
Acest proiect este în versiune beta. Ai găsit o eroare? Deschide un issue pe GitHub. Ai o întrebare sau o idee? Întreabă în Discussions — ne-ar plăcea să aflăm părerea ta!
Cum este calitatea traducerii pe această pagină?

Îmbunătățește valorile de așteptare cu absorbția propagată a zgomotului (PNA)

Estimare de utilizare: 10 minute pe un procesor Heron (NOTĂ: Aceasta este doar o estimare. Timpul tău de execuție poate varia.)

Rezultate ale învățării​

  • Ce este absorbția propagată a zgomotului (PNA) și cum atenuează zgomotul Gate-urilor cu doi Qubiți prin absorbția canalelor de zgomot invers învățate în observabila măsurată

  • Cum să folosești samplomatic pentru a încadra și adnota straturile circuitului pentru twirling, schimbări de bază și injectare de zgomot

  • Cum să înveți zgomotul straturilor cu [NoiseLearnerV3](/guides/directed-execution-model#noiselearnerv3) și să îl propagi într-o observabilă care atenuează zgomotul cu qiskit-addon-pna

  • Cum să eșantionezi circuite randomizate cu clasele QuantumProgram și Executor din qiskit-ibm-runtime și să combini PNA cu TREX și post-selecție

Condiții prealabile​

Context​

În acest tutorial, demonstrăm cum să valorificăm instrumente avansate de atenuare a erorilor din Qiskit pentru a îmbunătăți estimarea valorii de așteptare în experimente cuantice zgomotoase.

Ce este absorbția propagată a zgomotului (PNA)?​

Absorbția propagată a zgomotului este o tehnică de atenuare a erorilor de poartă prin propagarea observabilei prin canalul de zgomot invers care afectează porțile cu doi qubiți, rezultând o observabilă de atenuare a zgomotului.

Putem folosi atenuarea cuantică a erorilor pentru a extrage valori de așteptare utile din hardware cuantic zgomotos, fără a necesita toleranță completă la erori. PNA se concentrează în mod specific pe absorbirea efectelor zgomotului în observabilă în sine, mai degrabă decât pe modificarea operațiilor circuitului.

Fiecare poartă zgomotoasă dintr-un circuit cuantic poate fi modelată ca o poartă ideală urmată de un canal de zgomot. PNA învață sau caracterizează aceste canale de zgomot și le definește inversele. În loc să insereze operațiile inverse în execuția hardware (ceea ce este de obicei imposibil de realizat), PNA propagă canalele de zgomot inverse înainte prin circuit și le aplică observabilei. Acest proces transformă observabila OO într-un nou operator O~\tilde{O}, astfel încât măsurarea O~\tilde{O} pe circuitul zgomotos să dea aceeași valoare de așteptare ca măsurarea OO pe un circuit ideal, fără zgomot.

Acest lucru poate fi rezumat astfel:

  1. Modelează fiecare poartă zgomotoasă uiu_i ca o operație ideală urmată de un canal de zgomot Λi\Lambda_i.
  2. Învață sau estimează fiecare Λi\Lambda_i folosind instrumente de caracterizare a zgomotului.
  3. Definește și propagă hărțile de zgomot inverse Λi−1\Lambda_i^{-1} înainte prin circuit folosind tehnici de transfer Pauli.
  4. Absoarbe aceste inverse în observabilă, rezultând un operator atenuat de zgomot O~\tilde{O}.

Când zgomotul este descris ca un canal Pauli (sau, mai general, ca un canal Pauli-Lindblad rar), această propagare poate fi efectuată eficient utilizând propagarea Pauli. Propagarea Pauli oferă un cadru pentru aproximarea modului în care canalele de zgomot invers se transformă pe măsură ce trec prin straturi de operații Clifford și non-Clifford, controlând în același timp complexitatea computațională.

Prin deplasarea atenuării în observabilă, PNA evită overhead-ul mare de eșantionare care ar rezulta altfel din inserarea operațiilor de corecție fizică în circuit. În schimb, circuitul zgomotos original este executat, în timp ce observabila este transformată într-un nou operator O~\tilde{O} a cărui valoare de așteptare anulează efectele zgomotului.

Fluxul de lucru PNA​

Procesul poate fi înțeles prin următoarele etape conceptuale. Prima schemă ilustrează un experiment zgomotos standard.

Noisy experiment

Dacă învățăm modelul de zgomot, putem aplica inversul său și anula zgomotul.

Noise-mitigated experiment

În loc să implementăm canalul de zgomot invers prin eșantionarea lui pe QPU, așa cum se face în anularea probabilistică a erorilor (PEC), îl aplicăm clasic observabilei măsurate folosind propagarea Pauli. Observabila rezultată atenuează efectiv zgomotul învățat al porților atunci când este măsurată.

PNA overview

Atenuarea modulară a erorilor cu Samplomatic și Executor​

Abordarea PNA se bazează pe o arhitectură modulară de atenuare a erorilor în Qiskit. Această arhitectură folosește biblioteca samplomatic împreună cu clasele QuantumProgram și Executor (adăugate la Qiskit Runtime în qiskit-ibm-runtime v0.47.0) pentru a face tehnici precum absorbția propagată a zgomotului și twirling-ul Pauli compozabile și reutilizabile în cadrul diferitelor experimente.

În loc să integrăm logica de atenuare direct în definiția circuitului, atenuarea este exprimată declarativ prin adnotări samplomatic și gestionată programatic prin Executor, care controlează modul în care circuitele randomizate sunt generate, executate și post-procesate.

În acest tutorial implementăm un Qiskit pattern pentru a demonstra cum poate PNA propaga canalele de zgomot Pauli invers și modifica observabila corespunzător, pentru a îmbunătăți estimarea valorilor de așteptare pe QPU-uri zgomotoase.

Prezentare generală a fluxului de lucru​

  • Pasul 1: Mapare la problema cuantică

    • Construiește un model Ising forțat (kicked-Ising) Trotterizat oglindit și o observabilă țintă.
  • Pasul 2: Caracterizează și propagă zgomotul

    • Folosește samplomatic pentru a identifica și adnota straturile unice cu doi qubiți și măsurătorile din circuit.

    • Învață zgomotul care afectează fiecare strat unic folosind NoiseLearnerV3.

    • Mapează fiecare adnotare InjectNoise la modelul de zgomot învățat corespunzător.

    • Folosește qiskit-addon-pna pentru a propaga canalele de zgomot invers înainte prin circuit și a le absorbi în observabila țintă.

  • Pasul 3: Execută experimente cuantice

    • Definește un QuantumProgram pentru a specifica eșantionarea randomizată prin samplex și execută experimentele pe backend folosind Executor.
  • Pasul 4: Reconstruiește și analizează rezultatele

    • Compară strategiile de atenuare (PNA, PNA+TREX, PNA+PS, PNA+PS+TREX) și vizualizează îmbunătățirea față de rezultatele neatenuate.

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)

  • Samplomatic v0.13 sau o versiune ulterioară (pip install samplomatic)

  • Addon-ul Qiskit PNA (pip install qiskit-addon-pna)

  • Utilitare addon Qiskit (pip install qiskit-addon-utils)

Configurare​

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

_needed = {"matplotlib": "matplotlib", "numpy": "numpy", "qiskit": "qiskit", "qiskit_addon_pna": "qiskit-addon-pna", "qiskit_addon_utils": "qiskit-addon-utils", "qiskit_ibm_runtime": "qiskit-ibm-runtime", "samplomatic": "samplomatic"}
_missing = [pip for module, pip in _needed.items()
if importlib.util.find_spec(module) is None]
# One at a time, so a package that fails to install does not block the others
for _pip in _missing:
%pip install -q {_pip}
if not _missing:
print("\u2713 All required packages are installed")
from qiskit import QuantumCircuit
from qiskit.quantum_info import Pauli, SparsePauliOp
from qiskit.transpiler import generate_preset_pass_manager, PassManager
from qiskit_ibm_runtime import (
QiskitRuntimeService,
QuantumProgram,
Executor,
NoiseLearnerV3,
)
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 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_pna import generate_noise_mitigating_observable
import samplomatic
from samplomatic.transpiler import generate_boxing_pass_manager
from samplomatic.annotations import InjectNoise
from samplomatic.utils import get_annotation, find_unique_box_instructions
import numpy as np
import matplotlib.pyplot as plt

# Selects a connected chain of low-error qubits on the target backend.
# The mirrored kicked-Ising circuit is a 1D chain, so we only need a line
# of connected physical qubits; the helper walks the backend's coupling map
# and grows a chain along the lowest-error two-qubit edges, so it works for
# any backend rather than relying on a hardcoded layout.
def find_qubit_chain(backend, length):
"""Find a connected chain of ``length`` physical qubits on ``backend``.

The chain is grown greedily along the lowest-error two-qubit edges, so it
favors better-performing qubits. Because the mirrored kicked-Ising circuit
is a 1D chain, a connected line is all we need.
"""
target = backend.target

# Identify the native two-qubit gate and build a per-edge error lookup.
two_qubit_gate = next(
name
for name in target.operation_names
if target[name]
and all(q is not None and len(q) == 2 for q in target[name])
)
edge_error = {
frozenset(qargs): (
1.0 if props is None or props.error is None else props.error
)
for qargs, props in target[two_qubit_gate].items()
}

graph = backend.coupling_map.graph.to_undirected(multigraph=False)
neighbors = {n: list(graph.neighbors(n)) for n in graph.node_indices()}

def first_chain_from(start):
path, visited = [start], {start}

def grow():
if len(path) == length:
return True
node = path[-1]
order = sorted(
neighbors[node],
key=lambda m: edge_error.get(frozenset((node, m)), 1.0),
)
for nxt in order:
if nxt not in visited:
visited.add(nxt)
path.append(nxt)
if grow():
return True
path.pop()
visited.remove(nxt)
return False

return path if grow() else None

def chain_cost(path):
return sum(
edge_error.get(frozenset((path[i], path[i + 1])), 1.0)
for i in range(len(path) - 1)
)

# Try low-degree qubits first (the natural ends of long chains) and keep
# the lowest-error chain found.
best_path, best_cost = None, float("inf")
for start in sorted(neighbors, key=lambda n: len(neighbors[n])):
chain = first_chain_from(start)
if chain is not None and (cost := chain_cost(chain)) < best_cost:
best_path, best_cost = chain, cost

if best_path is None:
raise ValueError(
f"Could not find a connected chain of {length} qubits "
f"on '{backend.name}'."
)
return best_path

Exemplu de simulator la scară mică​

PNA atenuează zgomotul fizic al porților cu doi qubiți al unui procesor cuantic specific. Fluxul de lucru depinde de două servicii hardware care nu au un echivalent semnificativ pe un simulator ideal:

  • NoiseLearnerV3 caracterizează experimental canalul de zgomot Pauli-Lindblad rar atașat fiecărui strat unic cu doi qubiți al circuitului transpilat. Pe un simulator fără zgomot nu există zgomot de învățat, iar canalul invers propagat ar fi identitatea.

  • Primitiva Executor eșantionează circuitele randomizate, cu twirling, generate de samplomatic pe un backend.

În principiu ai putea substitui un model de zgomot sintetic. De exemplu, ai putea atașa instrucțiuni PauliLindbladError cu Qiskit Aer și transmite circuitul zgomotos rezultat direct către generate_noise_mitigating_observable. Totuși, acest lucru validează doar contabilitatea clasică față de zgomotul injectat chiar de tine și estompează scopul tehnicii. Din acest motiv, omitem exemplul cu simulator și demonstrăm fluxul complet PNA direct pe hardware, cu fiecare pas al Qiskit pattern detaliat mai jos.

Exemplu hardware la scară mare​

Rulăm acum fluxul de lucru complet PNA pe un model Ising forțat cu 30 de situri executat pe hardware IBM Quantum®, urmând cei patru pași ai unui Qiskit pattern.

Pasul 1: Mapare la o problemă cuantică​

Generează circuitul Trotter oglindit și observabila

Pentru acest experiment, vom studia dinamica temporală a unui model Ising forțat cu 30 de situri pe un lanț de spini 1D. Hamiltonianul considerat este:

H=−J∑⟨i,j⟩ZiZj+h∑iXiH = -J\sum\limits_{\langle i,j \rangle} Z_iZ_j + h\sum\limits_iX_i,

unde J>0J>0 descrie cuplarea dintre spinii cei mai apropiați vecini, i<ji<j, iar câmpul transversal global, hh, este setat la π8\frac{\pi}{8}. Cu cât hh este mai departe de un unghi Clifford (adică, θ=nπ2,n∈Z\theta=n\frac{\pi}{2}, n \in \mathbb{Z}), cu atât devine mai dificil să propagăm generatorii anti-zgomot prin circuit.

Pentru alegerea observabilei, vom considera magnetizarea medie pe un singur sit, 1N∑i=1N⟨zi⟩\frac{1}{N} \sum_{i=1}^{N} \langle z_i \rangle, unde NN este numărul de situri.

num_qubits = 30
num_trotter_steps = 10
rx_angle = np.pi / 8

# Avg single-site magnetization
id_pauli = Pauli("I" * num_qubits)
observable = (
SparsePauliOp([id_pauli.dot(Pauli("Z"), [i]) for i in range(num_qubits)])
/ num_qubits
)

# Implement Trotterized kicked-Ising model
circuit = QuantumCircuit(num_qubits)
for _step 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)
# Append the inverse circuit to complete the mirroring
circuit.compose(circuit.inverse(), inplace=True)
circuit.measure_active()
circuit.draw("mpl", fold=-1)

Output of the previous code cell

Pasul 2: Optimizează problema pentru execuția pe hardware​

Următorul pas este pregătirea circuitului nostru Trotter oglindit pentru execuția pe hardware IBM Quantum real. Rularea pe un QPU necesită mai mult decât simpla construire a circuitului abstract, deoarece trebuie să îl optimizăm astfel încât:

  1. Respectă setul nativ de porți și conectivitatea backend-ului. Transpilarea mapează circuitul logic într-un circuit ISA suportat de backend-ul țintă. Aceasta asigură că fiecare poartă și interacțiune între qubiți este realizabilă fizic.

  2. Putem caracteriza zgomotul la nivelul straturilor de circuit. PNA se bazează pe învățarea și propagarea canalelor de zgomot invers. Pentru a face acest lucru eficient, împărțim circuitul transpilat în straturi unice "încadrate" cu doi qubiți. Aceste încadrări ne permit să asociem fiecare strat al circuitului cu propriul model de zgomot învățat.

  3. Putem introduce modele de zgomot realiste în PNA. Odată ce circuitul a fost încadrat, folosim serviciul NoiseLearnerV3 pentru a învăța experimental canalele de zgomot Pauli care afectează fiecare strat unic cu doi qubiți. Aceste modele învățate sunt apoi legate înapoi de circuit prin adnotările Samplomatic.

În ansamblu, acest pas face legătura între un circuit oglindit idealizat și un circuit pregătit pentru hardware cu modele de zgomot învățate. Cu această configurație, PNA poate propaga canalele de zgomot invers prin circuit și ajusta observabila corespunzător.

Conectează-te la backend și transpilează într-un circuit ISA​

Mai întâi inițializăm IBM Quantum Compute Service și selectăm un backend. Autentifică-te cu propriul cont urmând instrucțiunile de salvare a acreditărilor, după care QiskitRuntimeService() le va prelua automat.

# Initialize IBM Quantum Compute Service using your saved credentials
service = QiskitRuntimeService()

backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=num_qubits
)
# 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_fez

În continuare, alegem un lanț conectat de qubiți pe backend și transpilăm circuitul pe acesta.

Folosind funcția ajutătoare find_qubit_chain definită în secțiunea Configurare, selectăm o linie de num_qubits qubiți fizici conectați. Apoi transpilăm cu optimization_level=0 și fixăm acest lanț ca initial_layout, ceea ce păstrează exact structura straturilor porților cu doi qubiți ale circuitului oglindit. Pe această structură se bazează pașii de încadrare și de învățare a zgomotului, așa că un nivel de optimizare mai ridicat (care ar anula porțile oglindite) trebuie evitat aici.

# Find a connected, low-error chain of qubits on the chosen backend
layout = find_qubit_chain(backend, num_qubits)

# Transpile the circuit for the target backend, pinning the chain as the layout.
# optimization_level=0 preserves the mirrored two-qubit-gate layers that the
# boxing and noise-learning steps rely on.
pm = generate_preset_pass_manager(
backend=backend, optimization_level=0, initial_layout=layout
)
isa_circuit = pm.run(circuit)
isa_observable = observable.apply_layout(isa_circuit.layout)
isa_circuit.draw("mpl", fold=-1)

Output of the previous code cell

Aplică twirling straturilor de porți cu doi qubiți și măsurătorilor, și găsește straturile unice​

Folosim samplomatic pentru a încadra circuitul și a identifica straturile unice cu doi qubiți. O încadrare (box) este o construcție care grupează instrucțiuni astfel încât intenții sau adnotări specifice să poată fi aplicate ulterior uniform tuturor porților din aceeași încadrare.

Aici apelăm metoda generate_boxing_pass_manager, care depășește simpla identificare a straturilor cu doi qubiți. Aceasta îndeplinește mai multe sarcini cheie:

  • Grupează toate straturile cu doi qubiți din circuit,

  • Aplică adnotările Twirl și ChangeBasis acestor straturi,

  • Grupează operațiile de măsurare în propriile secțiuni încadrate, și

  • Aplică adnotarea InjectNoise fiecărui strat cu doi qubiți.

Aceste adnotări definesc modul în care zgomotul, schimbările de bază și twirling-ul sunt gestionate pe parcursul circuitului. Ele stabilesc de asemenea structura folosită ulterior pentru învățarea și atenuarea zgomotului.

Opțiunile cheie de configurare sunt:

  • enable_gates/enable_measures: True: Încadrează toate straturile de porți cu doi qubiți și măsurătorile terminale. Porțile cu un singur qubit sunt integrate în încadrări (left-dressed).

  • measure_annotations: all: Include adnotările Twirl și ChangeBasis pe încadrarea de măsurare.

  • twirling_strategy: active: Aplică twirling tuturor qubiților activi din fiecare încadrare care conține porți de intricare.

  • inject_noise_targets: gates: Adaugă adnotări InjectNoise tuturor încadrărilor adnotate cu Twirl care conțin porți de intricare.

  • inject_noise_strategy: uniform_modification: Scalează toate straturile de zgomot echivalent pe tot circuitul.

# Box up circuit with Twirl and InjectNoise annotations
pm = generate_boxing_pass_manager(
enable_gates=True,
enable_measures=True,
measure_annotations="all",
twirling_strategy="active",
inject_noise_targets="gates",
inject_noise_strategy="uniform_modification",
)
boxed_circuit = pm.run(isa_circuit)
draw_circ = QuantumCircuit(boxed_circuit.num_qubits)
draw_circ.append(boxed_circuit.data[0], qargs=boxed_circuit.data[0].qubits)
draw_circ.append(boxed_circuit.data[1], qargs=boxed_circuit.data[1].qubits)
draw_circ.draw("mpl", fold=-1, scale=0.3, idle_wires=False)

Output of the previous code cell

Generează circuitul șablon și samplex, care definesc modul în care circuitul va fi eșantionat.

Aici adăugăm de asemenea măsurători spectator și de post-selecție, care sunt necesare pentru a efectua post-selecția pe eșantioanele returnate de Executor.

# Build template circuit and samplex for later use with the "Executor"
template_circuit, samplex = samplomatic.build(boxed_circuit)

# Add post-selection instructions to the template circuit
post_selection_pm = PassManager(
[
AddSpectatorMeasures(backend.coupling_map),
AddPostSelectionMeasures(x_pulse_type="rx"),
]
)
template_circuit = post_selection_pm.run(template_circuit)
draw_circ = template_circuit.copy_empty_like()
draw_circ.data = template_circuit.data[:324]
draw_circ.draw("mpl", fold=-1, scale=0.3, idle_wires=False)

Output of the previous code cell

Învață zgomotul folosind NoiseLearnerV3​

Înainte de a putea aplica PNA pentru atenuarea erorilor, trebuie mai întâi să caracterizăm zgomotul care acționează asupra fiecărui strat unic cu doi qubiți și asupra stratului de măsurare din circuitul nostru. Pentru a face acest lucru, folosim programul NoiseLearnerV3 pentru a învăța experimental modele de zgomot pentru fiecare strat identificat anterior. Modulul de învățare efectuează experimente de tip benchmarking care estimează canalul de zgomot ce afectează fiecare strat și returnează un obiect rezultat care conține modelul învățat.

Începem prin identificarea straturilor unice din circuitul nostru folosind find_unique_box_instructions din samplomatic. Acest lucru asigură că învățăm zgomotul o singură dată pentru fiecare tip distinct de strat, minimizând numărul de experimente și costul total de shot-uri. Lista rezultată de straturi este transmisă modulului de învățare a zgomotului.

Există câțiva parametri cheie care controlează modul în care este învățat zgomotul:

  • num_randomizations: Numărul de circuite aleatorii folosite pentru fiecare configurație de învățare.

  • shots_per_randomization: Numărul de shot-uri realizate pentru fiecare circuit aleatoriu de învățare.

  • layer_pair_depths: Adâncimile circuitului (măsurate în număr de perechi) folosite în experimentele de învățare.

  • post_selection: Activează post-selecția bazată pe muchii folosind porți rx pentru a aplica pulsuri post-măsurare.

# Noise learning parameters
num_randomizations_nl = 64
shots_per_randomization_nl = 128

# Match the deepest noise-learning depth to the depth of the circuit being
# mitigated. ``layer_pair_depths`` are measured in layer pairs (a layer + its
# inverse), so the deepest value is half the circuits's two-qubit-layer depth.
# Learning to this depth improves the quality of the mitigation.

depth_2q = isa_circuit.depth(lambda instr: instr.operation.num_qubits == 2)
max_layer_pair_depth = depth_2q // 2

# 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}
)

# Find the unique instructions (layers) from the boxed-up circuit
unique_2q_layers_and_meas = find_unique_box_instructions(
boxed_circuit, normalize_annotations=None, undress_boxes=True
)

# Configure and run the noise learner on the unique layers.
# Options can be passed directly as a dictionary.
noise_learner_options = {
"num_randomizations": num_randomizations_nl,
"shots_per_randomization": shots_per_randomization_nl,
"layer_pair_depths": layer_pair_depths,
"post_selection": {
"enable": True,
"strategy": "edge",
"x_pulse_type": "rx",
},
"environment": {"job_tags": ["TUT_PNA"]},
}

noise_learner = NoiseLearnerV3(backend, noise_learner_options)
noise_learner_job = noise_learner.run(unique_2q_layers_and_meas)
noise_learner_result = noise_learner_job.result()

Vizualizează ratele de zgomot învățate​

După învățarea modelelor de zgomot, putem inspecta distribuția ratelor de eroare inferate atât pentru operațiile cu un qubit, cât și pentru cele cu doi qubiți. Codul de mai jos extrage reprezentările Pauli-Lindblad din rezultatele de zgomot învățate și colectează ratele de zgomot corespunzătoare.

Pentru fiecare strat învățat:

  • Convertim modelul de zgomot într-o listă rară de tupluri (pstr, qubits, rate), unde pstr este șirul Pauli care acționează asupra qubiților dați, iar rate este rata de eroare asociată.
  • Separăm ratele în termeni cu un qubit (len(pstr) == 1) și cu doi qubiți (len(pstr) == 2).
  • Listele de rate sunt apoi sortate, iar valorile lor mediane sunt calculate.

Reprezentăm grafic distribuțiile ratelor de zgomot cu un qubit (roșu) și cu doi qubiți (albastru) pe o scală logaritmică, cu valorile lor mediane marcate prin linii verticale, astfel încât să putem compara magnitudinile relative ale generatorilor Pauli-Lindblad învățați. Ordonarea ratelor cu unul și doi qubiți depinde de dispozitiv și de straturile specifice care sunt caracterizate; în această rulare, generatorii cu un singur qubit (greutate 1) au rata mediană mai mare.

hw_rates_1q = []
hw_rates_2q = []
for nlr in noise_learner_result[:2]:
plm_list = nlr.to_pauli_lindblad_map().to_sparse_list()
hw_rates_1q += [
rate for (pstr, qubits, rate) in plm_list if len(pstr) == 1
]
hw_rates_2q += [
rate for (pstr, qubits, rate) in plm_list if len(pstr) == 2
]
hw_rates_1q = sorted(hw_rates_1q)
hw_rates_2q = sorted(hw_rates_2q)
median_1q = hw_rates_1q[len(hw_rates_1q) // 2]
median_2q = hw_rates_2q[len(hw_rates_2q) // 2]
fig, ax = plt.subplots(1, 1, figsize=(14, 5))
ax.scatter(
(hw_rates_1q),
[(i) / (len(hw_rates_1q) - 1) for i in range(len(hw_rates_1q))],
color="red",
label="1q rates",
)
ax.set_xscale("log")
ax.set_ylim(0, 1.1)
ax.vlines(median_1q, 0, 1, color="red")
ax.text(median_1q * 1.1, 0.1, f"{median_1q:.2e}")
ax.scatter(
(hw_rates_2q),
[(i) / (len(hw_rates_2q) - 1) for i in range(len(hw_rates_2q))],
color="blue",
label="2q rates",
)
ax.set_xscale("log")
ax.set_ylim(0, 1.1)
ax.vlines(median_2q, 0, 1, color="blue")
ax.text(median_2q * 1.1, 0.2, f"{median_2q:.2e}")
ax.set_title("Learned noise rates")
ax.set_xlabel("Noise rate")
ax.set_yticks([])
plt.legend()
<matplotlib.legend.Legend at 0x125336120>

Output of the previous code cell

Asociază încadrările circuitului cu zgomotul învățat​

Odată ce am obținut modele de zgomot pentru fiecare strat unic cu doi qubiți, trebuie să le legăm de adnotările InjectNoise corespunzătoare din circuitul încadrat.

Directiva InjectNoise este o adnotare samplomatic care folosește "dresser"-ele cu un singur qubit pentru a injecta zgomot în circuit într-un mod controlat și configurabil. Aceasta permite modelarea modulară a zgomotului pe diferite straturi.

Fiecare adnotare InjectNoise include:

  • InjectNoise.ref - un identificator unic pentru adnotare. Acesta este folosit de obiectul samplex pentru a atribui corect modelul de zgomot corespunzător.
  • InjectNoise.modifier_ref (opțional) - o referință secundară care permite scalarea modelului de zgomot atribuit printr-un factor multiplicativ.

În acest pas, construim o mapare de la fiecare InjectNoise.ref la modelul de zgomot învățat corespunzător (PauliLindbladMap). Această mapare asigură că fiecare strat de porți de intricare din circuit este asociat cu modelul de zgomot adecvat, astfel încât efectele zgomotului sunt aplicate corect în timpul eșantionării și al pașilor ulteriori de atenuare a zgomotului.

# map inject noise refs to pauli lindblad maps
refs_to_noise_models = {}
for instruction, result in zip(
unique_2q_layers_and_meas, noise_learner_result, strict=False
):
if inject_noise_annot := get_annotation(
instruction.operation, InjectNoise
):
refs_to_noise_models[inject_noise_annot.ref] = (
result.to_pauli_lindblad_map()
)

Propagă observabila prin anti-zgomotul învățat​

Așa cum am discutat mai sus, acest lucru se face în doi pași. Mai întâi, propagăm un generator anti-zgomot până la sfârșitul circuitului. După aceea, propagăm observabila prin acel generator evoluat. Acest proces se repetă pentru fiecare generator anti-zgomot din circuit. În această implementare, fiecare generator dintr-un strat dat este propagat până la sfârșitul circuitului în paralel. În plus, se folosește multiprocesarea Python pentru a efectua atât propagarea înainte a anti-zgomotului, cât și propagarea inversă a observabilei în paralel. Aceasta previne acumularea generatorilor evoluați în memorie și maximizează totodată resursele de calcul.

Când rulezi PNA, trebuie întotdeauna să furnizezi un circuit zgomotos și o observabilă. Dacă circuitul tău zgomotos este un circuit încadrat cu adnotări InjectNoise, trebuie să furnizezi maparea creată la pasul de mai sus. Se poate transmite de asemenea un circuit neîncadrat care conține instrucțiuni PauliLindbladError din qiskit-aer. În acest caz, nu este necesar să furnizezi refs_to_noise_models. Pe lângă intrările primare, ia în considerare și următoarele:

  • max_err_terms: Numărul de termeni de păstrat în fiecare generator anti-zgomot pe măsură ce acesta este propagat înainte. Permiterea unei valori mai mari crește în general acuratețea, dar acest comportament nu este garantat a fi monoton.

  • max_obs_terms: Numărul de termeni de păstrat în observabila de atenuare a zgomotului, O~\tilde{O}, pe măsură ce este propagată invers prin anti-zgomotul evoluat. Valorile mai mari cresc în general acuratețea, dar nu este garantat că o fac monoton.

  • num_processes: Numărul de nuclee dedicate procesului. Ține minte, generatorii sunt propagați înainte și aplicați observabilei în paralel.

  • search_step: Pasul de propagare inversă folosește o metodă greedy pentru a conjuga aproximativ doi operatori în baza Pauli. Această metodă poate fi accelerată prin creșterea search_step. Vezi documentația pauli-prop pentru mai multe informații.

  • num_to_measure: Deși această variabilă nu este o intrare pentru generate_noise_mitigating_observable, o folosim pentru a controla câți termeni din O~\tilde{O} dorim să măsurăm efectiv. Aici măsurăm doar primii 30 de termeni, care sunt termenii originali din observabila noastră. Termenii au fost acum rescalați astfel încât măsurarea lor are efectul de a atenua zgomotul învățat al porților. Deși măsurăm doar 30 de termeni din O~\tilde{O}, este adesea util să îl lăsăm să crească mult, deoarece asta crește precizia factorilor de scalare ai termenilor principali.

# PNA parameters
num_processes = 8
max_err_terms = 10_000
max_obs_terms = 10_000
num_to_measure = num_qubits

obs_tilde_isa = generate_noise_mitigating_observable(
boxed_circuit,
isa_observable,
refs_to_noise_models,
max_err_terms=max_err_terms,
max_obs_terms=max_obs_terms,
num_processes=num_processes,
print_progress=True,
search_step=8,
)
p_2_v = {p: v for v, p in enumerate(layout)}
obs_tilde_virtual = SparsePauliOp.from_sparse_list(
[
(pstr, [p_2_v[p] for p in p_qubits], coeff)
for (pstr, p_qubits, coeff) in obs_tilde_isa.to_sparse_list()
],
num_qubits=num_qubits,
)
obs_tilde_virtual = obs_tilde_virtual[
np.argsort(np.abs(obs_tilde_virtual.coeffs))[::-1]
][:num_to_measure]
Finished! 13740 / 13740 generators propagated.
obs_tilde_isa = obs_tilde_isa[np.argsort(np.abs(obs_tilde_isa.coeffs))][::-1]
plt.xscale("log")
plt.yscale("log")
plt.title(r"$\tilde{O}$ coeff magnitudes")
plt.ylabel("Magnitude")
plt.xlabel("Pauli term index")
plt.plot(np.abs(obs_tilde_isa.coeffs), ".")
[<matplotlib.lines.Line2D at 0x124b0a120>]

Output of the previous code cell

Transformă bazele de măsurare în formă canonică​

În continuare, găsim un set minim de baze de măsurat astfel încât să putem acoperi complet fiecare termen Pauli din observabila măsurată (multe observabile pot fi măsurate simultan dacă comută qubit cu qubit). Deoarece măsurăm doar termenii din observabila noastră originală, care este suma tuturor Paulilor Z cu un singur qubit, este necesară o singură bază: baza toate-Z.

Pe lângă găsirea unui set de baze de măsurare Pauli, trebuie să mapăm acești termeni Pauli la forma canonică așteptată de Executor. Pentru mai multe informații despre ordonarea canonică a qubiților, vizitează documentația Samplomatic.

meas_box = boxed_circuit.data[-1]
canonical_qubits = [
idx
for idx, qubit in enumerate(boxed_circuit.qubits)
if qubit in meas_box.qubits
]
c_2_p = {
c: p for c, p in enumerate(canonical_qubits)
} # canonical -> physical
p_2_v = {p: v for v, p in enumerate(layout)} # physical -> virtual
c_2_v = {c: p_2_v[p] for c, p in c_2_p.items()} # canonical -> virtual
meas_bases, bases_reverser = get_measurement_bases(obs_tilde_virtual)
meas_bases_canonical = [
np.array([base[c_2_v[c]] for c in range(num_qubits)], dtype=np.uint8)
for base in meas_bases
]

Pasul 3: Execută experimente cuantice​

Specifică modul de eșantionare în QuantumProgram​

Configurăm acum QuantumProgram, care servește drept container central pentru toate circuitele și configurațiile de eșantionare care vor fi executate de Executor. Acest obiect definește modul în care instanțele de circuit randomizate sunt generate, grupate în batch-uri și executate pentru a produce rezultatele de măsurare folosite în PNA.

Un QuantumProgram poate conține mai multe elemente, fiecare constând dintr-un circuit șablon și un obiect samplex corespunzător care definește modul în care sunt aplicate randomizările. Această abstracție permite Executor să gestioneze întregul flux de lucru ca pe un singur program modular — de la generarea circuitelor randomizate până la colectarea și agregarea shot-urilor.

În acest pas, creăm un QuantumProgram care rulează experimentul nostru PNA folosind circuitul șablon și samplex-ul construite anterior. Configurația include următoarele elemente:

  • template_circuit: Circuitul care conține toate porțile necesare pentru a implementa toate randomizările dorite (din randomizări de twirling, parametri, și așa mai departe).

  • samplex: Un obiect care definește o distribuție de probabilitate peste toate randomizările posibile ale circuitului din care se eșantionează.

  • samplex_arguments: Legăturile necesare pentru a defini complet samplex-ul

    • basis_changes: Aici specificăm un set de baze de măsurat care vor acoperi toți termenii Pauli din observabila măsurată.

    • noise_scales.ref: Setăm scala fiecărui strat de zgomot la 0.0 pentru a preveni injectarea vreunui zgomot suplimentar în eșantioanele noastre.

    • pauli_lindblad_maps: Necesar dacă sunt transmise noise_scales. Acesta doar mapează straturile de zgomot la modelul de zgomot asociat.

  • shape: Un tuplu de formă pentru a extinde forma implicită definită de samplex_arguments. Axele netriviale introduse de această extindere enumeră randomizările.

# Control the # of shots during execution
shots_per_randomization_exec = 64
num_randomizations_exec = 6144

# Zero out the noise to prevent noise from being injected during execution.
# We only added InjectNoise annotations so PNA could associate the noise
# to layers in the circuit
samplex_inputs = {f"noise_scales.{ref}": 0.0 for ref in refs_to_noise_models}
samplex_inputs |= {"pauli_lindblad_maps": refs_to_noise_models}

# Specify the bases to measure. The samplex exposes one basis-change input per
# ChangeBasis-annotated box; here a single all-Z basis covers every term. We
# look up the basis-change interface name rather than hardcoding an index, since
# the name depends on the circuit's box structure.
bases_broadcastable = np.expand_dims(np.array(meas_bases_canonical), axis=1)
samplex_inputs |= {
spec.name: bases_broadcastable
for spec in samplex.inputs().get_specs(r"^basis_changes\.")
}

# Convert samplex_inputs into a dict to pass to QuantumProgram
samplex_arguments = (
samplex.inputs().make_broadcastable().bind(**samplex_inputs)
)

# Instantiate the QuantumProgram with the specified parameters
program = QuantumProgram(shots=shots_per_randomization_exec)
program.append_samplex_item(
circuit=template_circuit,
samplex=samplex,
samplex_arguments=samplex_arguments,
shape=(num_randomizations_exec,),
)

Eșantionează circuitul folosind Executor​

Acum că am definit QuantumProgram-ul nostru, executarea experimentului este simplă. Instanțiem pur și simplu obiectul Executor, îi furnizăm backend-ul și rulăm programul.

# Execute (sample) the circuit
executor = Executor(backend)
job_exec = executor.run(program)
exec_results = job_exec.result()

Pasul 4: Reconstruiește și analizează rezultatele​

Pentru a calcula o valoare de așteptare cu erorile atenuate, facem următoarele:

  • Calculăm factorii de scalare TREX pe baza zgomotului învățat care afectează măsurătorile,

  • Generăm o mască pentru a păstra doar eșantioanele post-selectate, și

  • Folosim funcția executor_expectation_values din qiskit-addon-utils pentru a combina toate datele într-o valoare de așteptare cu erorile atenuate.

# Computing the TREX factors
measurement_noise_map = noise_learner_result[2].to_pauli_lindblad_map()
trex_rescale_factors = trex_factors(measurement_noise_map, bases_reverser)

# Post-select the results
post_selector = PostSelector.from_circuit(
circuit=template_circuit, coupling_map=backend.coupling_map
)

# Compute the ps mask for filtering results
mask = post_selector.compute_mask(exec_results[0], strategy="edge")

# Compute expvals using post selected results
results = executor_expectation_values(
exec_results[0]["meas"],
bases_reverser,
meas_basis_axis=0,
avg_axis=1,
measurement_flips=exec_results[0]["measurement_flips.meas"],
pauli_signs=exec_results[0].get("pauli_signs", None),
postselect_mask=mask,
rescale_factors=trex_rescale_factors,
)

Compară strategiile de atenuare: PNA, PNA+TREX, PNA+PS, PNA+PS+TREX​

Calculăm și vizualizăm valorile de așteptare pentru mai multe variante de atenuare folosind rezultatele Executor.

bases_reverser_unmit = {Pauli("Z" * num_qubits): [observable]}
args = [
(bases_reverser_unmit, None, None),
(bases_reverser, None, None),
(bases_reverser, None, trex_rescale_factors),
(bases_reverser, mask, None),
(bases_reverser, mask, trex_rescale_factors),
]

evs = []
for reverser, postsel_mask, factors in args:
# Compute expvals using post selected results
res_ps = executor_expectation_values(
exec_results[0]["meas"],
reverser,
meas_basis_axis=0,
avg_axis=1,
measurement_flips=exec_results[0]["measurement_flips.meas"],
pauli_signs=exec_results[0].get("pauli_signs", None),
postselect_mask=postsel_mask,
rescale_factors=factors,
)
res_ps = np.array(res_ps)
evs.append(res_ps[:, 0][0])

experiments = ["PNA", "PNA+TREX", "PNA+PS", "PNA+PS+TREX"]
colors = ["#d9d9d9", "#b0b0b0", "#7f7f7f", "#4c4c4c"]
plt.bar(experiments, evs[1:], color=colors)
plt.axhline(y=1, color="green", linestyle="--", linewidth=2, label="Ideal")
plt.axhline(
y=evs[0], color="red", linestyle="--", linewidth=2, label="Unmitigated"
)
plt.ylabel("Expectation value", fontsize=14)

plt.title(
r"30q Mirrored Ising, 10 Trotter steps, $\theta_{rx}=\frac{\pi}{8}$",
fontsize=14,
)
plt.legend(loc="upper left", bbox_to_anchor=(1.05, 1), borderaxespad=0.0)
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()

Output of the previous code cell

Rezultatele demonstrează beneficiile cumulative ale combinării diferitelor tehnici de atenuare a erorilor. Abordarea PNA simplă restabilește deja valoarea de așteptare aproape de referința ideală, indicând faptul că propagarea canalelor de zgomot invers în observabilă compensează eficient erorile porților cu doi qubiți.

  • Adăugarea reponderării TREX (PNA+TREX) îmbunătățește ușor estimarea prin corectarea dezechilibrului de eșantionare din circuitele randomizate.

  • Post-selecția (PNA+PS) oferă o îmbunătățire mai vizibilă prin filtrarea rezultatelor de măsurare inconsistente care probabil rezultă din erori reziduale.

  • În cele din urmă, combinarea ambelor (PNA+PS+TREX) produce cel mai precis rezultat, aproape egal cu valoarea ideală, arătând cum aceste strategii de atenuare se întăresc reciproc.

În ansamblu, comparația evidențiază faptul că PNA servește ca fundament robust pentru estimarea valorii de așteptare conștientă de zgomot, în timp ce TREX și post-selecția oferă rafinamente complementare pentru câștiguri suplimentare de precizie.

Pașii următori​

Dacă ai găsit această lucrare interesantă, s-ar putea să fii interesat de următoarele materiale: