Перейти до основного вмісту
Цей проєкт у бета-версії. Знайшов(-ла) помилку? Відкрити issue на GitHub. Є запитання чи ідея? Запитати в Discussions — будемо раді твоєму відгуку!
Яка якість перекладу на цій сторінці?

PEC із TREX

Імовірнісне скасування помилок (PEC) — це техніка пом'якшення квантового шуму в обчисленнях очікуваних значень observable. Вона дозволяє обчислити незміщену оцінку очікуваних значень. Ця техніка працює через виконання ансамблю шумних схем, у які введено обернену до вивченої моделі шуму. Потім модель шуму апроксимується каналом Паулі за допомогою твірлінгу Паулі (також відомого як рандомізована компіляція). Такі моделі шуму можна вивчати ефективно та пом'якшувати за допомогою PEC (докладніше див. Probabilistic error cancellation with sparse Pauli-Lindblad models on noisy quantum processors).

PEC

Twirled Readout Error eXtinction (TREX) — це техніка, що пом'якшує помилки вимірювання й корисна для оцінки очікуваних значень observable Паулі. Вона спирається на твірлінг вимірювань (випадкове додавання гейта X перед вимірюванням і інвертування виміряного результату), який діагоналізує матрицю переходів помилок зчитування та полегшує її обернення. Масштабувальний член вивчається з діагонального каналу шуму шляхом вимірювання твірльованих калібрувальних схем, ініціалізованих у нульовому стані.

Measurement twirling

Цей посібник показує, як використовувати PEC для пом'якшення шуму гейтів і поєднати його з TREX, щоб також пом'якшувати шум вимірювання під час оцінки очікуваних значень observable.

Робочий процес такий:

  1. Задай circuit та observable.
  2. Обчисли точні очікувані значення за допомогою симулятора вектора стану Qiskit.
  3. Обчисли шумні очікувані значення без пом'якшення.
  4. Вивчи шум заплутуючих шарів за допомогою qiskit-noise-learning.
  5. Підготуй програму PEC + TREX і додай до неї калібрувальний circuit TREX.
  6. Порівняй пом'якшені очікувані значення.

Задай circuit та observable​

Підготуй дзеркальний circuit тротеризованої часової еволюції лінійної моделі Ізінга з поперечним полем на восьми кубітах. Віддзеркали circuit, щоб скасувати його унітарність і отримати тотожний circuit. Це дозволяє легко обчислити ідеальне очікуване значення кожної observable. До того ж дзеркалення подвоює глибину circuit, що своєю чергою подвоює вплив шуму.

Використай кут θ=π2\theta= \frac{\pi}{2}, щоб усі гейти в circuit належали до групи Кліффорда, що дозволяє швидше моделювання за допомогою симулятора stabilizer.

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

_needed = {"matplotlib": "matplotlib", "numpy": "numpy", "qiskit": "qiskit", "qiskit_aer": "qiskit-aer", "qiskit_ibm_runtime": "qiskit-ibm-runtime", "qiskit_mitigation": "qiskit-mitigation", "qiskit_noise_learning": "qiskit-noise-learning", "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")
import warnings

import numpy as np
from qiskit.circuit import QuantumCircuit
from qiskit.quantum_info import SparsePauliOp

warnings.filterwarnings("ignore")

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 mirror_circuit(circuit: QuantumCircuit) -> QuantumCircuit:
mirror_circ = circuit.copy_empty_like()
mirror_circ.compose(circuit, inplace=True)
mirror_circ.barrier()
mirror_circ.compose(circuit.inverse(), inplace=True)
mirror_circ.measure_active()
return mirror_circ

# define observables
num_qubits = 8
observables = [
SparsePauliOp("I" * ((num_qubits - 1) - q) + "Z" + "I" * q)
for q in range(num_qubits)
]

# Instantiate circuit
num_trotter_steps = 5
rx_angle = np.pi / 2

circuit = construct_ising_circuit(
num_qubits, num_trotter_steps, rx_angle, barrier=False
)
mirrored_circuit = mirror_circuit(circuit)
print("Visualization of 1 mirrored Trotter step")
mirror_circuit(
construct_ising_circuit(num_qubits, 1, rx_angle, barrier=False)
).draw("mpl", scale=0.7)
Visualization of 1 mirrored Trotter step

Output of the previous code cell

Обчисли шумні очікувані значення без пом'якшення​

QuantumProgram містить квантовий circuit і samplex у форматі, придатному для примітива Executor. MitigationTask.prepare будує QuantumProgram зі схеми, observable та значень параметрів і розбиває circuit на бокси із заданими опціями боксингу. Встанови опцію боксингу measure_annotations у "change_basis", щоб повернути вимірювання в потрібні базиси, і додай add_tags: "unique_box", щоб симулятор міг відобразити модель шуму на бокси схеми. Програма за замовчуванням твірлиться за Паулі, що на реальному обладнанні перетворює когерентний шум на шум Паулі, який припускають методи пом'якшення. num_randomizations задає, скільки твірльованих екземплярів кожного circuit семплується, а shots_per_randomization задає, скільки шотів отримує кожен екземпляр, тож їхній добуток — це ліміт шотів на circuit.

Якщо ти маєш доступ до QPU, можеш виконати програму за допомогою Executor, створеного з бекенда QiskitRuntimeService. Цей приклад використовує AerSimulator для локального виконання програми. Його опція симулятора layer_noise_model додає випадкову модель шуму Паулі-Ліндбладу на кожен шар заплутуючих гейтів, згенеровану за допомогою random_pauli_lindblad_noise з qiskit-mitigation із помилкою на шаровий гейт (EPLG) 0.003, типовою для сучасних QPU Heron.

import matplotlib.pyplot as plt
from qiskit.circuit import CircuitInstruction
from qiskit.quantum_info import PauliLindbladMap, pauli_basis
from qiskit.transpiler import PassManager
from qiskit_aer import AerSimulator
from qiskit_ibm_runtime import Executor
from qiskit_mitigation import MitigationTask, find_combined_unique_layers
from qiskit_noise_learning.aer_executor.insert_noise_pass import (
InsertNoisePass,
)
from samplomatic import Tag
from samplomatic.utils import get_annotation

def generate_random_pauli_lindblad_noise(
layers,
num_qubits: int,
eplg: float = 3e-3,
readout_noise_scale: float = 3e-2,
seed: int | None = None,
) -> tuple[
list[tuple[CircuitInstruction, PauliLindbladMap]], PauliLindbladMap
]:
"""Generate random Pauli-Lindblad noise over the full Pauli basis."""
layer_noise_model = []
for layer in layers:
layer_instructions = layer.operation.params[0]
contain_measurement = any(
inst.name.startswith("measure") for inst in layer_instructions
)
if not contain_measurement:
edges = [
(inst.qubits[0]._index, inst.qubits[1]._index)
for inst in layer_instructions
] # extract the edges of the layer

basis_paulis = [p for p in pauli_basis(2) if np.sum(p.x + p.z)]
rng = np.random.default_rng(seed=seed)
num_generators = len(basis_paulis)
rates = rng.random(len(basis_paulis)) * eplg / num_generators

layer_noise = PauliLindbladMap.from_sparse_list(
[
(pauli.to_label(), edge, rate)
for pauli, rate in zip(basis_paulis, rates, strict=True)
for edge in edges
],
num_qubits=num_qubits,
)
layer_noise_model.append((layer, layer_noise))
# generate a readout noise model
rng = np.random.default_rng(seed=seed)
rates = rng.random(num_qubits) * readout_noise_scale
readout_noise_model = PauliLindbladMap.from_sparse_list(
[("X", [q], rate) for q, rate in enumerate(rates)],
num_qubits=num_qubits,
)
return layer_noise_model, readout_noise_model

def add_readout_noise(program, readout_noise_model):
"""Injects readout noise to the circuits in a quantum program."""
readout_noise_map = {}
for item in program.items:
readout_tag = item.circuit[-1].label[
-4:
] # extract the readout layer tag from the circuit
readout_noise_map[readout_tag] = readout_noise_model
item.circuit = PassManager(
[InsertNoisePass(noise_dict=readout_noise_map, noise_after=False)]
).run(item.circuit)

num_randomizations = 300
shots_per_randomization = 64

boxing_options = {"add_tags": "unique_box"}

# find the unique layers to generate the layered noise for
box_layers = find_combined_unique_layers(
[mirrored_circuit], custom_boxing_options=boxing_options
)
layer_noise_model, readout_noise_model = generate_random_pauli_lindblad_noise(
box_layers, num_qubits, seed=42
)

# a local noisy simulator in place of a QPU backend
noisy_executor = Executor(
AerSimulator(method="stabilizer"),
options={"simulator": {"layer_noise_model": layer_noise_model}},
)

task = MitigationTask()
program = task.prepare(
circuit=mirrored_circuit,
observables=observables,
parameters=None,
num_randomizations=num_randomizations,
shots_per_randomization=shots_per_randomization,
custom_boxing_options=boxing_options,
)
# For the simulation - we inject readout noise to the circuits of the program
add_readout_noise(program, readout_noise_model)

program._semantic_role = "simulation" # flag the program for the simulator
unmitigated_res = task.postprocess(noisy_executor.run(program).result())

x_vals = np.array(range(num_qubits))
fig, ax = plt.subplots(figsize=(7, 4))
ax.plot(x_vals, [1] * len(x_vals), "k-", alpha=0.5, label="Exact")
ax.errorbar(
x_vals,
unmitigated_res.data.evs,
unmitigated_res.data.stds,
fmt="o",
label="Unmitigated",
)

x_tick_labels = [r"$Z_{ {i} }$" for i in range(num_qubits)]
ax.set_xlabel(r"$\theta/\pi$")
ax.set_ylabel("Expectation value")
ax.legend()
ax.set_xticks(x_vals, x_tick_labels)
plt.tight_layout()
plt.show()

Output of the previous code cell

Вивчи шум заплутуючих шарів​

PEC починається з вивчення твірльованої моделі шуму кожного шару заплутуючих гейтів у circuit. Якщо припустити марковську модель шуму, вважається, що глибина circuit не впливає на канал шуму заданого заплутуючого шару. Тому слід вивчати лише модель шуму кожного унікального шару заплутуючих гейтів. Circuit, використаний у цьому посібнику, можна розбити на два унікальні шари. Проєктування схем із мінімальним набором унікальних заплутуючих шарів важливе, якщо ти хочеш вивчити модель шуму, оскільки унікальні шари треба вивчати незалежно. Якщо у твоєму circuit забагато унікальних шарів, вивчення шуму може стати нездійсненним через вартість семплування для вивчення багатьох шарів.

qiskit-noise-learning використовується для вивчення шуму. Вивчальник повторює кожен шар fragment_depths разів у своїх навчальних схемах, твірлить кожну схему num_randomizations разів і підганяє спад виміряних точностей до моделі Паулі-Ліндбладу. Ці параметри можуть впливати на точність вивченої моделі. Що точніше вивчена модель шуму, то точнішим буде скасування шуму. Наведений нижче код малює вивчену модель шуму, де два шари об'єднано в одну карту. Кожен кубіт зображено як діаграму-круг із його ваговими-1 швидкостями X, Y і Z, підсумованими за двома шарами, а кожне ребро показує вивчені вагові-2 швидкості. Наприклад, швидкість 3e-4 означає, що помилка виникає приблизно три рази на кожні 10 000 використань шару.

Пом'якшення шуму зчитування виконується в постобробці за допомогою TREX, на відміну від моделі шуму гейтів, яка потрібна для підготовки програми, щоб додати антишум, що відповідає вивченій моделі шуму. Тому шум зчитування можна виконувати та вивчати або під час вивчення шуму гейтів, або під час виконання пом'якшеної програми. Вивчення шуму зчитування разом із шумом гейтів може бути корисним для вивчення самоузгодженої моделі шуму (див., наприклад, Disambiguating Pauli noise in quantum computers). У випадках, коли шум гейтів і шум зчитування розділені в моделі шуму, рекомендується вивчати шум зчитування під час виконання пом'якшеної програми, щоб уникнути дрейфу шуму між моментом вивчення та виконання.

У цьому посібнику шум зчитування вивчається під час виконання пом'якшеної програми за допомогою калібрувального circuit TREX.

from qiskit.providers.fake_provider import GenericBackendV2
from qiskit.quantum_info import PauliList
from qiskit_ibm_runtime.results import LayerError, PauliLindbladError
from qiskit_ibm_runtime.visualization.embeddings import Embedding
from qiskit_mitigation import PEC
from qiskit_noise_learning.aer_executor import AerExecutor
from qiskit_noise_learning.noise_learner import LearningOptions, NoiseLearner
from samplomatic import InjectNoise

line_coupling = [(i, i + 1) for i in range(num_qubits - 1)]
backend = GenericBackendV2(
num_qubits=num_qubits,
basis_gates=["id", "rz", "sx", "x", "cz"],
coupling_map=line_coupling,
)

task_pec = PEC()
# first learn only the layers PEC will inject noise into, i.e. not the measurement layer
pec_layers = [
layer
for layer in task_pec.find_unique_layers(mirrored_circuit, boxing_options)
if get_annotation(layer.operation, InjectNoise)
]
layers = task_pec.find_unique_layers(mirrored_circuit, boxing_options)

learning_options = LearningOptions(
fragment_depths=[2, 4, 8, 16],
num_randomizations=250,
shots_per_randomizations=16,
)

# qiskit-noise-learning is not compatible with the Executor in local mode,
# and requires the simulator present in the package for running simulation
noise_dict = {
get_annotation(layer.operation, Tag).ref: noise_layer
for layer, noise_layer in layer_noise_model
}
noise_learner_simulator = AerExecutor(
qasm_simulator=AerSimulator(method="stabilizer"), noise_dict=noise_dict
)
learner = NoiseLearner(
backend, options=learning_options, executor=noise_learner_simulator
)
learned_noise = (
learner.run(pec_layers).result().to_dict()
) # keyed by InjectNoise reference

# Draw the learned model on the line, laid out as a line with the qubits in order.
# Composing the two layers' maps sums their rates, giving one map over all the edges.
embedding = Embedding(
[(0, 0), (0, 2), (0, 4), (2, 4), (4, 4), (4, 2), (4, 0), (2, 0)],
line_coupling,
)
first, second = learned_noise.values()
learned_model = first.compose(second).simplify()
error = PauliLindbladError(
PauliList(
learned_model.get_qubit_sparse_pauli_list_copy().to_pauli_list()
),
learned_model.rates,
)
fig = LayerError(circuit, list(range(num_qubits)), error).draw_map(
embedding, radius=0.4, height=460, width=620
)
fig.update_traces(
marker={
"cmin": 0,
"cmax": max(learned_model.rates),
"colorbar": {"tickformat": ".1e", "title": "rate"},
},
selector={"mode": "markers"},
)
fig.show()

Output of the previous code cell

Підготуй комбіновану програму PEC і TREX​

Тут ми готуємо і завдання TREX, і завдання PEC, та додаємо завдання TREX як змінну до завдання PEC. Це гарантує, що всі потрібні для пом'якшення TREX властивості (як-от твірлінг вимірювань) додаються до елемента, пом'якшеного PEC. Доданий шум семплується один раз на рандомізацію, тому статистична похибка PEC контролюється кількістю рандомізацій, а не кількістю шотів на рандомізацію. Тож використовується багато рандомізацій — кожна з кількома шотами — зі збереженням загальної кількості шотів такою ж, як для програми без пом'якшення.

Калібрувальний circuit TREX додається до програми, щоб вивчати шум зчитування під час виконання програми. Калібрувальний circuit TREX дуже неглибокий, що робить додаткові рандомізації для нього відносно дешевими та дозволяє вивчати шум зчитування точніше.

from qiskit_mitigation import TREX

trex = TREX()

program_pec = task_pec.prepare(
circuit=mirrored_circuit,
observables=observables,
parameters=None,
num_randomizations=1200,
shots_per_randomization=16,
custom_boxing_options=boxing_options,
trex=trex,
noise_maps=learned_noise, # PEC option
noise_gain=0, # PEC option
max_sampling_overhead=100, # PEC option
)
program_pec = trex.prepare(
num_randomizations=1500,
quantum_program=program_pec,
custom_boxing_options=boxing_options,
)

# For the simulation - we inject readout noise to the circuits of the program
add_readout_noise(program_pec, readout_noise_model)

Перевір витрати шотів, виконай програму та обміркуй результати​

PEC повертає незміщену оцінку очікуваного значення ціною великих витрат шотів. Потрібні витрати шотів можна наблизити, обчисливши гамма-коефіцієнт circuit, який відображає вплив вивченої моделі шуму на circuit. За замовчуванням кількість рандомізацій завдання PEC масштабується за приблизними витратами шотів, потрібними для незміщеної оцінки (~γ2\gamma^2). Щоб уникнути цього автоматичного масштабування, можна встановити параметр scale_randomizations_by_gamma у False. Витрати шотів обмежуються параметром max_sampling_overhead.

Тобі слід переглянути кількість шотів, потрібну PEC для досягнення незміщеної оцінки, перед виконанням, щоб вирішити, чи ця кількість прийнятна, оскільки вона зростає експоненційно зі швидкостями шуму та глибиною circuit.

print(f"The circuit calculated gamma is: {task_pec.gamma}")
print(f"The circuit shot overhead is ~{task_pec.gamma**2}")
The circuit calculated gamma is: 1.2774846896089953
The circuit shot overhead is ~1.631967132185391
program_pec._semantic_role = (
"simulation" # flag the program for the simulator
)
job_pec_results = noisy_executor.run(program_pec).result()

# post-processing
pec_res = task_pec.postprocess(job_pec_results)
# In order to post process the results without TREX, we call `postprocess` again, but instead of the default options we
# pass only the PEC item result and the `measure_noise_data` as `None`
pec_without_trex_res = task_pec.postprocess(
job_pec_results[task_pec._program_item_index], measure_noise_data=None
)

Порівняй пом'якшені очікувані значення​

Нарешті, побудуй графік очікуваного значення кожної observable. PEC + TREX відновлює точні очікувані значення в межах своїх смуг похибок, тоді як результат без пом'якшення значно зміщений униз від них.

x = np.array(range(num_qubits))
x_tick_labels = [r"$Z_{ {i} }$" for i in range(num_qubits)]

fig, ax = plt.subplots(figsize=(8, 5))
ax.errorbar(
x,
unmitigated_res.data.evs,
unmitigated_res.data.stds,
fmt="o",
label="Twirl only",
)
ax.errorbar(
x,
pec_without_trex_res.data.evs,
pec_without_trex_res.data.stds,
fmt="o",
label="PEC only",
)
ax.errorbar(
x,
pec_res.data.evs,
pec_res.data.stds,
fmt="o",
label="PEC + TREX",
)

ax.plot(x, [1] * len(x), "--", color="black", alpha=0.5, label="Exact")

tick_labels = [f"$Z_{ {i} }$" for i in range(num_qubits)]
ax.set_xticks(x, tick_labels)

ax.set_ylabel("Expectation value")
ax.legend()
ax.set_title("PEC+TREX Mitigated Expectation Values")
plt.tight_layout()
plt.show()

Output of the previous code cell