Покращення очікуваних значень: поглинання поширеного шуму (PNA)
У цьому посібнику ти дізнаєшся, як використовувати найновіші інструменти екосистеми Qiskit для реалізації повністю настроюваного робочого процесу з пом'якшенням помилок. Ми познайомимося з технікою PNA та застосуємо її для пом'якшення помилок Gate. Також ми використаємо TREX для пом'якшення помилок зчитування та постселекцію для пом'якшення помилок, що не охоплені вивченою моделлю шуму.
Зміст
- Коротко розглянемо
PNA - Створимо тротеризований квантовий Circuit та спостережувану величину. Транспілюємо його до Backend і включимо вимірювання постселекції.
- Використаємо
samplomatic, щоб крутити шари двоквбітних Gate і вимірювань. Знайдемо унікальні двоквбітні шари, щоб знизити вартість навчання шуму. - Використаємо
NoiseLearnerV3для вивчення моделі помилок, що впливає на двоквбітні Gate і вимірювання. - Використаємо
qiskit-addon-pnaдля генерації спостережуваної величини, що пом'якшує шум - Використаємо примітив
qiskit-ibm-runtime.Executorдля отримання вихідних зразків QPU, що відображають кожний знімок для кожної рандомізації скручування та виміряного базису - Використаємо
qiskit-addon-utilsдля постобробки даних у пом'якшене очікуване значення.
Що таке поглинання поширеного шуму (PNA)?
Техніка пом'якшення помилок Gate шляхом поширення спостережуваної величини через зворотний канал шуму, що впливає на двоквбітні Gate, що призводить до спостережуваної величини, яка пом'якшує шум.
Двоквбітні Gate в експерименті, який ми хочемо запустити, зазнаватимуть значного шуму.
Якщо ми вивчимо модель шуму, ми можемо застосувати її обернену і скасувати шум.
Замість того, щоб реалізовувати зворотний канал шуму шляхом його вибірки на QPU, як у PEC, ми можемо реалізувати його класично у виміряній спостережуваній величині, використовуючи поширення Паулі. Це призводить до складнішої спостережуваної величини, яка при вимірюванні має ефект пом'якшення вивченого шуму Gate.

Генерація дзеркального Circuit Троттера та спостережуваної величини
Для цього експерименту ми вивчимо часову динаміку 30-вузлової kicked Ising моделі на одновимірному спіновому ланцюжку. Гамільтоніан, що розглядається:
,
де описує взаємодію найближчих сусідніх спінів, , а глобальне поперечне поле, , встановлено рівним . Чим далі від кліффордового кута (тобто ), тим складніше стає поширення анти-шумових генерато рів через Circuit.
Для вибору спостережуваної величини ми розглянемо середню намагніченість одного вузла, , де — кількість вузлів.
# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-addon-pna qiskit-addon-utils qiskit-ibm-runtime samplomatic
import numpy as np
from qiskit import QuantumCircuit
from qiskit.quantum_info import Pauli, SparsePauliOp
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)
circuit.compose(circuit.inverse(), inplace=True)
circuit.measure_active()
circuit.draw("mpl", fold=-1)

Далі ми виберемо ланцюжок Qubit на ibm_kingston, що показують низький рівень помилок, і транспілюємо Circuit до Backend.
from qiskit.transpiler import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService
backend_name = "ibm_kingston"
service = QiskitRuntimeService()
backend = service.backend(backend_name, use_fractional_gates=True)
# Use a chain of low-noise qubits
layout = [
44,
45,
46,
47,
57,
67,
68,
69,
78,
89,
88,
87,
97,
107,
106,
105,
117,
125,
126,
127,
128,
129,
118,
109,
110,
111,
98,
91,
92,
93,
]
pm = generate_preset_pass_manager(backend=backend, initial_layout=layout, optimization_level=0)
isa_circuit = pm.run(circuit)
isa_observable = observable.apply_layout(isa_circuit.layout)
isa_circuit.draw("mpl", fold=-1)
qiskit_runtime_service._discover_account:WARNING:2025-11-10 14:30:57,148: Loading account with the given token. A saved account will not be used.

Скручування шарів двоквбітних Gate і вимірювань та пошук унікальних шарів
Тут ми гарантуємо, що менед жер пропусків анотує блоки з Twirl та InjectNoise анотаціями, що дозволяє нам вивчити шум, який впливатиме на наш Circuit, і пов'язати цей шум з відповідним шаром Circuit.
enable_gates/enable_measure: True: Заблокувати всі шари двоквбітних Gate і кінцеві вимірювання. Одноквбітні Gate будуть ліво-одягнені всередині блоків.measure_annotations: allВключити анотаціїTwirlіChangeBasisна блок вимірюваньtwirling_strategy: active: Скрутити всі активні Qubit у кожному блоці, що містить заплутуючі Gateinject_noise_targets: gates: АнотаціїInjectNoiseмають бути додані до всіх блоків з анотацієюTwirl, що містять заплутуючі Gateinject_noise_strategy: uniform_modification: Усі шари шуму мають бути масштабовані еквівалентно.
from samplomatic.transpiler import generate_boxing_pass_manager
# 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",
remove_barriers=True,
)
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)

Генерація шаблонного Circuit і samplex, визначення способу вибірки Circuit
Тут ми також додаємо вимірювання спостерігачів і постселекції, які потрібні для виконання постселекції на зразках, що виводяться з Executor.
import samplomatic
from qiskit.transpiler import PassManager
from qiskit_addon_utils.noise_management.post_selection.transpiler.passes import (
AddPostSelectionMeasures,
AddSpectatorMeasures,
)
# 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)

Навчання шуму
Перш ніж запускати експерименти, ми навчаємо модель шуму, що впливає на заплутувальні вентилі та вимірювання в схемі. Наявність точної моделі шуму необхідна для ефективного пом'якшення помилок. Навчання шуму безпосередньо перед виконанням експериментів дає найкращі шанси на те, що модель шуму точно описуватиме реальний шум, який впливає на вентилі під час виконання.
Перш ніж навчати шум, нам потрібно знайти унікальні двокубітні шари в нашій схемі, щоб мінімізувати кількість вимірів, потрібних для навчання шуму для всієї схеми. Ми використовуємо find_unique_box_instructions з samplomatic, щоб отримати унікальні шари з обрамленої схеми, включаючи шар вимірювань. Саме ці шари ми передаємо навчальнику шуму.
Коли шари визначено, можна навчати шум. Є кілька параметрів, які варто розглянути:
num_randomizations: Кількість випадкових схем для кожної конфігурації навчальної схемиshots_per_randomization: Загальна кількість вимірів на одну випадкову навчальну схемуlayer_pair_depths: Глибини схеми (вимірювані в кількості пар), що використовуються в навчальних експериментахpost_selection: Ми будемо застосовувати постселекцію на основі ребер під час навчання з використанням вентилівrxдля реалізації поствимірювальних імпульсів
from qiskit_ibm_runtime.noise_learner_v3.noise_learner_v3 import NoiseLearnerV3
from qiskit_ibm_runtime.options import NoiseLearnerV3Options
from samplomatic.utils import find_unique_box_instructions
# Load noise learner data from a shared job
load_saved_nl_result = True
# Noise learning parameters
num_randomizations_nl = 64
shots_per_randomization_nl = 128
strategy = "edge"
enable_postsel = True
x_pulse_type = "rx"
# Find the unique instructions (layers) from boxed-up circuit
unique_2q_layers_and_meas = find_unique_box_instructions(
boxed_circuit, normalize_annotations=None, undress_boxes=True
)
noise_learner_params = {
"num_randomizations": num_randomizations_nl,
"shots_per_randomization": shots_per_randomization_nl,
"layer_pair_depths": [1, 2, 4, 8, 12, 16, 24, 32, 40, 48],
"post_selection": {
"enable": enable_postsel,
"strategy": strategy,
"x_pulse_type": x_pulse_type,
},
"experimental": {},
}
# set the options
noise_learner_options = NoiseLearnerV3Options(**noise_learner_params)
# run the noise learner job
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()
nl_metadata = noise_learner_params | {"layout": layout}
import matplotlib.pyplot as plt
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 0x321dd63f0>

Асоціювання блоків схеми з навченим шумом
Тут ми створюємо відображення між ідентифікаторами посилань InjectNoise кожного блоку та навченою моделлю шуму (PauliLindbladMap), що впливає на заплутувальні вентилі в цьому блоці.
from samplomatic.annotations import InjectNoise
from samplomatic.utils import get_annotation
# 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()