Přeskočit na hlavní obsah
Tento projekt je v betě. Našel jsi chybu? Otevřít issue na GitHubu. Máš otázku nebo nápad? Zeptej se v Discussions — rádi si poslechneme tvou zpětnou vazbu!
Jaká je kvalita překladu na této stránce?

Zlepši střední hodnoty pomocí absorpce propagovaného šumu (PNA)

Odhadovaná doba: 10 minut na procesoru Heron (POZNÁMKA: Toto je pouze odhad. Skutečná doba běhu se může lišit.)

Výsledky učení​

  • Co je absorpce propagovaného šumu (PNA) a jak zmírňuje šum dvouqubitových hradel absorpcí naučených inverzních šumových kanálů do měřené pozorovatelné veličiny

  • Jak použít samplomatic k zabalení a anotování vrstev obvodu pro twirling, změny báze a injektáž šumu

  • Jak se naučit šum vrstvy pomocí [NoiseLearnerV3](/guides/directed-execution-model#noiselearnerv3) a propagovat jej do pozorovatelné veličiny zmírňující šum pomocí qiskit-addon-pna

  • Jak vzorkovat randomizované obvody pomocí tříd QuantumProgram a Executor v qiskit-ibm-runtime a kombinovat PNA s TREX a post-selekcí

Předpoklady​

Kontext​

V tomto tutoriálu ukážeme, jak využít pokročilé nástroje pro zmírnění chyb v Qiskitu ke zlepšení odhadu očekávaných hodnot v zašuměných kvantových experimentech.

Co je absorpce propagovaného šumu (PNA)?​

Absorpce propagovaného šumu je technika pro zmírnění chyb hradel propagací pozorovatelné veličiny inverzním šumovým kanálem ovlivňujícím dvouqubitová hradla, což vede k pozorovatelné veličině zmírňující šum.

Kvantové zmírnění chyb můžeme použít k získání užitečných očekávaných hodnot ze zašuměného kvantového hardwaru bez nutnosti plné odolnosti proti chybám. PNA se konkrétně zaměřuje na absorpci účinků šumu do samotné pozorovatelné veličiny, spíše než na úpravu operací obvodu.

Každé zašuměné hradlo v kvantovém obvodu lze modelovat jako ideální hradlo následované šumovým kanálem. PNA se tyto šumové kanály naučí nebo charakterizuje a definuje jejich inverze. Namísto vkládání inverzních operací do hardwarového provedení (což je obvykle neproveditelné) PNA propaguje inverzní šumové kanály vpřed obvodem a aplikuje je na pozorovatelnou veličinu. Tento proces transformuje pozorovatelnou veličinu OO na nový operátor O~\tilde{O} tak, že měření O~\tilde{O} na zašuměném obvodu poskytuje stejnou očekávanou hodnotu jako měření OO na ideálním obvodu bez šumu.

To lze shrnout takto:

  1. Namodelovat každé zašuměné hradlo uiu_i jako ideální operaci následovanou šumovým kanálem Λi\Lambda_i.
  2. Naučit se nebo odhadnout každé Λi\Lambda_i pomocí nástrojů pro charakterizaci šumu.
  3. Definovat a propagovat inverzní šumové mapy Λi−1\Lambda_i^{-1} vpřed obvodem pomocí technik Pauliho přenosu.
  4. Absorbovat tyto inverze do pozorovatelné veličiny, což vede k operátoru O~\tilde{O} se zmírněným šumem.

Pokud je šum popsán jako Pauliho kanál (nebo obecněji jako řídký Pauli-Lindbladův kanál), lze tuto propagaci efektivně provést pomocí Pauliho propagace. Pauliho propagace poskytuje rámec pro aproximaci toho, jak se inverzní šumové kanály transformují při průchodu vrstvami Cliffordových a neCliffordových operací, přičemž kontroluje výpočetní složitost.

Přesunutím zmírnění do pozorovatelné veličiny se PNA vyhýbá velké režii vzorkování, která by jinak vznikla vkládáním fyzických korekčních operací do obvodu. Místo toho se provede původní zašuměný obvod, zatímco pozorovatelná veličina se transformuje na nový operátor O~\tilde{O}, jehož očekávaná hodnota ruší účinky šumu.

Workflow PNA​

Proces lze pochopit prostřednictvím následujících koncepčních fází. První schéma znázorňuje standardní zašuměný experiment.

Noisy experiment

Pokud se naučíme model šumu, můžeme aplikovat jeho inverzi a šum vyrušit.

Noise-mitigated experiment

Namísto implementace inverzního šumového kanálu jeho vzorkováním na QPU, jako je tomu u pravděpodobnostního rušení chyb (PEC), jej aplikujeme klasicky na měřenou pozorovatelnou veličinu pomocí Pauliho propagace. Výsledná pozorovatelná veličina při měření efektivně zmírňuje naučený šum hradel.

PNA overview

Modulární zmírnění chyb pomocí Samplomatic a Executor​

Přístup PNA staví na modulární architektuře pro zmírnění chyb v Qiskitu. Tato architektura využívá knihovnu samplomatic společně s třídami QuantumProgram a Executor (přidanými do Qiskit Runtime ve verzi qiskit-ibm-runtime v0.47.0), aby techniky jako absorpce propagovaného šumu a Pauli twirling byly skladatelné a znovupoužitelné napříč různými experimenty.

Namísto vkládání logiky zmírnění přímo do definice obvodu je zmírnění vyjádřeno deklarativně pomocí anotací samplomatic a spravováno programově prostřednictvím Executor, který řídí, jak jsou randomizované obvody generovány, prováděny a zpracovávány po skončení.

V tomto tutoriálu implementujeme Qiskit pattern, abychom ukázali, jak PNA dokáže propagovat inverzní Pauliho šumové kanály a odpovídajícím způsobem upravit pozorovatelnou veličinu, aby se zlepšil odhad očekávaných hodnot na zašuměných QPU.

Přehled workflow​

  • Krok 1: Mapování na kvantový problém

    • Sestavit zrcadlený Trotterizovaný kicked-Ising model a cílovou pozorovatelnou veličinu.
  • Krok 2: Charakterizace a propagace šumu

    • Použít samplomatic k identifikaci a anotaci unikátních dvouqubitových vrstev a měření v obvodu.

    • Naučit se šum ovlivňující každou unikátní vrstvu pomocí NoiseLearnerV3.

    • Namapovat každou anotaci InjectNoise na odpovídající naučený model šumu.

    • Použít qiskit-addon-pna k propagaci inverzních šumových kanálů vpřed obvodem a jejich absorpci do cílové pozorovatelné veličiny.

  • Krok 3: Provedení kvantových experimentů

    • Definovat QuantumProgram pro specifikaci randomizovaného vzorkování pomocí samplex a provést experimenty na backendu pomocí Executor.
  • Krok 4: Rekonstrukce a analýza výsledků

    • Porovnat strategie zmírnění (PNA, PNA+TREX, PNA+PS, PNA+PS+TREX) a vizualizovat zlepšení oproti nezmírněným výsledkům.

Požadavky​

Než začneš s tímto tutoriálem, ujisti se, že máš nainstalováno následující:

  • Qiskit SDK v2.2 nebo novější, s podporou vizualizace

  • Qiskit Runtime v0.47 nebo novější (pip install qiskit-ibm-runtime)

  • Samplomatic v0.13 nebo novější (pip install samplomatic)

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

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

Nastavení​

# 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

Příklad malého simulátoru​

PNA zmírňuje fyzický šum dvouqubitových hradel konkrétního kvantového procesoru. Workflow závisí na dvou hardwarových službách, které nemají na ideálním simulátoru žádný smysluplný analog:

  • NoiseLearnerV3 experimentálně charakterizuje řídký Pauli-Lindbladův šumový kanál přiřazený ke každé unikátní dvouqubitové vrstvě transpilovaného obvodu. Na bezšumovém simulátoru není žádný šum, který by bylo možné se naučit, a propagovaný inverzní kanál by byl identitou.

  • Primitivum Executor vzorkuje na backendu twirlované, randomizované obvody generované pomocí samplomatic.

V principu byste mohli nahradit syntetický model šumu. Například byste mohli připojit instrukce PauliLindbladError pomocí Qiskit Aer a předat výsledný zašuměný obvod přímo funkci generate_noise_mitigating_observable. To však jen ověří klasické účetnictví proti šumu, který jste sami vložili, a zastírá smysl této techniky. Z tohoto důvodu vynecháváme příklad simulátoru a demonstrujeme celý workflow PNA přímo na hardwaru, přičemž jednotlivé kroky Qiskit patternu jsou rozebrány níže.

Příklad rozsáhlého hardwaru​

Nyní spustíme kompletní workflow PNA na 30místovém kicked Ising modelu prováděném na hardwaru IBM Quantum®, podle čtyř kroků Qiskit patternu.

Krok 1: Mapování na kvantový problém​

Vygenerování zrcadleného Trotterova obvodu a pozorovatelné veličiny

Pro tento experiment budeme studovat časovou dynamiku 30místového kicked Ising modelu na 1D spinovém řetězci. Uvažovaný hamiltonián je:

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

kde J>0J>0 popisuje vazbu spinů nejbližších sousedů, i<ji<j, a globální příčné pole hh je nastaveno na π8\frac{\pi}{8}. Čím dále je hh od Cliffordova úhlu (tedy θ=nπ2,n∈Z\theta=n\frac{\pi}{2}, n \in \mathbb{Z}), tím obtížnější je propagace generátorů anti-šumu obvodem.

Jako pozorovatelnou veličinu zvolíme průměrnou magnetizaci na jednom místě, 1N∑i=1N⟨zi⟩\frac{1}{N} \sum_{i=1}^{N} \langle z_i \rangle, kde NN je počet míst.

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

Krok 2: Optimalizace problému pro provedení na hardwaru​

Dalším krokem je připravit náš zrcadlený Trotterův obvod pro provedení na skutečném hardwaru IBM Quantum. Spuštění na QPU vyžaduje víc než jen sestrojení abstraktního obvodu, protože jej potřebujeme optimalizovat tak, aby:

  1. Respektoval nativní sadu hradel a konektivitu backendu. Transpilace mapuje logický obvod na ISA obvod podporovaný cílovým backendem. Tím je zajištěno, že každé hradlo a interakce qubitů jsou fyzicky realizovatelné.

  2. Abychom mohli charakterizovat šum na úrovni vrstev obvodu. PNA se opírá o učení se a propagaci inverzních šumových kanálů. Aby to bylo efektivní, rozdělíme transpilovaný obvod na unikátní dvouqubitové "zabalené" vrstvy. Tyto boxy nám umožňují přiřadit ke každé vrstvě obvodu její vlastní naučený model šumu.

  3. Abychom mohli do PNA vložit realistické modely šumu. Jakmile je obvod zabalen, použijeme službu NoiseLearnerV3 k experimentálnímu naučení Pauliho šumových kanálů ovlivňujících každou unikátní dvouqubitovou vrstvu. Tyto naučené modely jsou pak zpětně propojeny s obvodem prostřednictvím anotací Samplomatic.

Celkově tento krok překlenuje propast mezi idealizovaným zrcadleným obvodem a obvodem připraveným pro hardware s naučenými modely šumu. S tímto nastavením může PNA propagovat inverzní šumové kanály obvodem a odpovídajícím způsobem upravit pozorovatelnou veličinu.

Připojení k backendu a transpilace na ISA obvod​

Nejprve inicializujeme IBM Quantum Compute Service a vybereme backend. Ověřte se pomocí vlastního účtu podle instrukcí k uložení přihlašovacích údajů, po kterých je QiskitRuntimeService() automaticky převezme.

# 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

Dále vybereme propojený řetězec qubitů na backendu a transpilujeme na něj obvod.

Pomocí pomocné funkce find_qubit_chain definované v sekci Nastavení vybereme linii num_qubits propojených fyzických qubitů. Poté transpilujeme s optimization_level=0 a tento řetězec pevně nastavíme jako initial_layout, což přesně zachovává strukturu vrstev dvouqubitových hradel zrcadleného obvodu. Na tuto strukturu se spoléhají kroky zabalení a učení šumu, takže je zde třeba se vyhnout vyšší úrovni optimalizace (která by zrcadlená hradla vyrušila).

# 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

Twirling vrstev dvouqubitových hradel a měření a nalezení unikátních vrstev​

Pomocí samplomatic zabalíme obvod a identifikujeme unikátní dvouqubitové vrstvy. Box je konstrukt, který seskupuje instrukce dohromady tak, aby bylo možné později jednotně aplikovat konkrétní záměry nebo anotace na všechna hradla v rámci stejného boxu.

Zde voláme metodu generate_boxing_pass_manager, která jde nad rámec pouhé identifikace dvouqubitových vrstev. Provádí několik klíčových úkolů:

  • Seskupuje všechny dvouqubitové vrstvy v obvodu,

  • Aplikuje anotace Twirl a ChangeBasis na tyto vrstvy,

  • Seskupuje měřicí operace do vlastních zabalených sekcí a

  • Aplikuje anotaci InjectNoise na každou dvouqubitovou vrstvu.

Tyto anotace definují, jak je v celém obvodu zpracováván šum, změny báze a twirling. Zároveň zakládají strukturu, která se později používá pro učení a zmírňování šumu.

Klíčové konfigurační možnosti jsou:

  • enable_gates/enable_measures: True: Zabalí všechny dvouqubitové vrstvy hradel a koncová měření. Jednoqubitová hradla jsou v rámci boxů připojena zleva (left-dressed).

  • measure_annotations: all: Zahrne anotace Twirl a ChangeBasis na měřicím boxu.

  • twirling_strategy: active: Provede twirling všech aktivních qubitů v každém boxu obsahujícím propletující hradla.

  • inject_noise_targets: gates: Přidá anotace InjectNoise do všech boxů anotovaných Twirl, které obsahují propletující hradla.

  • inject_noise_strategy: uniform_modification: Škáluje všechny šumové vrstvy rovnoměrně napříč obvodem.

# 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

Vygenerujte šablonový obvod a samplex, které definují, jak bude obvod vzorkován.

Zde také přidáme spectator a post-selekční měření, která jsou potřebná k provedení post-selekce na vzorcích výstupu z 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

Naučení se šumu pomocí NoiseLearnerV3​

Než budeme moci použít PNA pro zmírnění chyb, musíme nejprve charakterizovat šum působící na každou unikátní dvouqubitovou vrstvu a měřicí vrstvu v našem obvodu. K tomu použijeme program NoiseLearnerV3, abychom se experimentálně naučili modely šumu pro každou dříve identifikovanou vrstvu. Learner provádí experimenty ve stylu benchmarků, které odhadují šumový kanál ovlivňující každou vrstvu, a vrací objekt výsledku obsahující naučený model.

Začneme identifikací unikátních vrstev v našem obvodu pomocí find_unique_box_instructions z samplomatic. Tím je zajištěno, že se šum naučíme pouze jednou pro každý odlišný typ vrstvy, což minimalizuje počet experimentů a celkové náklady na shoty. Výsledný seznam vrstev je předán do noise learneru.

Existuje několik klíčových parametrů, které řídí, jak se šum učí:

  • num_randomizations: Počet náhodných obvodů použitých na jednu konfiguraci učení.

  • shots_per_randomization: Počet shotů provedených na jeden náhodný učicí obvod.

  • layer_pair_depths: Hloubky obvodu (měřené počtem párů), které se použijí v učicích experimentech.

  • post_selection: Umožňuje hranovou post-selekci pomocí hradel rx k aplikaci pulzů po měření.

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

Vizualizace naučených měr šumu​

Po naučení modelů šumu můžeme prozkoumat rozdělení odvozených chybovostí jak pro jednoqubitové, tak pro dvouqubitové operace. Níže uvedený kód extrahuje reprezentace Pauli-Lindblad z výsledků naučeného šumu a shromažďuje odpovídající šumové sazby.

Pro každou naučenou vrstvu:

  • Převedeme model šumu na řídký seznam n-tic (pstr, qubits, rate), kde pstr je Pauliho řetězec působící na dané qubity a rate je přidružená chybovost.
  • Rozdělíme míry na jednoqubitové (len(pstr) == 1) a dvouqubitové (len(pstr) == 2) členy.
  • Seznamy měr jsou pak seřazeny a jsou vypočteny jejich mediánové hodnoty.

Vykreslíme rozdělení měr šumu pro jednoqubitové (červená) a dvouqubitové (modrá) operace na logaritmické škále, s jejich mediánovými hodnotami vyznačenými svislými čarami, abychom mohli porovnat relativní velikosti naučených Pauli-Lindbladových generátorů. Pořadí jednoqubitových a dvouqubitových měr závisí na zařízení a na konkrétních charakterizovaných vrstvách; v tomto běhu nesou jednoqubitové (váhy 1) generátory vyšší mediánovou míru.

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

Přiřazení boxů obvodu k naučenému šumu​

Jakmile získáme modely šumu pro každou unikátní dvouqubitovou vrstvu, musíme je propojit s odpovídajícími anotacemi InjectNoise v rámci zabaleného obvodu.

Direktiva InjectNoise je anotace samplomatic, která používá jednoqubitové dressery k řízenému a konfigurovatelnému vkládání šumu do obvodu. Umožňuje modulární modelování šumu napříč různými vrstvami.

Každá anotace InjectNoise obsahuje:

  • InjectNoise.ref - unikátní identifikátor anotace. Ten se používá objektem samplex ke správnému přiřazení odpovídajícího modelu šumu.
  • InjectNoise.modifier_ref (volitelné) - sekundární reference, která umožňuje škálovat přiřazený model šumu multiplikativním faktorem.

V tomto kroku sestavíme mapování z každého InjectNoise.ref na odpovídající naučený model šumu (PauliLindbladMap). Toto mapování zajišťuje, že každá vrstva propletujících hradel v obvodu je spárována s příslušným modelem šumu, takže účinky šumu jsou přesně aplikovány během vzorkování a následných kroků zmírnění šumu.

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

Propagace pozorovatelné veličiny naučeným anti-šumem​

Jak bylo diskutováno výše, děje se to ve dvou krocích. Nejprve propagujeme generátor anti-šumu na konec obvodu. Poté propagujeme pozorovatelnou veličinu tímto vyvinutým generátorem. Tento proces se opakuje pro každý generátor anti-šumu v obvodu. V této implementaci je každý generátor v dané vrstvě propagován na konec obvodu paralelně. Kromě toho se pro paralelní provedení jak dopředné propagace anti-šumu, tak zpětné propagace pozorovatelné veličiny používá Python multiprocessing. To zabraňuje hromadění vyvinutých generátorů v paměti a zároveň maximalizuje využití výpočetních zdrojů.

Při spuštění PNA je vždy potřeba poskytnout zašuměný obvod a pozorovatelnou veličinu. Pokud je váš zašuměný obvod zabalený obvod s anotacemi InjectNoise, musíte poskytnout mapování, které jsme vytvořili v předchozím kroku. Lze také předat nezabalený obvod obsahující instrukce PauliLindbladError z qiskit-aer. V takovém případě není nutné poskytovat refs_to_noise_models. Kromě hlavních vstupů zvažte také následující:

  • max_err_terms: Počet členů, které se ponechají v každém generátoru anti-šumu při jeho dopředné propagaci. Vyšší hodnota obecně zvyšuje přesnost, ale toto chování není zaručeně monotónní.

  • max_obs_terms: Počet členů, které se ponechají v pozorovatelné veličině zmírňující šum, O~\tilde{O}, při její zpětné propagaci vyvinutým anti-šumem. Vyšší hodnoty obecně zvyšují přesnost, ale není zaručeno, že tak činí monotónně.

  • num_processes: Počet jader vyhrazených pro proces. Nezapomeňte, že generátory jsou dopředně propagovány a aplikovány na pozorovatelnou veličinu paralelně.

  • search_step: Krok zpětné propagace používá hladovou metodu k přibližnému konjugování dvou operátorů v Pauliho bázi. Tuto metodu lze zrychlit zvýšením search_step. Další informace najdete v dokumentaci pauli-prop.

  • num_to_measure: Ačkoli tato proměnná není vstupem funkce generate_noise_mitigating_observable, používáme ji k řízení toho, kolik členů z O~\tilde{O} chceme skutečně měřit. Zde měříme pouze 30 nejvýznamnějších členů, což jsou původní členy naší pozorovatelné veličiny. Členy byly nyní přeškálovány tak, že jejich měření má za následek zmírnění naučeného šumu hradel. Přestože měříme pouze 30 členů z O~\tilde{O}, je často stále užitečné nechat ho narůst do velké velikosti, protože to zvyšuje přesnost škálovacích faktorů vedoucích členů.

# 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

Transformace měřicích bází do kanonického tvaru​

Dále najdeme minimální množinu bází k měření tak, abychom plně pokryli každý Pauliho člen v měřené pozorovatelné veličině (mnoho pozorovatelných veličin lze měřit současně, pokud spolu komutují po qubitech). Vzhledem k tomu, že měříme pouze členy v naší původní pozorovatelné veličině, což je součet všech jednoqubitových Pauliho operátorů Z, je potřeba pouze jedna báze: báze všech-Z.

Kromě nalezení množiny Pauliho měřicích bází musíme tyto Pauliho členy namapovat na kanonický tvar očekávaný Executor. Další informace o kanonickém řazení qubitů najdete v dokumentaci 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
]

Krok 3: Provedení kvantových experimentů​

Specifikace způsobu vzorkování v QuantumProgram​

Nyní nastavíme QuantumProgram, který slouží jako centrální kontejner pro všechny obvody a konfigurace vzorkování, které budou provedeny pomocí Executor. Tento objekt definuje, jak jsou generovány, dávkovány a prováděny instance randomizovaného obvodu, aby vznikly výsledky měření použité v PNA.

QuantumProgram může obsahovat více položek, z nichž každá se skládá ze šablonového obvodu a odpovídajícího objektu samplex, který definuje, jak jsou randomizace aplikovány. Tato abstrakce umožňuje Executor zpracovat celý workflow jako jediný, modulární program — od generování randomizovaného obvodu po sběr a agregaci shotů.

V tomto kroku vytvoříme QuantumProgram, který spouští náš experiment PNA pomocí šablonového obvodu a samplex sestavených dříve. Konfigurace zahrnuje následující prvky:

  • template_circuit: Obvod obsahující všechna hradla potřebná k implementaci všech požadovaných randomizací (z randomizací twirlingu, parametrů a tak dále).

  • samplex: Objekt definující pravděpodobnostní rozdělení nad všemi možnými randomizacemi obvodu, ze kterých se vzorkuje.

  • samplex_arguments: Vazby nezbytné k úplné definici samplex

    • basis_changes: Zde specifikujeme množinu bází k měření, které pokryjí všechny Pauliho členy v měřené pozorovatelné veličině.

    • noise_scales.ref: Nastavíme škálu každé šumové vrstvy na 0.0, abychom zabránili vložení jakéhokoli dalšího šumu do našich vzorků.

    • pauli_lindblad_maps: Vyžadováno, pokud jsou předány noise_scales. Toto pouze mapuje šumové vrstvy na přidružený model šumu.

  • shape: N-tice tvaru, která rozšiřuje implicitní tvar definovaný samplex_arguments. Netriviální osy zavedené tímto rozšířením enumerují randomizace.

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

Vzorkování obvodu pomocí Executor​

Nyní, když jsme definovali náš QuantumProgram, je spuštění experimentu jednoduché. Jednoduše vytvoříme instanci objektu Executor, poskytneme mu backend a spustíme program.

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

Krok 4: Rekonstrukce a analýza výsledků​

Pro výpočet střední hodnoty se zmírněním chyb postupujeme následovně:

  • Vypočítej škálovací faktory TREX na základě naučeného šumu ovlivňujícího měření,

  • Vygeneruj masku pro ponechání pouze post-selektovaných vzorků a

  • Použij funkci executor_expectation_values z qiskit-addon-utils pro kombinaci všech dat do střední hodnoty se zmírněním chyb.

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

Porovnání strategií zmírnění chyb: PNA, PNA+TREX, PNA+PS, PNA+PS+TREX​

Vypočítáme a vizualizujeme střední hodnoty pro několik variant zmírnění chyb pomocí výsledků 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

Výsledky demonstrují kumulativní přínosy kombinace různých technik zmírnění chyb. Samotný přístup PNA již obnovuje střední hodnotu blízko ideálnímu benchmarku, což naznačuje, že propagace inverzních šumových kanálů do pozorovatelné veličiny účinně kompenzuje chyby dvouqubitových hradel.

  • Přidání převáhování TREX (PNA+TREX) mírně zlepšuje odhad korekcí nerovnováhy vzorkování v randomizovaných obvodech.

  • Post-selekce (PNA+PS) přináší výraznější zlepšení tím, že odfiltruje nekonzistentní výsledky měření, které pravděpodobně vznikají v důsledku zbytkových chyb.

  • Nakonec kombinace obou (PNA+PS+TREX) poskytuje nejpřesnější výsledek, téměř odpovídající ideální hodnotě, což ukazuje, jak se tyto strategie zmírnění chyb navzájem posilují.

Celkově srovnání zdůrazňuje, že PNA slouží jako robustní základ pro odhad střední hodnoty s ohledem na šum, zatímco TREX a post-selekce nabízejí doplňková vylepšení pro další zvýšení přesnosti.

Další kroky​

Pokud tě tato práce zaujala, mohl by tě zajímat následující materiál: