PEC dengan TREX
Probabilistic error cancellation (PEC) adalah teknik untuk memitigasi noise kuantum dalam perhitungan expectation value observable. Teknik ini memungkinkan kamu menghitung estimasi expectation value yang tidak bias. Cara kerjanya adalah menjalankan sekumpulan circuit ber-noise yang disuntiki invers dari model noise yang dipelajari. Model noise tersebut kemudian diaproksimasi menjadi kanal Pauli dengan memakai Pauli twirling (juga dikenal sebagai randomized compiling). Model noise seperti ini bisa dipelajari secara efisien dan bisa dimitigasi dengan PEC (lihat Probabilistic error cancellation with sparse Pauli-Lindblad models on noisy quantum processors untuk detail lebih lanjut).
Twirled Readout Error eXtinction (TREX) adalah teknik yang memitigasi error pengukuran dan berguna untuk mengestimasi expectation value observable Pauli. Teknik ini bergantung pada measurement twirling (menyuntikkan gate X secara acak sebelum pengukuran dan membalik hasil yang terukur), yang mendiagonalkan matriks transfer error readout dan membuatnya mudah diinvers. Suku penskalaan ulang dipelajari dari kanal noise diagonal dengan mengukur circuit kalibrasi twirled yang diinisialisasi dalam keadaan nol.
Panduan ini menunjukkan cara memakai PEC untuk memitigasi noise gate dan menggabungkannya dengan TREX untuk juga memitigasi noise pengukuran saat mengestimasi expectation value observable.
Alur kerjanya sebagai berikut:
- Tentukan circuit dan observable.
- Hitung expectation value eksak dengan simulator statevector milik Qiskit.
- Hitung expectation value ber-noise tanpa mitigasi.
- Pelajari noise layer entangling dengan qiskit-noise-learning.
- Siapkan program PEC + TREX dan tambahkan circuit kalibrasi TREX ke dalamnya.
- Bandingkan expectation value hasil mitigasi.
Tentukan circuit dan observable
Siapkan circuit cermin dari evolusi waktu Trotterized sebuah model Ising medan-transversal linear pada delapan qubit. Cerminkan circuit untuk meniadakan unitary circuit dan membentuk circuit identitas. Ini memudahkan kamu menghitung expectation value ideal setiap observable. Selain itu, pencerminan menggandakan kedalaman circuit, yang pada gilirannya menggandakan efek noise.
Gunakan sudut agar semua gate dalam circuit berasal dari grup Clifford, sehingga simulasi lebih cepat dengan memakai simulator 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
Hitung expectation value ber-noise tanpa mitigasi
Sebuah QuantumProgram berisi circuit kuantum dan samplex dalam format yang sesuai untuk primitive Executor. MitigationTask.prepare membangun QuantumProgram dari circuit, observable, dan nilai parameter, lalu membungkus circuit dalam box sesuai opsi boxing yang diberikan. Atur opsi boxing measure_annotations ke "change_basis" untuk memutar pengukuran ke basis yang diperlukan, dan tambahkan add_tags: "unique_box" agar simulator dapat memetakan model noise ke box-box circuit. Program ini di-twirl dengan Pauli secara default, yang pada hardware mengubah noise koheren menjadi noise Pauli yang diasumsikan oleh metode mitigasi. num_randomizations menentukan berapa banyak instance twirled dari setiap circuit yang disampel dan shots_per_randomization menentukan berapa banyak shot yang diterima tiap instance, sehingga hasil kali keduanya adalah batas shot per circuit.
Jika kamu punya akses QPU, kamu bisa menjalankan program dengan Executor yang dibangun dari backend milik QiskitRuntimeService. Contoh ini memakai AerSimulator untuk menjalankan program secara lokal. Opsi simulator layer_noise_model miliknya menyuntikkan model noise Pauli-Lindblad acak pada setiap layer gate entangling, yang dihasilkan dengan random_pauli_lindblad_noise dari qiskit-mitigation dengan error per layered gate (EPLG) sebesar 0.003, tipikal untuk QPU Heron saat ini.
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()
Pelajari noise layer entangling
PEC diawali dengan mempelajari model noise twirled dari setiap layer gate entangling di dalam circuit. Jika kamu mengasumsikan model noise Markovian, kedalaman circuit diasumsikan tidak memengaruhi kanal noise dari suatu layer entangling. Karena itu, hanya model noise dari setiap layer gate entangling yang unik yang perlu dipelajari. Circuit yang dipakai di panduan ini dapat dibagi menjadi dua layer unik. Merancang circuit dengan himpunan minimal layer entangling unik itu penting jika kamu ingin mempelajari model noise, karena layer-layer unik harus dipelajari secara independen. Jika circuit kamu punya terlalu banyak layer unik, pembelajaran noise bisa menjadi tidak layak karena biaya sampling untuk mempelajari banyak layer.
qiskit-noise-learning dipakai untuk mempelajari noise. Learner mengulang setiap layer sebanyak fragment_depths kali di dalam circuit pembelajarannya, men-twirl setiap circuit sebanyak num_randomizations kali, lalu mencocokkan peluruhan fidelity hasil pengukuran ke model Pauli-Lindblad. Parameter-parameter ini dapat memengaruhi akurasi model yang dipelajari. Makin akurat model noise yang dipelajari, makin akurat pula pembatalan noisenya.
Kode berikut menggambar model noise yang dipelajari dengan kedua layer digabung menjadi satu peta. Setiap qubit berupa diagram pie dari laju X, Y, dan Z berbobot-1 yang dijumlahkan pada kedua layer, dan setiap edge menunjukkan laju berbobot-2 yang dipelajari. Misalnya, laju 3e-4 berarti error itu terjadi kira-kira tiga dari setiap 10.000 kali pemakaian layer.
Mitigasi noise readout dilakukan pada post-processing dengan memakai TREX, berbeda dengan model noise gate, yang diperlukan untuk persiapan program agar dapat menyuntikkan anti-noise yang sesuai dengan model noise yang dipelajari. Karena itu, noise readout dapat dijalankan dan dipelajari saat mempelajari noise gate, atau saat menjalankan program yang dimitigasi. Mempelajari noise readout bersamaan dengan noise gate mungkin bermanfaat untuk mempelajari model noise yang konsisten satu sama lain (lihat misalnya Disambiguating Pauli noise in quantum computers). Dalam kasus ketika noise gate dan noise readout dipisahkan dalam model noise, disarankan agar kamu mempelajari noise readout saat menjalankan program yang dimitigasi untuk menghindari drift noise antara saat noise dipelajari dan saat dijalankan.
Dalam panduan ini, noise readout dipelajari saat menjalankan program yang dimitigasi dengan memakai circuit kalibrasi 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()
Siapkan program gabungan PEC dan TREX
Di sini kita menyiapkan task TREX sekaligus PEC, dan menambahkan task TREX sebagai variabel ke task PEC. Ini memastikan semua properti yang diperlukan untuk mitigasi TREX ditambahkan ke item yang dimitigasi PEC (seperti measurement twirling). Noise yang disuntikkan disampel sekali per randomization, sehingga error statistik PEC dikendalikan oleh jumlah randomization, bukan shot per randomization. Jadi, banyak randomization dipakai - masing-masing dengan sedikit shot - sambil menjaga total shot tetap sama dengan program tanpa mitigasi.
Circuit kalibrasi TREX ditambahkan ke program untuk mempelajari noise readout selagi program dijalankan. Circuit kalibrasi TREX sangat dangkal, sehingga randomization tambahan untuknya relatif murah dan memungkinkan noise readout dipelajari dengan lebih akurat.
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)
Periksa shot overhead, jalankan program, lalu lakukan post-processing
PEC menghasilkan estimasi expectation value yang tidak bias, dengan biaya shot overhead yang besar. Shot overhead yang dibutuhkan dapat diperkirakan dengan menghitung faktor gamma circuit, yang mencerminkan efek model noise yang dipelajari pada circuit. Secara default, jumlah randomization pada task PEC diskalakan menurut perkiraan shot overhead yang diperlukan untuk estimasi tanpa bias (~). Untuk menghindari penskalaan otomatis ini, kamu bisa menyetel parameter scale_randomizations_by_gamma ke False. Shot overhead dibatasi oleh parameter max_sampling_overhead.
Kamu sebaiknya meninjau jumlah shot yang dibutuhkan PEC untuk mencapai estimasi tanpa bias sebelum eksekusi untuk memutuskan apakah jumlah itu terjangkau, karena jumlah ini naik secara eksponensial terhadap laju noise dan kedalaman 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
)
Bandingkan expectation value hasil mitigasi
Terakhir, plot expectation value dari setiap observable. PEC + TREX memulihkan expectation value eksak dalam batas error bar-nya, sementara hasil tanpa mitigasi bias jauh di bawahnya.
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()