Przejdź do głównej treści

Probabilistyczne anulowanie błędów z przyciemnianymi stożkami świetlnymi

Wprowadzenie

Ten samouczek demonstruje, jak ograniczać błędy za pomocą dodatku Shaded lightcone (SLC). Dodatek ten stanowi rozwinięcie techniki probabilistycznego anulowania błędów (PEC), w której użytkownik uczy się szumu unikalnych warstw w układzie, a następnie anuluje go, stosując bramki jednoQubitowe i techniki post-przetwarzania. W porównaniu z innymi metodami PEC oferuje bardziej solidne ograniczenia na odchylenie zmitygowanego wyniku, ale zazwyczaj wiąże się z wyższym narzutem pod względem czasu QPU. Podczas PEC, aby skompensować osłabienie wartości oczekiwanej przez szum, średni wynik jest skalowany przez czynnik γ=exp(l,σ2λl,σ)\gamma = \exp(\sum_{l,\sigma} 2\lambda_{l,\sigma}), gdzie λl,σ\lambda_{l,\sigma} jest nauczonym współczynnikiem szumu błędu Pauliego σ\sigma w warstwie ll układu. To skalowanie zwiększa wariancję o czynnik γ2\gamma^2, a tym samym mnoży liczbę wykonań układu potrzebnych w QPU przez γ2\gamma^2, co nazywamy kosztem próbkowania lub narzutem próbkowania. Ponieważ γ\gamma rośnie wykładniczo, PEC jest często ograniczone do płytkich lub małoQubitowych układów. Dowiedz się więcej o PEC w artykule Probabilistic error cancellation with sparse Pauli-Lindblad models on noisy quantum processors.

Jeśli możemy zidentyfikować błędy, które nie muszą być mitygowane, możemy wykładniczo zmniejszyć ten koszt próbkowania. Pierwszym krokiem w tym kierunku jest wdrożenie lokalnie świadomej mitygacji błędów, która wykorzystuje szybko obliczalny konwencjonalny „stożek świetlny" do zmniejszenia narzutu PEC poprzez ograniczenie czułości obserwabli na błędy w całym układzie, rozszerzając wykonalność PEC na większe skale dla niektórych problemów. Błędy poza tym stożkiem świetlnym nie mogą wpływać na mierzony wynik i dlatego można je wykluczyć z procedury anulowania błędów. To wykluczenie zmniejsza narzut próbkowania — w niektórych przypadkach znacząco — bez wprowadzania dodatkowego odchylenia. W szczególności, przy pomiarze lokalnej obserwabli OO układu o stałej głębokości, wymagany narzut próbkowania ostatecznie stabilizuje się przy skalowaniu liczby Qubitów w układzie (zob. Fig. 2b w Locality and Error Mitigation of Quantum Circuits.)

Przyciemniane stożki świetlne (SLC) idą dalej, używając symulacji klasycznych, aby dokładniej ograniczyć czułość na błędy w całym układzie. Wymaga to zamiany części czasu QPU na czas CPU i zmniejsza narzut próbkowania potrzebny do renormalizacji odchylenia. Zamiast twardego odcięcia, każdemu potencjalnemu błędowi w układzie przypisywany jest gradientowy „cień", który stanowi górne ograniczenie podatności obserwabli na ten błąd. Ta precyzyjna charakteryzacja umożliwia bardziej efektywne, ukierunkowane zastosowania PEC przy zmniejszonej wariancji, dając jednocześnie użytkownikowi możliwość kontrolowanego strojenia odchylenia w estymacji obserwabli. Więcej szczegółów znajdziesz w artykule Lightcone shading for classically accelerated quantum error mitigation.

Nasz przepływ pracy dla dodatku SLC wykorzystuje nowe środowisko Samplomatic i Executor, umożliwiając użytkownikom bardziej modularną kontrolę ustawień wykonania dla tłumienia i mitygacji błędów przy jednoczesnym zachowaniu łatwości obsługi dla zaawansowanych użytkowników. Aby lepiej zrozumieć korzyści płynące z tego środowiska i jego ogólne funkcje, zapoznaj się z samouczkiem Hello samplomatic.

Przepływ pracy dla przyciemniania stożka świetlnego, uczenia szumu i iniekcji antyszumu

Do modelowania szumu QPU wybraliśmy rzadki model szumu Pauliego-Lindbladiego z 1- i 2-Qubitowymi współczynnikami błędów Pauliego, generowanymi lokalnie na każdym Qubicie i krawędzi urządzenia. Przy tym wyborze przepływ pracy mitygacji błędów SLC przedstawiony w tym samouczku jest następujący:

a. CPU — Ogranicz wpływ poszczególnych błędów 1- i 2-Qubitowych Pauliego

  1. Propagacja wprzód (ograniczenie wpływu na obserwablę). Propaguj każdy błąd do końca układu i oblicz jego komutator z obserwablą.
    • Obcinaj człony operatora podczas ewolucji, aby utrzymać obliczenia w rozsądnych granicach.
    • Dalej zaostrzaj te ograniczenia przez luźną propagację wsteczną obserwabli opartą na kwantowych limitach prędkości.
  2. Propagacja wstecz (ograniczenie wpływu na stan początkowy). Propaguj każdy błąd do początku układu i oblicz jego komutator ze stanem początkowym.

b. QPU — Naucz współczynniki szumu. Użyj NoiseLearner, aby estymować współczynniki modelu szumu Pauliego-Lindbladiego.

c. CPU — Priorytetyzuj mitygację

  1. Zaktualizuj scalone ograniczenia o nauczone współczynniki szumu. Połącz ograniczenia wprzód i wstecz obliczone wcześniej i zaktualizuj je o nauczone współczynniki szumu.
  2. Uszereguj składowe szumu do mitygacji, korzystając z obliczonych ograniczeń i nauczonych współczynników. Priorytetyzuj każdy możliwy błąd szumu na podstawie jego szacowanego wpływu na odchylenie i kosztu korekcji.

d. QPU — Wstaw antyszum i uruchom. Wykonaj układ docelowy z antyszumem (odwrotnym szumem) określonym za pomocą adnotacji Box.

e. CPU — Estymuj obserwablę. Oblicz wartość oczekiwaną, stosując post-selekcję opartą na pomiarach, aby zmniejszyć wpływ szumu nieMarkowskiego.

Przegląd uczenia szumu

Uczenie szumu jest wspólnym krokiem w kilku metodach mitygacji błędów, realizowanym przez NoiseLearner, i można je zobaczyć w naszym samouczku mitygacji błędów PEA, a także w naszym samouczku dotyczącym propagowanej absorpcji szumu (PNA). W NoiseLearnerV3 użytkownik może wyraźnie określić warstwy szumu do nauczenia jako obiekty CircuitInstruction, co pozwala obliczać pożądane ograniczenia szumu SLC dla każdej warstwy w opisany powyżej sposób. Nauczony model Pauliego-Lindbladiego dostarcza współczynników do użycia w priorytetyzacji PEC-SLC. Sposób grupowania bramek w warstwy można określić, używając funkcji pomocniczych generate_boxing_pass_manager i unique_2q_instructions, a następnie podać je do funkcji pomocniczej SLC generate_noise_model_paulis, jak opisano w Kroku 2 poniżej.

Część 1Część 2Część 3
Pauli-twirl two-qubit gate layersRepeat identity pairs of layers and learn noiseDerive a fidelity (error for each noise channel)
paulitwirling.pnglearnlayer.pngcurvefit.png

Przegląd post-przetwarzania

Po wykonaniu na sprzęcie kwantowym przy użyciu środowiska Samplomatic i Executor, konwertujemy nasze pomiary bitstring na pożądaną wartość obserwabli. W przypadku naszego lustrzanego układu Isinga, idealnie otrzymamy zmierzoną obserwablę równą 1, ponieważ wszystkie Qubity powinny idealnie wrócić do swojego stanu początkowego 0\ket{0}. Obliczając wartość obserwabli za pomocą naszej funkcji expectation_values, zastosujemy kilka technik post-przetwarzania, które zmniejszają wpływ szumu. Obejmuje to usuwanie strzałów dotkniętych szumem nieMarkowskim, mitygację błędów odczytu, a także uwzględnienie szczegółów naszej implementacji PEC. Szczegóły omówiono w Kroku 4 poniżej.

Wymagania

Przed rozpoczęciem tego samouczka upewnij się, że masz zainstalowane następujące pakiety:

  • Qiskit IBM Runtime z prymitywem Executor (pip install "qiskit-ibm-runtime @ git+https://github.com/Qiskit/qiskit-ibm-runtime.git")
  • Qiskit addon Shaded lightcone 0.1 (pip install "qiskit-addon-slc~=0.1.0")
  • Qiskit addon utils (pip install "qiskit-addon-utils~=0.3.0")
  • Samplomatic w wersji 0.16 lub nowszej (pip install samplomatic)
  • Obsługa wizualizacji Qiskit (pip install "qiskit[visualization]")

Krok 0. Konfiguracja

Najpierw zaimportuj pakiety i funkcje potrzebne do poprawnego uruchomienia tego notatnika.

# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-addon-slc qiskit-addon-utils qiskit-ibm-runtime samplomatic
import logging

logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(module)s %(message)s")

# Setting this value prevents itertools.starmap deadlock on UNIX systems
from multiprocessing import set_start_method

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 pickle

import numpy as np
import samplomatic
from matplotlib import pyplot as plt
from qiskit import QuantumCircuit
from qiskit.quantum_info import SparsePauliOp
from qiskit.transpiler import PassManager, generate_preset_pass_manager
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
from qiskit_addon_utils.exp_vals.expectation_values import executor_expectation_values
from qiskit_addon_utils.exp_vals.measurement_bases import get_measurement_bases
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_ibm_runtime import Executor, QiskitRuntimeService, QuantumProgram
from qiskit_ibm_runtime.noise_learner_v3 import NoiseLearnerV3
from qiskit_ibm_runtime.options import NoiseLearnerV3Options
from samplomatic.transpiler import generate_boxing_pass_manager
from samplomatic.utils import find_unique_box_instructions

Krok 1. Odwzorowanie problemu

Dla uproszczenia demonstracji wybieramy jednowymiarowy lustrzany łańcuch Isinga. Jednowymiarowy łańcuch Isinga zapewnia wygodnie gęstą strukturę obwodu, co jest przydatne przy prezentacji implementacji PEC. Obwód lustrzany sprawia, że oczekiwany wynik jest oczywisty (mianowicie powinniśmy zmierzyć obserwowalną o wartości 1).

Co więcej, chcemy uruchomić obwód lustrzany, więc dla każdej bramki w drugiej połowie obwodu musi istnieć bramka odwrotna w pierwszej połowie. Ponieważ mierzona obserwowalna <X6Z13><X_6 Z_{13}> zawiera pomiary poza bazą Z, a executor uwzględnia żądaną bazę na końcu obwodu, udostępniamy funkcję prepare_basis, która wstawia odpowiednie bramki na początku obwodu lustrzanego. Ten szczegół jest specyficzny dla naszej demonstracji z obwodem lustrzanym. Funkcja get_measurement_bases pozwala łatwo określić, które bramki są potrzebne i gdzie je dołączyć, a także śledzić subtelności związane z indeksowaniem kubitów wynikające z konwencji stosowanych w adnotacjach box, omówionych w sekcji „Przygotowanie kanonicznych pomiarów bazowych".

num_qubits = 20
target_obs_sparse = [("XZ", [6, 13], 1.0)]
observable = SparsePauliOp.from_sparse_list(target_obs_sparse, num_qubits=num_qubits)
bases_virt, reverser_virt = get_measurement_bases(observable)
num_trotter_steps = 10
rx_angle = np.pi / 4
def construct_ising_circuit(
num_qubits: int, num_trotter_steps: int, rx_angle: float, barrier: bool = True
) -> QuantumCircuit:
circuit = QuantumCircuit(num_qubits)

for _step in range(num_trotter_steps):
circuit.rx(rx_angle, range(num_qubits))
if barrier:
circuit.barrier()
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)
if barrier:
circuit.barrier()

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 in {0, 1}:
continue
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, *, inverse_first: bool = False) -> QuantumCircuit:
mirror_circ = circuit.copy_empty_like()
mirror_circ.compose(circuit.inverse() if inverse_first else circuit, inplace=True)
mirror_circ.barrier()
mirror_circ.compose(circuit if inverse_first else circuit.inverse(), inplace=True)
mirror_circ.measure_active()
return mirror_circ
# Instantiate circuit
circuit = construct_ising_circuit(num_qubits, num_trotter_steps, rx_angle, barrier=False)
mirrored_circuit = mirror_circuit(circuit, inverse_first=True)
mirrored_circuit = prepare_basis(mirrored_circuit, bases_virt[0])
mirrored_circuit.draw("mpl", fold=-1, scale=0.3, idle_wires=False, measure_arrows=False)

Quantum circuit diagram

Krok 2. Optymalizacja

Zoptymalizujemy szczegóły dotyczące uruchamianego układu, mierzonej wartości własnej oraz parametrów uczenia szumów. Na początek upewniamy się, że instancjujemy backend z włączoną opcją bramek ułamkowych. Te bramki ułamkowe pozwolą na większą czułość w niektórych filtrach post-selekcji.

token = "<YOUR_TOKEN>"
instance = "<YOUR_INSTANCE>"

# This is used to retrieve shared results
shared_service = QiskitRuntimeService(
channel="ibm_quantum_platform",
token=token,
instance=instance,
)

# This is used to run on real hardware
service = service = QiskitRuntimeService()
qiskit_runtime_service._discover_account:WARNING:2025-11-10 11:19:40,108: Loading account with the given token. A saved account will not be used.
backend = service.backend("ibm_kingston", use_fractional_gates=True)

Najpierw transpilujemy nasz układ do instrukcji ISA, zgodnie z wymogami wykonania na naszych QPU. W danych zebranych podczas tego eksperymentu ręcznie wybieramy kubity na podstawie oceny łańcucha o najwyższej jakości.

layout = [44, 45, 46, 47, 57, 67, 68, 69, 78, 89, 88, 87, 97, 107, 106, 105, 104, 103, 96, 83]
isa_pm = generate_preset_pass_manager(backend=backend, initial_layout=layout, optimization_level=0)

isa_circuit = isa_pm.run(mirrored_circuit)
assert isa_circuit.layout.final_index_layout() == layout

isa_observable = observable.apply_layout(layout, num_qubits=isa_circuit.num_qubits)
2025-11-10 11:19:57,810 INFO base_tasks Pass: ContainsInstruction - 0.00715 (ms)
2025-11-10 11:19:57,811 INFO base_tasks Pass: UnitarySynthesis - 0.00525 (ms)
2025-11-10 11:19:57,811 INFO base_tasks Pass: HighLevelSynthesis - 0.02599 (ms)
2025-11-10 11:19:57,811 INFO base_tasks Pass: BasisTranslator - 0.09131 (ms)
2025-11-10 11:19:57,811 INFO base_tasks Pass: SetLayout - 0.02623 (ms)
2025-11-10 11:19:57,812 INFO base_tasks Pass: FullAncillaAllocation - 0.14400 (ms)
2025-11-10 11:19:57,812 INFO base_tasks Pass: EnlargeWithAncilla - 0.06318 (ms)
2025-11-10 11:19:57,813 INFO base_tasks Pass: ApplyLayout - 0.29802 (ms)
2025-11-10 11:19:57,813 INFO base_tasks Pass: CheckMap - 0.07820 (ms)
2025-11-10 11:19:57,814 INFO base_tasks Pass: FilterOpNodes - 0.33283 (ms)
2025-11-10 11:19:57,814 INFO base_tasks Pass: UnitarySynthesis - 0.00691 (ms)
2025-11-10 11:19:57,814 INFO base_tasks Pass: HighLevelSynthesis - 0.13208 (ms)
2025-11-10 11:19:57,816 INFO base_tasks Pass: BasisTranslator - 1.00303 (ms)
2025-11-10 11:19:57,818 INFO base_tasks Pass: FoldRzzAngle - 1.78719 (ms)
2025-11-10 11:19:57,818 INFO base_tasks Pass: ContainsInstruction - 0.00691 (ms)
2025-11-10 11:19:57,818 INFO base_tasks Pass: InstructionDurationCheck - 0.00405 (ms)
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
)

Quantum circuit diagram

Opakowywanie układu w pudełka

Dla wygody implementacji skorzystamy z przebiegu transpilacji generate_boxing_pass_manager, który umieszcza instrukcje układu w opatrzonych adnotacjami pudełkach. Te pudełka wyraźnie wskazują miejsca, w których — w przypadku PEC — do układu należy wstrzyknąć antyszum. Szczegóły dotyczące ustawień znajdziesz w dokumentacji Samplomatic.

Zwróć uwagę, że przepływ pracy SLC wymaga użycia inject_noise_strategy="individual_modification" w dalszej części procesu, ponieważ pozwala to na unikalne zidentyfikowanie każdego BoxOp w układzie.

Funkcja find_unique_box_instructions iteruje po podanym opakowaniu układu i identyfikuje te instrukcje, które mają unikalne warstwy 2-kubitowe lub pomiary, na potrzeby uczenia szumów i wstrzykiwania szumów.

# Box circuit with Twirl and InjectNoise annotations
boxes_pm = generate_boxing_pass_manager(
twirling_strategy="active",
inject_noise_strategy="individual_modification",
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
)
2025-11-10 11:20:01,088 INFO base_tasks Pass: RemoveBarriers - 0.02289 (ms)
2025-11-10 11:20:01,100 INFO base_tasks Pass: GroupGatesIntoBoxes - 12.38990 (ms)
2025-11-10 11:20:01,101 INFO base_tasks Pass: GroupMeasIntoBoxes - 0.47898 (ms)
2025-11-10 11:20:01,104 INFO base_tasks Pass: AddTerminalRightDressedBoxes - 2.88177 (ms)
2025-11-10 11:20:01,111 INFO base_tasks Pass: AddInjectNoise - 6.66904 (ms)
boxed_circuit.draw(
"mpl", fold=-1, scale=0.3, idle_wires=False, wire_order=wire_order, measure_arrows=False
)

Quantum circuit diagram

Przygotowanie kanonicznych pomiarów bazowych

Ze względu na sposób etykietowania kubitów podczas identyfikowania unikalnych warstw 2-kubitowych, należy zachować szczególną ostrożność w śledzeniu kolejności kubitów. Poniżej wprowadzamy pojęcie canonical_qubits jako sposób na odpowiednią aktualizację kolejności kubitów przekazywanej do executora, wynikającą z tego, jak kolejność kubitów jest przechwytywana podczas opakowywania układów i znajdowania unikalnych instrukcji. Szczegóły znajdziesz w dokumentacji konwencji kolejności kubitów.

# Determine the canonical qubits order
meas_box = boxed_circuit.data[-1]
canonical_qubits = [
idx for idx, qubit in enumerate(boxed_circuit.qubits) if qubit in meas_box.qubits
]

# map canonical qubit to physical (isa) qubit
c_2_p = {c: p for c, p in enumerate(canonical_qubits)}
# map physical (isa) qubit to virtual qubit (index in original circuit)
p_2_v = {p: v for v, p in enumerate(layout)}
# compute map between virtual and canonical qubit indices.
c_2_v = {c: p_2_v[p] for c, p in c_2_p.items()}

assert len(c_2_v) == num_qubits

bases_canon = [
np.array([base_i[c_2_v[c]] for c in range(num_qubits)], dtype=np.uint8) for base_i in bases_virt
]

Workflow for lightcone shading, noise learning, and anti-noise injection

Uwaga: Na potrzeby implementacji SLC-PEC w tym samouczku uruchamiamy obliczenia granic SLC przed zakończeniem uczenia szumu, tak aby mitigowany układ był wykonany jak najbliżej w czasie do wyuczonego modelu szumu. Zasadniczo ten workflow można dalej usprawnić, wykonując oba kroki równolegle. Mianowicie: zadanie uczenia szumu jest uruchamiane jednocześnie, gdy szacowane są granice szumu. Dla dowolnego obwodu kwantowego obliczanie granic szumu może skalować się ze słabo wykładniczą zależnością. Dlatego korzystne może być użycie zrównoleglonego wykonania, gdy zależy nam na maksymalizacji efektywności workflow. W tym celu krótko to demonstrujemy, uwzględniając zasoby oparte na klastrach (128 wątków) i pokazując, jak można uzyskać dokładniejszy zestaw granic dla danego obwodu przy ograniczonym do równego limitu czasu obliczeniowego w porównaniu do laptopa (8 wątków). Ponadto, choć nie zostało to zaimplementowane w tym workflow, można zrównoleglić wykonania na QPU dla uczenia szumu i obliczeń granic szumu, aby uzyskać najbardziej efektywny workflow.

Predict to-be-learned noise-model Paulis

Funkcja generate_noise_model_paulis przechodzi przez każdą opakowaną warstwę dostarczonego obwodu i generuje wszystkie odpowiednie termy Pauliego wagowego jeden i dwa, uwzględniając spójność qubitów w obwodzie oraz wybierając termy istotne dla aktywnych węzłów i krawędzi. Termy te są następnie używane do obliczania granic szumu w przód i w tył.

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. Compute and tighten forward bounds

Funkcja compute_forward_bounds ocenia relacje komutacji między bramkami w każdej warstwie a powyżej wygenerowanymi termami Pauliego pod kątem tego, jak błędy propagowane w przód wpływają na pożądaną obserwowalną AA. Dla bramek, które komutują z termami Pauliego, nic nie jest robione. Bramki Clifforda są przesuwane w kierunku początku obwodu. Dla bramek nieCliffordowych przybliżamy ich wpływ na docelowe obserwowalne, aby później nadać im priorytet w anulowaniu szumu (po scaleniu wszystkich granic). Granica ta jest osiągana przez pierwsze zastosowanie normy L2 (czyli pierwiastka kwadratowego z sumy kwadratów odpowiednich współczynników termów Pauliego). Gdy zaangażowanych jest zbyt wiele termów qubitowych, wracamy do luźniejszej granicy korzystającej z nierówności trójkąta.

Laptop-level resources

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,
)
2025-11-10 11:20:04,344 INFO forward Evolving Pauli error terms forwards through the circuit.
2025-11-10 11:20:04,344 INFO forward Modelling errors as though they happen *after* each noise layer.
2025-11-10 11:20:04,345 INFO remove_measure Removing ANY Measure operations from the provided circuit!
2025-11-10 11:20:04,453 INFO circuit_iter Noisy box 'm39'
2025-11-10 11:20:05,254 INFO circuit_iter Noisy box 'm38'
2025-11-10 11:20:05,304 INFO circuit_iter Noisy box 'm37'
2025-11-10 11:20:05,382 INFO circuit_iter Noisy box 'm36'
2025-11-10 11:20:05,467 INFO circuit_iter Noisy box 'm35'
2025-11-10 11:20:05,580 INFO circuit_iter Noisy box 'm34'
2025-11-10 11:20:05,705 INFO circuit_iter Noisy box 'm33'
2025-11-10 11:20:05,857 INFO circuit_iter Noisy box 'm32'
2025-11-10 11:20:06,034 INFO circuit_iter Noisy box 'm31'
2025-11-10 11:20:06,221 INFO circuit_iter Noisy box 'm30'
2025-11-10 11:20:06,449 INFO circuit_iter Noisy box 'm29'
2025-11-10 11:20:06,724 INFO circuit_iter Noisy box 'm28'
2025-11-10 11:20:07,628 INFO circuit_iter Noisy box 'm27'
2025-11-10 11:20:09,110 INFO circuit_iter Noisy box 'm26'
2025-11-10 11:20:11,696 INFO circuit_iter Noisy box 'm25'
2025-11-10 11:20:16,100 INFO circuit_iter Noisy box 'm24'
2025-11-10 11:20:21,781 INFO circuit_iter Noisy box 'm23'
2025-11-10 11:20:30,244 INFO circuit_iter Noisy box 'm22'
2025-11-10 11:20:40,416 INFO circuit_iter Noisy box 'm21'
2025-11-10 11:20:53,437 INFO circuit_iter Noisy box 'm20'
2025-11-10 11:21:06,038 INFO circuit_iter Noisy box 'm19'
2025-11-10 11:21:06,038 WARNING commutator_bounds Bounds computation timed out.
2025-11-10 11:21:06,039 INFO circuit_iter Noisy box 'm18'
2025-11-10 11:21:06,039 INFO circuit_iter Noisy box 'm17'
2025-11-10 11:21:06,039 INFO circuit_iter Noisy box 'm16'
2025-11-10 11:21:06,040 INFO circuit_iter Noisy box 'm15'
2025-11-10 11:21:06,040 INFO circuit_iter Noisy box 'm14'
2025-11-10 11:21:06,040 INFO circuit_iter Noisy box 'm13'
2025-11-10 11:21:06,040 INFO circuit_iter Noisy box 'm12'
2025-11-10 11:21:06,041 INFO circuit_iter Noisy box 'm11'
2025-11-10 11:21:06,041 INFO circuit_iter Noisy box 'm10'
2025-11-10 11:21:06,041 INFO circuit_iter Noisy box 'm9'
2025-11-10 11:21:06,042 INFO circuit_iter Noisy box 'm8'
2025-11-10 11:21:06,042 INFO circuit_iter Noisy box 'm7'
2025-11-10 11:21:06,042 INFO circuit_iter Noisy box 'm6'
2025-11-10 11:21:06,042 INFO circuit_iter Noisy box 'm5'
2025-11-10 11:21:06,043 INFO circuit_iter Noisy box 'm4'
2025-11-10 11:21:06,043 INFO circuit_iter Noisy box 'm3'
2025-11-10 11:21:06,043 INFO circuit_iter Noisy box 'm2'
2025-11-10 11:21:06,043 INFO circuit_iter Noisy box 'm1'
2025-11-10 11:21:06,044 INFO circuit_iter Noisy box 'm0'

Visualize the SLC for manual inspection

Możesz interpretować zachowanie zacieniowanych granic, analizując, jak pomiary i termy Pauliego oddziałują z lokalnymi błędami. Te wzorce są charakterystyczne dla tego problemu ewolucji czasowej hamiltonowianu Isinga z wymuszeniem i pojawiają się też w artykule Lightcone Shading for Classically Accelerated Quantum Error Mitigation, z kilkoma charakterystycznymi cechami:

  • Możemy wyraźnie odróżnić dwa stożki wynikające z dwóch nieidentycznych Paulich w obserwowalnej.
  • Możemy zobaczyć, że pomiar X na qubicie 6 komutuje z błędem X w skrajnie prawej warstwie.
  • Możemy zobaczyć, że Pauli Z na qubicie 13 komutuje z błędem Z w skrajnie prawej warstwie.
  • Gdy osiągniemy przekroczenie limitu czasu określonego powyżej, pozostałe warstwy po lewej stronie są wypełnione wyłącznie trywialnymi granicami równymi dwa.
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,
)
)

Quantum circuit diagram

Quantum circuit diagram

Quantum circuit diagram

b. Compute and tighten forward bounds

Następnie zacieśniamy granice, używając funkcji tighten_with_speed_limit, która śledzi, jak obserwowalna rozprzestrzenia się wstecz przez obwód, i wykorzystuje to rozprzestrzenianie do wyznaczenia górnych granic wpływu każdego operatora szumu, przyjmując mniejszą wartość spośród obliczonej wcześniej granicy w przód i granicy wynikającej z propagacji wstecznej.

forward_bounds_tighter = tighten_with_speed_limit(
forward_bounds, boxed_circuit, noise_model_paulis, isa_observable
)
2025-11-10 11:21:08,270 INFO speed_limit Tighting bounds using information propagation speed limits
2025-11-10 11:21:08,270 INFO speed_limit Modelling errors as though they happen *after* each noise layer.
2025-11-10 11:21:08,298 INFO remove_measure Removing ANY Measure operations from the provided circuit!
2025-11-10 11:21:08,310 INFO circuit_iter Noisy box 'm39'
2025-11-10 11:21:08,314 INFO circuit_iter Noisy box 'm38'
2025-11-10 11:21:08,317 INFO circuit_iter Noisy box 'm37'
2025-11-10 11:21:08,319 INFO circuit_iter Noisy box 'm36'
2025-11-10 11:21:08,323 INFO circuit_iter Noisy box 'm35'
2025-11-10 11:21:08,325 INFO circuit_iter Noisy box 'm34'
2025-11-10 11:21:08,328 INFO circuit_iter Noisy box 'm33'
2025-11-10 11:21:08,330 INFO circuit_iter Noisy box 'm32'
2025-11-10 11:21:08,334 INFO circuit_iter Noisy box 'm31'
2025-11-10 11:21:08,336 INFO circuit_iter Noisy box 'm30'
2025-11-10 11:21:08,338 INFO circuit_iter Noisy box 'm29'
2025-11-10 11:21:08,340 INFO circuit_iter Noisy box 'm28'
2025-11-10 11:21:08,344 INFO circuit_iter Noisy box 'm27'
2025-11-10 11:21:08,346 INFO circuit_iter Noisy box 'm26'
2025-11-10 11:21:08,349 INFO circuit_iter Noisy box 'm25'
2025-11-10 11:21:08,351 INFO circuit_iter Noisy box 'm24'
2025-11-10 11:21:08,355 INFO circuit_iter Noisy box 'm23'
2025-11-10 11:21:08,357 INFO circuit_iter Noisy box 'm22'
2025-11-10 11:21:08,360 INFO circuit_iter Noisy box 'm21'
2025-11-10 11:21:08,362 INFO circuit_iter Noisy box 'm20'
2025-11-10 11:21:08,367 INFO circuit_iter Noisy box 'm19'
2025-11-10 11:21:08,369 INFO circuit_iter Noisy box 'm18'
2025-11-10 11:21:08,372 INFO circuit_iter Noisy box 'm17'
2025-11-10 11:21:08,375 INFO circuit_iter Noisy box 'm16'
2025-11-10 11:21:08,378 INFO circuit_iter Noisy box 'm15'
2025-11-10 11:21:08,380 INFO circuit_iter Noisy box 'm14'
2025-11-10 11:21:08,383 INFO circuit_iter Noisy box 'm13'
2025-11-10 11:21:08,386 INFO circuit_iter Noisy box 'm12'
2025-11-10 11:21:08,389 INFO circuit_iter Noisy box 'm11'
2025-11-10 11:21:08,391 INFO circuit_iter Noisy box 'm10'
2025-11-10 11:21:08,394 INFO circuit_iter Noisy box 'm9'
2025-11-10 11:21:08,396 INFO circuit_iter Noisy box 'm8'
2025-11-10 11:21:08,399 INFO circuit_iter Noisy box 'm7'
2025-11-10 11:21:08,401 INFO circuit_iter Noisy box 'm6'
2025-11-10 11:21:08,404 INFO circuit_iter Noisy box 'm5'
2025-11-10 11:21:08,406 INFO circuit_iter Noisy box 'm4'
2025-11-10 11:21:08,410 INFO circuit_iter Noisy box 'm3'
2025-11-10 11:21:08,412 INFO circuit_iter Noisy box 'm2'
2025-11-10 11:21:08,415 INFO circuit_iter Noisy box 'm1'
2025-11-10 11:21:08,417 INFO circuit_iter Noisy box 'm0'

Visualize the SLC for manual inspection

Możemy dalej zacieśniać granice, uwzględniając ograniczenie stożka świetlnego. Zasadniczo daje to nam płynniejsze przejście od obliczonych granic do granic trywialnych wyznaczonych po osiągnięciu limitu czasu. Tutaj efekt nie jest tak widoczny, ponieważ stożki świetlne dotarły już do krawędzi obwodu.

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

Quantum circuit diagram

Quantum circuit diagram

Quantum circuit diagram

c. Compute backward bounds

Ta część predykcji szumu ocenia, jak błąd w konkretnej warstwie może wpłynąć na stan wejściowy ρ\rho. Funkcja compute_backward_bounds najpierw odwraca obwód, usuwa bramki pomiaru, a następnie przeprowadza podobną analizę jak w przypadku obliczeń granic w przód.

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,
)
2025-11-10 11:21:10,666 INFO backward Evolving Pauli error terms backwards through the circuit.
2025-11-10 11:21:10,666 INFO backward Modelling errors as though they happen *after* each noise layer.
2025-11-10 11:21:10,667 INFO remove_measure Removing ANY Measure operations from the provided circuit!
2025-11-10 11:21:10,774 INFO circuit_iter Noisy box 'm0'
2025-11-10 11:21:11,640 INFO circuit_iter Noisy box 'm1'
2025-11-10 11:21:11,681 INFO circuit_iter Noisy box 'm2'
2025-11-10 11:21:11,867 INFO circuit_iter Noisy box 'm3'
2025-11-10 11:21:12,078 INFO circuit_iter Noisy box 'm4'
2025-11-10 11:21:12,329 INFO circuit_iter Noisy box 'm5'
2025-11-10 11:21:12,637 INFO circuit_iter Noisy box 'm6'
2025-11-10 11:21:13,110 INFO circuit_iter Noisy box 'm7'
2025-11-10 11:21:13,705 INFO circuit_iter Noisy box 'm8'
2025-11-10 11:21:14,384 INFO circuit_iter Noisy box 'm9'
2025-11-10 11:21:15,213 INFO circuit_iter Noisy box 'm10'
2025-11-10 11:21:15,946 INFO circuit_iter Noisy box 'm11'
2025-11-10 11:21:16,754 INFO circuit_iter Noisy box 'm12'
2025-11-10 11:21:17,557 INFO circuit_iter Noisy box 'm13'
2025-11-10 11:21:18,447 INFO circuit_iter Noisy box 'm14'
2025-11-10 11:21:19,453 INFO circuit_iter Noisy box 'm15'
2025-11-10 11:21:20,472 INFO circuit_iter Noisy box 'm16'
2025-11-10 11:21:21,479 INFO circuit_iter Noisy box 'm17'
2025-11-10 11:21:22,660 INFO circuit_iter Noisy box 'm18'
2025-11-10 11:21:23,705 INFO circuit_iter Noisy box 'm19'
2025-11-10 11:21:24,849 INFO circuit_iter Noisy box 'm20'
2025-11-10 11:21:26,030 INFO circuit_iter Noisy box 'm21'
2025-11-10 11:21:27,111 INFO circuit_iter Noisy box 'm22'
2025-11-10 11:21:28,354 INFO circuit_iter Noisy box 'm23'
2025-11-10 11:21:29,554 INFO circuit_iter Noisy box 'm24'
2025-11-10 11:21:30,897 INFO circuit_iter Noisy box 'm25'
2025-11-10 11:21:32,113 INFO circuit_iter Noisy box 'm26'
2025-11-10 11:21:33,622 INFO circuit_iter Noisy box 'm27'
2025-11-10 11:21:34,962 INFO circuit_iter Noisy box 'm28'
2025-11-10 11:21:36,504 INFO circuit_iter Noisy box 'm29'
2025-11-10 11:21:38,021 INFO circuit_iter Noisy box 'm30'
2025-11-10 11:21:39,750 INFO circuit_iter Noisy box 'm31'
2025-11-10 11:21:41,237 INFO circuit_iter Noisy box 'm32'
2025-11-10 11:21:42,974 INFO circuit_iter Noisy box 'm33'
2025-11-10 11:21:44,527 INFO circuit_iter Noisy box 'm34'
2025-11-10 11:21:46,535 INFO circuit_iter Noisy box 'm35'
2025-11-10 11:21:48,152 INFO circuit_iter Noisy box 'm36'
2025-11-10 11:21:50,074 INFO circuit_iter Noisy box 'm37'
2025-11-10 11:21:51,814 INFO circuit_iter Noisy box 'm38'
2025-11-10 11:21:53,943 INFO circuit_iter Noisy box 'm39'

Visualize the SLC for manual inspection

Obliczając granice wsteczne, możemy zobaczyć, jak struktura stanu początkowego rządzi wczesnym zachowaniem propagacji błędów:

  • Możemy wyraźnie zobaczyć, jak błędy Z początkowo komutują ze stanem początkowym |0⟩.
  • Tylko na qubicie 6, gdzie inicjalizujemy stan własny +1 bazy X, błąd Z nie komutuje, natomiast błąd X komutuje.
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,
)
)

Quantum circuit diagram

Quantum circuit diagram

Quantum circuit diagram

Preview merged bounds without learned noise rates

Funkcja merged_bounds wyznacza punkt w obwodzie, w którym przełączenie się z granic wstecznych na granice w przód minimalizuje całkowite szacowane odchylenie dla pożądanej obserwowalnej. Odchylenie to jest obliczane jako suma składowych granic wstecznych dla wszystkich lokalizacji szumu przed tym punktem, plus składowe granic w przód dla wszystkich lokalizacji szumu po nim. Aktualnie jest to robione jednolicie dla wszystkich qubitów.

Ważna uwaga: Punkt przełączenia z granic w przód na wsteczne zależy od wyuczonych współczynników szumu.

merged_bounds = merge_bounds(
boxed_circuit,
forward_bounds_tighter,
backward_bounds,
noise_model_rates,
)
2025-11-10 11:21:58,304 WARNING merge Missing noise rates. Partitioning backward/forward commutator bounds by assuming uniform error rates.
2025-11-10 11:21:58,305 WARNING merge Optimal spacetime partitioning not implemented!Just partitioning list of noisy boxes.
2025-11-10 11:21:58,305 INFO merge Determined Box idx for partitioning to be 20.

Wizualizacja SLC do ręcznej inspekcji

Po scaleniu granic wstecznych i zaostrzonych granic przednich zachowanie połączonych SLC staje się jasne:

  • Powyższa funkcja informuje nas, że wybrano punkt podziału, w którym następuje przejście z granic wstecznych na zaostrzone granice przednie.
  • Poniżej możemy zobaczyć, że SLC zawierają teraz częściowe granice wsteczne oraz częściowe zaostrzone granice przednie.
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,
)
)

Quantum circuit diagram

Quantum circuit diagram

Quantum circuit diagram

Zasoby klastrowe

Tutaj pokazujemy, jak wykorzystanie 128 wątków na klastrze pozwala nam propagować przez większą część tego obszerniejszego układu, przy tym samym czasie obliczeniowym co na laptopie.

with open("exp_data/merged_bounds_cluster.pickle", "rb") as file:
merged_bounds_cluster = pickle.load(file)
for p in "XYZ":
display(
draw_shaded_lightcone(
boxed_circuit,
merged_bounds_cluster,
noise_model_paulis,
pauli_filter=p,
scale=0.15,
fold=-1,
idle_wires=False,
wire_order=wire_order,
measure_arrows=False,
)
)

Quantum circuit diagram

Quantum circuit diagram

Quantum circuit diagram

Step 3. Execute

W tej sekcji rozpoczynamy część przepływu pracy, która wymaga użycia prawdziwego urządzenia kwantowego. W przypadku tej metody mitygacji błędów opartej na uczeniu, składa się ona z dwóch kroków:

  1. Uczenie szumu przy użyciu NoiseLeanerV3.
  2. Wykonanie obwodu mitygacji błędów za pomocą nowego frameworka Samplomatic i Estimator.

Mając ograniczone błędy z naszego obwodu kwantowego, musimy poznać powiązane wskaźniki szumu, aby nadać priorytety naszemu budżetowi błędów, określić koszt próbkowania i wykonać obliczenia na QPU. Co więcej, dzięki informacjom o wskaźnikach szumu możemy też pokazać, jak korzystanie z zasobów obliczeniowych naszego klastra pozwala zmniejszyć koszt próbkowania przy zachowaniu tej samej resztkowej wartości błędu systematycznego.

a. Uczenie wskaźników szumu

Learner szumu umożliwia charakteryzację procesów szumowych wpływających na bramki w jednym lub kilku badanych obwodach, na podstawie modelu szumu Pauli-Lindblad opisanego w artykule Probabilistic error cancellation with sparse Pauli-Lindblad models on noisy quantum processors. Metoda run() uruchamia zadanie uczenia szumu dla podanych unikalnych warstw 2-kubitowych, zgodnie z opcjami określonymi w konfiguracji learnera szumu. W tych opcjach możesz dostosować strategię splatania Pauliego, co pomaga zapewnić, że sprzęt jest dobrze opisany modelem szumu Pauli-Lindblad.

Szczegóły twojego modelu szumu są narażone na dryf w czasie. Dlatego ustawiamy parametr, który zapewnia ponowne obliczenie wyuczonego modelu szumu dla eksperymentów starszych niż cztery godziny. Jest to przybliżona reguła praktyczna i należy ją dokładnie rozważyć przed zastosowaniem we własnej pracy.

post_selection_enabled = True
load_cached_noise_results = True
noise_learner_options = NoiseLearnerV3Options(
num_randomizations=64,
shots_per_randomization=128,
layer_pair_depths=[1, 2, 4, 8, 12, 16, 24, 32, 40, 48],
post_selection={
"enable": post_selection_enabled,
"strategy": "edge",
"x_pulse_type": "rx",
},
)

noise_learner = NoiseLearnerV3(backend, noise_learner_options)
if load_cached_noise_results:
noise_learner_job = shared_service.job("d46ssf71gh7s7398k9a0")
else:
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.58
# Get a dict mapping InjectNoise.ref to QubitSparsePaulilist
refs_2_plm = noise_learner_result.to_dict(unique_2q_instructions, require_refs=False)

b.i. Aktualizacja scalonych granic przy użyciu rzeczywiście wyuczonych wskaźników szumu

Teraz, gdy konkretny model szumu został wyuczony, możemy zastosować wyuczone wskaźniki szumu do przewidzianych granic szumu i uzyskać ostateczne określenie, które granice mają największy wpływ na minimalizację błędu systematycznego.

merged_bounds = merge_bounds(
boxed_circuit,
forward_bounds_tighter,
backward_bounds,
refs_2_plm,
)
2025-11-10 11:22:03,755 WARNING merge Optimal spacetime partitioning not implemented!Just partitioning list of noisy boxes.
2025-11-10 11:22:03,756 INFO merge Determined Box idx for partitioning to be 20.

b.ii. Obliczanie local_scales dla wykonania sprzętowego

Funkcja compute_local_scales analizuje każdy możliwy błąd szumu w obwodzie i szacuje, w jakim stopniu ten błąd mógłby wpłynąć na ostateczny pomiar, a także jak kosztowne byłoby jego skorygowanie. Następnie błędy są rangowane według opłacalności ich mitygacji, a wybierany jest podzbiór, który maksymalnie redukuje błąd systematyczny, mieszcząc się w dozwolonym budżecie kosztu próbkowania (lub osiągając pożądaną dokładność). Wynikiem jest zestaw czynników skalowania wskazujących, które błędy będą aktywnie mitygowane, a które pozostaną bez mitygacji (local_scales), wraz z przewidywanym całkowitym narzutem kosztu próbkowania (sampling_costs) i pozostałym ograniczeniem błędu systematycznego (residual_bias_bound).

Możliwość kontrolowania pożądanego pozostałego błędu systematycznego jest kluczową cechą implementacji PEC z SLC. O ile w oryginalnej implementacji narzut próbkowania zawsze dążył do zerowego błędu systematycznego, tutaj możemy dostosować wymagany narzut próbkowania przy pewnym kompromisie w postaci oczekiwanego pozostałego błędu systematycznego. Pomaga to użytkownikowi pozostać w ustalonym budżecie próbkowania, co może być szczególnie przydatne podczas początkowego prototypowania przepływu pracy.

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=128.56055005423153, sampling cost (gamma^2) = 16527.81503024657
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_)
biases_cluster = []
costs_cluster = []
for bias in [0.0, *np.arange(0.001, 0.102, 0.01).tolist()]:
_, cost_, bias_ = compute_local_scales(
boxed_circuit,
merged_bounds_cluster,
refs_2_plm,
sampling_cost_budget=np.inf,
bias_tolerance=bias,
)
biases_cluster.append(bias_)
costs_cluster.append(cost_)

Korzyści z klastrów w zakresie redukcji narzutu próbkowania dla danego czasu obliczeń klasycznych

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="local PEC+SLC")
ax.plot(
100 * np.array(biases_cluster),
np.array(costs_cluster),
"o-",
c="tab:green",
label="cluster PEC+SLC",
)
ax.set_yscale("log")
ax.set_ylim([100, 50000])
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')

Plot output

chosen_bias_thres = 0.1
local_scales, sampling_cost, residual_bias_bound = compute_local_scales(
boxed_circuit,
merged_bounds_cluster,
refs_2_plm,
sampling_cost_budget=np.inf,
bias_tolerance=chosen_bias_thres,
)
print(
f"PEC+SLC sampling cost (gamma^2) = {sampling_cost} w/ remaining bias = {100 * residual_bias_bound:.1f}%"
)
PEC+SLC sampling cost (gamma^2) = 563.1803982530477 w/ remaining bias = 9.3%

c. Wykonanie obwodu z antyszumem

c.i. Przygotowanie szablonowego obwodu przy użyciu samplex

samplex jest wynikiem metody build pakietu Samplomatic i koduje wszystkie informacje potrzebne do wygenerowania losowych parametrów dla template_circuit. Są one następnie używane do konfiguracji obiektów QuantumProgram, które z kolei są uruchamiane na QPU za pomocą prymitywu Executor. Każdy QuantumProgram może zawierać kilka elementów, które można traktować jako parę złożoną z template i samplex.

Szczegóły znajdziesz w samouczku Hello samplomatic.

# 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
2025-11-10 11:22:04,839 INFO base_tasks Pass: AddSpectatorMeasures - 3.41392 (ms)
2025-11-10 11:22:04,843 INFO base_tasks Pass: AddPostSelectionMeasures - 2.88510 (ms)

c.ii. Konfiguracja 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}
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(
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}
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(
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}
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(
circuit=final_template_circuit,
samplex=samplex,
samplex_arguments=samplex_arguments,
shape=(num_randomizations,),
chunk_size=chunk_size,
)

c.iii. Wykonanie programu za pomocą prymitywu Executor

executor = Executor(backend)
load_cached_executor_results = True
if load_cached_executor_results:
job_exec = shared_service.job("d46t1q6qsa9s73cb28g0")
else:
job_exec = executor.run(program)
results_exec = job_exec.result()

Krok 4. Post-processing

Obliczając końcową wartość oczekiwaną za pomocą expectation_values, zastosujemy kilka korzystnych technik post-processingu, aby uzyskać jak najwyższą jakość wyników. Najpierw stosujemy twirled readout mitigation, TREX, który uwzględnia błędy powstające podczas procesu odczytu. Następnie korygujemy błędy wynikające z szumu nie-Markowskiego w naszych backendach Heron, korzystając z metody post-selekcji. Metoda ta mierzy aktywne kubity i kubity-spektatorów, a następnie stosuje powolną rotację każdego kubitu i mierzy ponownie. W przypadkach gdy dwa pomiary nie potwierdzają oczekiwanego obrócenia kubitu, dane strzały są odrzucane przez zastosowanie mask z funkcji PostSelector. Podczas obliczania maski można ustawić konkretną strategię filtrowania opartą na węzłach jednokubitowych lub sąsiadujących krawędziach spektatorów, co może wpływać zarówno na liczbę odfiltrowanych strzałów, jak i na jakość wyników.

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 postselectoion
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 (27.0% of shots kept):
With node-based post selection (26.8% of shots kept):

Na podstawie analizy wyników eksperymentalnych możemy bezpośrednio porównać zachowanie różnych podejść: PEC, PEC połączonego z SLC oraz wyniki bazowe bez mitygacji. Kilka szczegółów wartych podkreślenia:

  • Wyniki bez mitygacji pozostają poza pożądanym zakresem odchylenia i nie są wrażliwe na koszty próbkowania.
  • Ze względu na wysoki koszt próbkowania obliczony powyżej (~10k), samo PEC nie zbiega w granicach użytej liczby randomizacji.
  • PEC + SLC natomiast zbiega znacznie szybciej.
  • Granice błędu również zmniejszają się znacznie szybciej dla PEC + SLC niż dla zwykłego PEC.
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)

Plot output