Lewati ke konten utama
Proyek ini masih dalam tahap beta. Menemukan bug? Buat isu GitHub. Punya pertanyaan atau ide? Tanya di Discussions — kami sangat menghargai masukan kamu!
Bagaimana kualitas terjemahan di halaman ini?

Meningkatkan expectation value dengan propagated noise absorption (PNA)

Perkiraan penggunaan: 10 menit pada prosesor Heron (CATATAN: Ini hanyalah perkiraan. Runtime Anda mungkin bervariasi.)

Hasil pembelajaran​

  • Apa itu propagated noise absorption (PNA), dan bagaimana ia memitigasi noise gate dua-qubit dengan menyerap noise channel invers yang telah dipelajari ke dalam observable yang diukur

  • Cara menggunakan samplomatic untuk mengemas dan menganotasi lapisan circuit untuk twirling, perubahan basis, dan injeksi noise

  • Cara mempelajari noise lapisan dengan [NoiseLearnerV3](/guides/directed-execution-model#noiselearnerv3) dan mempropagasikannya ke dalam observabel yang memitigasi noise dengan qiskit-addon-pna

  • Cara mengambil sampel circuit teracak dengan kelas QuantumProgram dan Executor di qiskit-ibm-runtime, serta menggabungkan PNA dengan TREX dan post-selection

Prasyarat​

Latar belakang​

Dalam tutorial ini, kita akan mendemonstrasikan cara memanfaatkan tools error-mitigation tingkat lanjut di Qiskit untuk meningkatkan estimasi expectation value dalam eksperimen kuantum yang noisy.

Apa itu propagated noise absorption (PNA)?​

Propagated noise absorption adalah teknik untuk memitigasi error gate dengan mempropagasikan observable melalui inverse noise channel yang memengaruhi gate dua-qubit, menghasilkan observable yang memitigasi noise.

Kita bisa menggunakan quantum error mitigation untuk mengekstraksi expectation value yang berguna dari hardware kuantum yang noisy tanpa memerlukan full fault tolerance. PNA secara spesifik berfokus pada menyerap efek noise ke dalam observable itu sendiri alih-alih memodifikasi operasi circuit.

Setiap gate yang noisy dalam circuit kuantum bisa dimodelkan sebagai gate ideal yang diikuti oleh noise channel. PNA mempelajari atau mengkarakterisasi noise channel ini dan mendefinisikan inversnya. Alih-alih menyisipkan operasi invers ke dalam eksekusi hardware (yang biasanya tidak feasible), PNA mempropagasikan inverse noise channel maju melalui circuit dan menerapkannya pada observable. Proses ini mengubah observable OO menjadi operator baru O~\tilde{O}, sedemikian rupa sehingga mengukur O~\tilde{O} pada circuit yang noisy menghasilkan expectation value yang sama seperti mengukur OO pada circuit ideal yang bebas noise.

Ini bisa diringkas sebagai berikut:

  1. Memodelkan setiap gate noisy uiu_i sebagai operasi ideal yang diikuti oleh noise channel Λi\Lambda_i.
  2. Mempelajari atau memperkirakan setiap Λi\Lambda_i menggunakan tools karakterisasi noise.
  3. Mendefinisikan dan mempropagasikan inverse noise map Λi−1\Lambda_i^{-1} maju melalui circuit menggunakan teknik Pauli transfer.
  4. Menyerap invers ini ke dalam observable, menghasilkan operator yang dimitigasi-noise O~\tilde{O}.

Ketika noise dideskripsikan sebagai Pauli channel (atau lebih umum sebagai sparse Pauli-Lindblad channel), propagasi ini bisa dilakukan secara efisien menggunakan Pauli propagation. Pauli propagation menyediakan framework untuk mengaproksimasi bagaimana inverse noise channel bertransformasi ketika melewati layer operasi Clifford dan non-Clifford, sambil mengontrol kompleksitas komputasi.

Dengan memindahkan mitigation ke dalam observable, PNA menghindari sampling overhead besar yang seharusnya muncul dari menyisipkan operasi koreksi fisik ke dalam circuit. Sebagai gantinya, circuit noisy yang asli dieksekusi, sementara observable diubah menjadi operator baru O~\tilde{O} yang expectation value-nya membatalkan efek noise.

Alur kerja PNA​

Proses ini bisa dipahami melalui tahapan konseptual berikut. Skema pertama mengilustrasikan eksperimen noisy standar.

Noisy experiment

Jika kita mempelajari noise model, kita bisa menerapkan inversnya dan membatalkan noise.

Noise-mitigated experiment

Alih-alih mengimplementasikan inverse noise channel dengan mengambil sample-nya pada QPU seperti pada probabilistic error cancellation (PEC), kita menerapkannya secara klasik pada observable yang diukur menggunakan Pauli propagation. Observable yang dihasilkan secara efektif memitigasi noise gate yang telah dipelajari saat diukur.

PNA overview

Error mitigation modular dengan Samplomatic dan Executor​

Pendekatan PNA dibangun di atas arsitektur error-mitigation modular di Qiskit. Arsitektur ini menggunakan library samplomatic bersama dengan kelas QuantumProgram dan Executor (ditambahkan ke Qiskit Runtime dalam qiskit-ibm-runtime v0.47.0) untuk membuat teknik seperti propagated noise absorption dan Pauli twirling menjadi composable dan reusable di berbagai eksperimen.

Alih-alih menyematkan logika mitigation di dalam definisi circuit itu sendiri, mitigation diekspresikan secara deklaratif melalui annotation samplomatic dan dikelola secara programatik melalui Executor, yang mengontrol bagaimana circuit acak dihasilkan, dieksekusi, dan diproses lebih lanjut.

Dalam tutorial ini kita mengimplementasikan sebuah Qiskit pattern untuk mendemonstrasikan bagaimana PNA bisa mempropagasikan inverse Pauli noise channel dan memodifikasi observable sesuai kebutuhan, untuk meningkatkan estimasi expectation value pada QPU yang noisy.

Ringkasan alur kerja​

  • Langkah 1: Memetakan ke masalah kuantum

    • Membangun model kicked-Ising Trotterized yang dicerminkan dan sebuah observable target.
  • Langkah 2: Mengkarakterisasi dan mempropagasikan noise

    • Menggunakan samplomatic untuk mengidentifikasi dan mengannotasi layer dua-qubit unik dan measurement dalam circuit.

    • Mempelajari noise yang memengaruhi setiap layer unik menggunakan NoiseLearnerV3.

    • Memetakan setiap annotation InjectNoise ke noise model yang dipelajari yang bersesuaian.

    • Menggunakan qiskit-addon-pna untuk mempropagasikan inverse noise channel maju melalui circuit dan menyerapnya ke dalam observable target.

  • Langkah 3: Mengeksekusi eksperimen kuantum

    • Mendefinisikan sebuah QuantumProgram untuk menentukan randomized sampling melalui samplex dan mengeksekusi eksperimen pada backend menggunakan Executor.
  • Langkah 4: Merekonstruksi dan menganalisis hasil

    • Membandingkan strategi mitigation (PNA, PNA+TREX, PNA+PS, PNA+PS+TREX) dan memvisualisasikan peningkatan dibandingkan hasil yang tidak dimitigasi.

Persyaratan​

Sebelum memulai tutorial ini, pastikan Anda telah menginstal hal berikut:

  • Qiskit SDK v2.2 atau yang lebih baru, dengan dukungan visualization

  • Qiskit Runtime v0.47 atau yang lebih baru (pip install qiskit-ibm-runtime)

  • Samplomatic v0.13 atau yang lebih baru (pip install samplomatic)

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

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

Penyiapan​

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

_needed = {"matplotlib": "matplotlib", "numpy": "numpy", "qiskit": "qiskit", "qiskit_addon_pna": "qiskit-addon-pna", "qiskit_addon_utils": "qiskit-addon-utils", "qiskit_ibm_runtime": "qiskit-ibm-runtime", "samplomatic": "samplomatic"}
_missing = [pip for module, pip in _needed.items()
if importlib.util.find_spec(module) is None]
# One at a time, so a package that fails to install does not block the others
for _pip in _missing:
%pip install -q {_pip}
if not _missing:
print("\u2713 All required packages are installed")
from qiskit import QuantumCircuit
from qiskit.quantum_info import Pauli, SparsePauliOp
from qiskit.transpiler import generate_preset_pass_manager, PassManager
from qiskit_ibm_runtime import (
QiskitRuntimeService,
QuantumProgram,
Executor,
NoiseLearnerV3,
)
from qiskit_addon_utils.exp_vals.measurement_bases import (
get_measurement_bases,
)
from qiskit_addon_utils.exp_vals.expectation_values import (
executor_expectation_values,
)
from qiskit_addon_utils.noise_management import trex_factors
from qiskit_addon_utils.noise_management.post_selection import PostSelector
from qiskit_addon_utils.noise_management.post_selection.transpiler.passes import (
AddPostSelectionMeasures,
AddSpectatorMeasures,
)
from qiskit_addon_pna import generate_noise_mitigating_observable
import samplomatic
from samplomatic.transpiler import generate_boxing_pass_manager
from samplomatic.annotations import InjectNoise
from samplomatic.utils import get_annotation, find_unique_box_instructions
import numpy as np
import matplotlib.pyplot as plt

# Selects a connected chain of low-error qubits on the target backend.
# The mirrored kicked-Ising circuit is a 1D chain, so we only need a line
# of connected physical qubits; the helper walks the backend's coupling map
# and grows a chain along the lowest-error two-qubit edges, so it works for
# any backend rather than relying on a hardcoded layout.
def find_qubit_chain(backend, length):
"""Find a connected chain of ``length`` physical qubits on ``backend``.

The chain is grown greedily along the lowest-error two-qubit edges, so it
favors better-performing qubits. Because the mirrored kicked-Ising circuit
is a 1D chain, a connected line is all we need.
"""
target = backend.target

# Identify the native two-qubit gate and build a per-edge error lookup.
two_qubit_gate = next(
name
for name in target.operation_names
if target[name]
and all(q is not None and len(q) == 2 for q in target[name])
)
edge_error = {
frozenset(qargs): (
1.0 if props is None or props.error is None else props.error
)
for qargs, props in target[two_qubit_gate].items()
}

graph = backend.coupling_map.graph.to_undirected(multigraph=False)
neighbors = {n: list(graph.neighbors(n)) for n in graph.node_indices()}

def first_chain_from(start):
path, visited = [start], {start}

def grow():
if len(path) == length:
return True
node = path[-1]
order = sorted(
neighbors[node],
key=lambda m: edge_error.get(frozenset((node, m)), 1.0),
)
for nxt in order:
if nxt not in visited:
visited.add(nxt)
path.append(nxt)
if grow():
return True
path.pop()
visited.remove(nxt)
return False

return path if grow() else None

def chain_cost(path):
return sum(
edge_error.get(frozenset((path[i], path[i + 1])), 1.0)
for i in range(len(path) - 1)
)

# Try low-degree qubits first (the natural ends of long chains) and keep
# the lowest-error chain found.
best_path, best_cost = None, float("inf")
for start in sorted(neighbors, key=lambda n: len(neighbors[n])):
chain = first_chain_from(start)
if chain is not None and (cost := chain_cost(chain)) < best_cost:
best_path, best_cost = chain, cost

if best_path is None:
raise ValueError(
f"Could not find a connected chain of {length} qubits "
f"on '{backend.name}'."
)
return best_path

Contoh simulator skala kecil​

PNA memitigasi noise gate dua-qubit fisik dari prosesor kuantum tertentu. Alur kerja ini bergantung pada dua layanan hardware yang tidak memiliki analog yang berarti pada simulator ideal:

  • NoiseLearnerV3 mengkarakterisasi secara eksperimental sparse Pauli-Lindblad noise channel yang melekat pada setiap layer dua-qubit unik dari circuit yang telah ditranspilasi. Pada simulator tanpa noise, tidak ada noise yang perlu dipelajari, dan propagated inverse channel akan menjadi identity.

  • Primitive Executor mengambil sample dari circuit acak yang di-twirl yang dihasilkan oleh samplomatic pada sebuah backend.

Pada prinsipnya Anda bisa menggantinya dengan noise model sintetis. Misalnya, Anda bisa menempelkan instruksi PauliLindbladError dengan Qiskit Aer dan meneruskan circuit noisy yang dihasilkan langsung ke generate_noise_mitigating_observable. Namun, ini hanya memvalidasi pembukuan klasik terhadap noise yang Anda suntikkan sendiri, dan mengaburkan inti dari teknik ini. Karena itu kita melewati contoh simulator dan mendemonstrasikan alur kerja PNA lengkap langsung pada hardware, dengan setiap langkah dari Qiskit pattern diuraikan di bawah ini.

Contoh hardware skala besar​

Kita sekarang menjalankan alur kerja PNA lengkap pada model kicked Ising 30-site yang dieksekusi pada hardware IBM Quantum®, mengikuti empat langkah dari sebuah Qiskit pattern.

Langkah 1: Memetakan ke masalah kuantum​

Membuat circuit Trotter cerminan dan observable

Untuk eksperimen ini, kita akan mempelajari dinamika waktu dari model Ising kicked 30-site pada rantai spin 1D. Hamiltonian yang dipertimbangkan adalah:

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

di mana J>0J>0 menggambarkan coupling dari spin tetangga terdekat, i<ji<j, dan medan transversal global, hh, diatur ke π8\frac{\pi}{8}. Semakin jauh hh dari sudut Clifford (yaitu, θ=nπ2,n∈Z\theta=n\frac{\pi}{2}, n \in \mathbb{Z}), semakin sulit untuk mempropagasi generator anti-noise melalui circuit tersebut.

Untuk pilihan observable, kita akan mempertimbangkan magnetisasi rata-rata single-site, 1N∑i=1N⟨zi⟩\frac{1}{N} \sum_{i=1}^{N} \langle z_i \rangle, di mana NN adalah jumlah site.

num_qubits = 30
num_trotter_steps = 10
rx_angle = np.pi / 8

# Avg single-site magnetization
id_pauli = Pauli("I" * num_qubits)
observable = (
SparsePauliOp([id_pauli.dot(Pauli("Z"), [i]) for i in range(num_qubits)])
/ num_qubits
)

# Implement Trotterized kicked-Ising model
circuit = QuantumCircuit(num_qubits)
for _step in range(num_trotter_steps):
circuit.rx(rx_angle, range(num_qubits))
for first_qubit in (1, 2):
for idx in range(first_qubit, num_qubits, 2):
# equivalent to Rzz(-pi/2):
circuit.sdg([idx - 1, idx])
circuit.cz(idx - 1, idx)
# Append the inverse circuit to complete the mirroring
circuit.compose(circuit.inverse(), inplace=True)
circuit.measure_active()
circuit.draw("mpl", fold=-1)

Output of the previous code cell

Langkah 2: Optimalkan masalah untuk eksekusi hardware​

Langkah selanjutnya adalah menyiapkan circuit Trotter cerminan kita untuk eksekusi pada hardware IBM Quantum yang sebenarnya. Menjalankan pada QPU membutuhkan lebih dari sekadar membangun circuit abstrak, karena kita perlu mengoptimalkannya sehingga:

  1. Circuit menghormati native gate set dan konektivitas backend. Transpilasi memetakan circuit logis menjadi circuit ISA yang didukung oleh backend target. Ini memastikan setiap gate dan interaksi qubit dapat direalisasikan secara fisik.

  2. Kita dapat mengkarakterisasi noise pada level lapisan circuit. PNA bergantung pada pembelajaran dan propagasi inverse noise channel. Untuk melakukan ini secara efisien, kita memecah circuit yang telah ditranspilasi menjadi lapisan two-qubit "boxed" yang unik. Box ini memungkinkan kita mengasosiasikan setiap lapisan circuit dengan model noise hasil pembelajarannya masing-masing.

  3. Kita dapat memasukkan model noise yang realistis ke dalam PNA. Setelah circuit di-box, kita menggunakan layanan NoiseLearnerV3 untuk mempelajari secara eksperimental channel Pauli noise yang memengaruhi setiap lapisan two-qubit unik. Model yang dipelajari ini kemudian dihubungkan kembali ke circuit melalui anotasi Samplomatic.

Secara keseluruhan, langkah ini menjembatani kesenjangan antara circuit cerminan yang ideal dengan circuit siap-hardware yang memiliki model noise yang dipelajari. Dengan setup ini, PNA dapat mempropagasi channel inverse noise melalui circuit dan menyesuaikan observable sesuai kebutuhan.

Hubungkan ke backend dan transpilasi menjadi circuit ISA​

Pertama, kita menginisialisasi IBM Quantum Compute Service dan memilih backend. Autentikasi dengan akun Anda sendiri dengan mengikuti instruksi untuk menyimpan kredensial Anda, setelah itu QiskitRuntimeService() akan mengambilnya secara otomatis.

# Initialize IBM Quantum Compute Service using your saved credentials
service = QiskitRuntimeService()

backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=num_qubits
)
# Re-fetch with fractional gates enabled (least_busy does not forward this)
# Fractional gates are enabled so the non-Clifford Rx rotations are supported natively.
backend = service.backend(backend.name, use_fractional_gates=True)
print(f"Selected backend: {backend.name}")
Selected backend: ibm_fez

Selanjutnya, kita memilih rantai qubit yang terhubung pada backend dan mentranspilasi circuit ke dalamnya.

Dengan menggunakan helper find_qubit_chain yang didefinisikan di bagian Setup, kita memilih sebuah garis dari num_qubits qubit fisik yang terhubung. Kita kemudian mentranspilasi dengan optimization_level=0 dan menetapkan rantai ini sebagai initial_layout, yang mempertahankan struktur lapisan two-qubit-gate dari circuit cerminan secara persis. Struktur itulah yang diandalkan oleh langkah boxing dan noise-learning, sehingga optimization level yang lebih tinggi (yang akan membatalkan gate cerminan) harus dihindari di sini.

# Find a connected, low-error chain of qubits on the chosen backend
layout = find_qubit_chain(backend, num_qubits)

# Transpile the circuit for the target backend, pinning the chain as the layout.
# optimization_level=0 preserves the mirrored two-qubit-gate layers that the
# boxing and noise-learning steps rely on.
pm = generate_preset_pass_manager(
backend=backend, optimization_level=0, initial_layout=layout
)
isa_circuit = pm.run(circuit)
isa_observable = observable.apply_layout(isa_circuit.layout)
isa_circuit.draw("mpl", fold=-1)

Output of the previous code cell

Twirl lapisan two-qubit gate dan pengukuran, serta temukan lapisan unik​

Kita menggunakan samplomatic untuk mem-box circuit dan mengidentifikasi lapisan two-qubit yang unik. Box adalah sebuah konstruk yang mengelompokkan instruksi bersama-sama sehingga intent atau anotasi tertentu dapat kemudian diterapkan secara seragam ke semua gate dalam box yang sama.

Di sini, kita memanggil metode generate_boxing_pass_manager, yang melakukan lebih dari sekadar mengidentifikasi lapisan two-qubit. Metode ini melakukan beberapa tugas utama:

  • Mengelompokkan semua lapisan two-qubit dalam circuit,

  • Menerapkan anotasi Twirl dan ChangeBasis pada lapisan-lapisan tersebut,

  • Mengelompokkan operasi pengukuran ke dalam bagian boxed-nya sendiri, dan

  • Menerapkan anotasi InjectNoise pada setiap lapisan two-qubit.

Anotasi ini mendefinisikan bagaimana noise, perubahan basis, dan twirling ditangani di seluruh circuit. Anotasi ini juga menetapkan struktur yang digunakan nanti untuk mempelajari dan memitigasi noise.

Opsi konfigurasi utama adalah:

  • enable_gates/enable_measures: True: Mem-box semua lapisan two-qubit gate dan pengukuran terminal. Single-qubit gate di-left-dressed di dalam box.

  • measure_annotations: all: Menyertakan anotasi Twirl dan ChangeBasis pada box pengukuran.

  • twirling_strategy: active: Meng-twirl semua qubit aktif dalam setiap box yang berisi entangling gate.

  • inject_noise_targets: gates: Menambahkan anotasi InjectNoise pada semua box yang beranotasi Twirl yang berisi entangling gate.

  • inject_noise_strategy: uniform_modification: Menskalakan semua lapisan noise secara setara di seluruh circuit.

# Box up circuit with Twirl and InjectNoise annotations
pm = generate_boxing_pass_manager(
enable_gates=True,
enable_measures=True,
measure_annotations="all",
twirling_strategy="active",
inject_noise_targets="gates",
inject_noise_strategy="uniform_modification",
)
boxed_circuit = pm.run(isa_circuit)
draw_circ = QuantumCircuit(boxed_circuit.num_qubits)
draw_circ.append(boxed_circuit.data[0], qargs=boxed_circuit.data[0].qubits)
draw_circ.append(boxed_circuit.data[1], qargs=boxed_circuit.data[1].qubits)
draw_circ.draw("mpl", fold=-1, scale=0.3, idle_wires=False)

Output of the previous code cell

Membuat circuit template dan samplex, yang mendefinisikan bagaimana circuit akan disampel.

Di sini kita juga menambahkan pengukuran spectator dan post-selection, yang diperlukan untuk melakukan post-selection pada sampel yang dikeluarkan dari Executor.

# Build template circuit and samplex for later use with the "Executor"
template_circuit, samplex = samplomatic.build(boxed_circuit)

# Add post-selection instructions to the template circuit
post_selection_pm = PassManager(
[
AddSpectatorMeasures(backend.coupling_map),
AddPostSelectionMeasures(x_pulse_type="rx"),
]
)
template_circuit = post_selection_pm.run(template_circuit)
draw_circ = template_circuit.copy_empty_like()
draw_circ.data = template_circuit.data[:324]
draw_circ.draw("mpl", fold=-1, scale=0.3, idle_wires=False)

Output of the previous code cell

Pelajari noise menggunakan NoiseLearnerV3​

Sebelum kita dapat menerapkan PNA untuk mitigasi error, kita pertama-tama perlu mengkarakterisasi noise yang bekerja pada setiap lapisan two-qubit unik dan lapisan pengukuran dalam circuit kita. Untuk melakukan ini, kita menggunakan program NoiseLearnerV3 untuk mempelajari secara eksperimental model noise untuk setiap lapisan yang diidentifikasi sebelumnya. Learner ini melakukan eksperimen bergaya benchmarking yang mengestimasi channel noise yang memengaruhi setiap lapisan dan mengembalikan objek hasil yang berisi model yang dipelajari.

Kita mulai dengan mengidentifikasi lapisan unik dalam circuit kita menggunakan find_unique_box_instructions dari samplomatic. Ini memastikan bahwa kita hanya mempelajari noise sekali per tipe lapisan yang berbeda, meminimalkan jumlah eksperimen dan total biaya shot. Daftar lapisan yang dihasilkan kemudian diteruskan ke noise learner.

Ada beberapa parameter kunci yang mengontrol bagaimana noise dipelajari:

  • num_randomizations: Jumlah circuit acak yang digunakan per konfigurasi pembelajaran.

  • shots_per_randomization: Jumlah shot yang diambil per circuit pembelajaran acak.

  • layer_pair_depths: Kedalaman circuit (diukur dalam jumlah pasangan) yang digunakan dalam eksperimen pembelajaran.

  • post_selection: Mengaktifkan post-selection berbasis edge menggunakan gate rx untuk menerapkan pulsa pasca-pengukuran.

# Noise learning parameters
num_randomizations_nl = 64
shots_per_randomization_nl = 128

# Match the deepest noise-learning depth to the depth of the circuit being
# mitigated. ``layer_pair_depths`` are measured in layer pairs (a layer + its
# inverse), so the deepest value is half the circuits's two-qubit-layer depth.
# Learning to this depth improves the quality of the mitigation.

depth_2q = isa_circuit.depth(lambda instr: instr.operation.num_qubits == 2)
max_layer_pair_depth = depth_2q // 2

# Use a fixed schedule of learning depths, but drop any that exceed the circuit's
# depth and always cap the deepest value at ``max_layer_pair_depth`` so we never
# learn deeper than the circuit being mitigated
candidate_depths = [1, 2, 4, 8, 12, 16, 24, 32, 40, 48]
layer_pair_depths = sorted(
{d for d in candidate_depths if d < max_layer_pair_depth}
| {max_layer_pair_depth}
)

# Find the unique instructions (layers) from the boxed-up circuit
unique_2q_layers_and_meas = find_unique_box_instructions(
boxed_circuit, normalize_annotations=None, undress_boxes=True
)

# Configure and run the noise learner on the unique layers.
# Options can be passed directly as a dictionary.
noise_learner_options = {
"num_randomizations": num_randomizations_nl,
"shots_per_randomization": shots_per_randomization_nl,
"layer_pair_depths": layer_pair_depths,
"post_selection": {
"enable": True,
"strategy": "edge",
"x_pulse_type": "rx",
},
"environment": {"job_tags": ["TUT_PNA"]},
}

noise_learner = NoiseLearnerV3(backend, noise_learner_options)
noise_learner_job = noise_learner.run(unique_2q_layers_and_meas)
noise_learner_result = noise_learner_job.result()

Visualisasikan tingkat noise yang dipelajari​

Setelah mempelajari model noise, kita dapat memeriksa distribusi tingkat error yang disimpulkan untuk operasi satu-qubit dan dua-qubit. Kode di bawah ini mengekstrak representasi Pauli-Lindblad dari hasil noise yang dipelajari dan mengumpulkan tingkat noise yang bersesuaian.

Untuk setiap lapisan yang dipelajari:

  • Kita mengonversi model noise menjadi daftar sparse dari tuple (pstr, qubits, rate), di mana pstr adalah string Pauli yang bekerja pada qubit yang diberikan dan rate adalah tingkat error terkait.
  • Kita memisahkan tingkat tersebut menjadi suku satu-qubit (len(pstr) == 1) dan dua-qubit (len(pstr) == 2).
  • Daftar tingkat tersebut kemudian diurutkan, dan nilai mediannya dihitung.

Kita memplot distribusi tingkat noise satu-qubit (merah) dan dua-qubit (biru) pada skala logaritmik, dengan nilai median masing-masing ditandai dengan garis vertikal, sehingga kita dapat membandingkan besaran relatif dari generator Pauli-Lindblad yang dipelajari. Urutan tingkat satu- dan dua-qubit bergantung pada perangkat dan pada lapisan spesifik yang dikarakterisasi; dalam run ini, generator satu-qubit (weight-1) memiliki tingkat median yang lebih besar.

hw_rates_1q = []
hw_rates_2q = []
for nlr in noise_learner_result[:2]:
plm_list = nlr.to_pauli_lindblad_map().to_sparse_list()
hw_rates_1q += [
rate for (pstr, qubits, rate) in plm_list if len(pstr) == 1
]
hw_rates_2q += [
rate for (pstr, qubits, rate) in plm_list if len(pstr) == 2
]
hw_rates_1q = sorted(hw_rates_1q)
hw_rates_2q = sorted(hw_rates_2q)
median_1q = hw_rates_1q[len(hw_rates_1q) // 2]
median_2q = hw_rates_2q[len(hw_rates_2q) // 2]
fig, ax = plt.subplots(1, 1, figsize=(14, 5))
ax.scatter(
(hw_rates_1q),
[(i) / (len(hw_rates_1q) - 1) for i in range(len(hw_rates_1q))],
color="red",
label="1q rates",
)
ax.set_xscale("log")
ax.set_ylim(0, 1.1)
ax.vlines(median_1q, 0, 1, color="red")
ax.text(median_1q * 1.1, 0.1, f"{median_1q:.2e}")
ax.scatter(
(hw_rates_2q),
[(i) / (len(hw_rates_2q) - 1) for i in range(len(hw_rates_2q))],
color="blue",
label="2q rates",
)
ax.set_xscale("log")
ax.set_ylim(0, 1.1)
ax.vlines(median_2q, 0, 1, color="blue")
ax.text(median_2q * 1.1, 0.2, f"{median_2q:.2e}")
ax.set_title("Learned noise rates")
ax.set_xlabel("Noise rate")
ax.set_yticks([])
plt.legend()
<matplotlib.legend.Legend at 0x125336120>

Output of the previous code cell

Kaitkan box circuit dengan noise yang dipelajari​

Setelah kita memperoleh model noise untuk setiap lapisan two-qubit yang unik, kita perlu menghubungkannya dengan anotasi InjectNoise yang bersesuaian di dalam circuit yang telah di-box.

Direktif InjectNoise adalah anotasi samplomatic yang menggunakan dresser single-qubit untuk menyuntikkan noise ke dalam circuit dengan cara yang terkontrol dan dapat dikonfigurasi. Ini memungkinkan pemodelan noise modular di berbagai lapisan.

Setiap anotasi InjectNoise mencakup:

  • InjectNoise.ref - sebuah identifier unik untuk anotasi tersebut. Ini digunakan oleh objek samplex untuk menetapkan model noise yang bersesuaian dengan benar.
  • InjectNoise.modifier_ref (opsional) - sebuah referensi sekunder yang memungkinkan penskalaan model noise yang ditetapkan dengan faktor pengali.

Pada langkah ini, kita membangun sebuah pemetaan dari setiap InjectNoise.ref ke model noise yang dipelajari yang bersesuaian (PauliLindbladMap). Pemetaan ini memastikan bahwa setiap lapisan entangling gate dalam circuit dipasangkan dengan model noise yang tepat, sehingga efek noise diterapkan secara akurat selama sampling dan langkah-langkah mitigasi noise selanjutnya.

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

Propagasikan observable melalui anti-noise yang dipelajari​

Seperti dibahas di atas, ini dilakukan dalam dua langkah. Pertama, kita mempropagasi generator anti-noise ke akhir circuit. Setelah itu, kita mempropagasi observable melalui generator yang telah berevolusi tersebut. Proses ini diulang untuk setiap generator anti-noise dalam circuit. Dalam implementasi ini, setiap generator dalam lapisan tertentu dipropagasi ke akhir circuit secara paralel. Selain itu, Python multiprocessing digunakan untuk melakukan baik forward-propagation dari anti-noise maupun back-propagation dari observable secara paralel. Ini mencegah penumpukan generator yang telah berevolusi di memori dan juga memaksimalkan sumber daya komputasi.

Saat menjalankan PNA, Anda selalu perlu menyediakan sebuah circuit yang noisy beserta observable-nya. Jika circuit noisy Anda adalah circuit yang di-box dengan anotasi InjectNoise, Anda perlu menyediakan pemetaan yang kita buat pada langkah di atas. Anda juga dapat meneruskan circuit yang tidak di-box yang berisi instruksi PauliLindbladError dari qiskit-aer. Dalam kasus tersebut, refs_to_noise_models tidak perlu disediakan. Selain input utama, pertimbangkan juga hal-hal berikut:

  • max_err_terms: Jumlah suku yang dipertahankan pada setiap generator anti-noise saat dipropagasi maju. Membiarkan ini lebih besar umumnya meningkatkan akurasi, tetapi perilaku ini tidak dijamin monotonik.

  • max_obs_terms: Jumlah suku yang dipertahankan pada observable pemitigasi-noise, O~\tilde{O}, saat dipropagasi mundur melalui anti-noise yang telah berevolusi. Nilai yang lebih besar umumnya meningkatkan akurasi, tetapi tidak dijamin melakukannya secara monotonik.

  • num_processes: Jumlah core yang didedikasikan untuk proses tersebut. Ingat, generator dipropagasi maju dan diterapkan pada observable secara paralel.

  • search_step: Langkah back-propagation menggunakan metode greedy untuk mengonjugasi secara aproksimasi dua operator dalam basis Pauli. Metode ini dapat dipercepat dengan meningkatkan search_step. Lihat dokumentasi pauli-prop untuk informasi lebih lanjut.

  • num_to_measure: Meskipun variabel ini bukan input untuk generate_noise_mitigating_observable, kita menggunakannya untuk mengontrol berapa banyak suku dari O~\tilde{O} yang sebenarnya ingin kita ukur. Di sini kita hanya mengukur 30 suku teratas, yang merupakan suku asli dalam observable kita. Suku-suku tersebut kini telah diskalakan ulang sedemikian rupa sehingga mengukurnya memiliki efek memitigasi noise gate yang dipelajari. Meskipun kita hanya mengukur 30 suku dari O~\tilde{O}, seringkali masih berguna untuk membiarkannya tumbuh besar, karena hal itu meningkatkan presisi faktor skala dari suku-suku terdepan.

# PNA parameters
num_processes = 8
max_err_terms = 10_000
max_obs_terms = 10_000
num_to_measure = num_qubits

obs_tilde_isa = generate_noise_mitigating_observable(
boxed_circuit,
isa_observable,
refs_to_noise_models,
max_err_terms=max_err_terms,
max_obs_terms=max_obs_terms,
num_processes=num_processes,
print_progress=True,
search_step=8,
)
p_2_v = {p: v for v, p in enumerate(layout)}
obs_tilde_virtual = SparsePauliOp.from_sparse_list(
[
(pstr, [p_2_v[p] for p in p_qubits], coeff)
for (pstr, p_qubits, coeff) in obs_tilde_isa.to_sparse_list()
],
num_qubits=num_qubits,
)
obs_tilde_virtual = obs_tilde_virtual[
np.argsort(np.abs(obs_tilde_virtual.coeffs))[::-1]
][:num_to_measure]
Finished! 13740 / 13740 generators propagated.
obs_tilde_isa = obs_tilde_isa[np.argsort(np.abs(obs_tilde_isa.coeffs))][::-1]
plt.xscale("log")
plt.yscale("log")
plt.title(r"$\tilde{O}$ coeff magnitudes")
plt.ylabel("Magnitude")
plt.xlabel("Pauli term index")
plt.plot(np.abs(obs_tilde_isa.coeffs), ".")
[<matplotlib.lines.Line2D at 0x124b0a120>]

Output of the previous code cell

Ubah basis pengukuran menjadi bentuk kanonik​

Selanjutnya, kita mencari himpunan minimal basis untuk diukur sedemikian rupa sehingga kita dapat sepenuhnya mencakup setiap suku Pauli dalam observable yang diukur (banyak observable dapat diukur secara simultan jika mereka berkomutasi secara qubit-wise). Karena kita hanya mengukur suku dalam observable asli kita, yang merupakan jumlah dari semua Pauli single-Z, hanya satu basis yang diperlukan: basis all-Z.

Selain menemukan sekumpulan basis pengukuran Pauli, kita perlu memetakan suku Pauli ini ke bentuk kanonik yang diharapkan oleh Executor. Untuk informasi lebih lanjut tentang urutan qubit kanonik, kunjungi dokumentasi Samplomatic.

meas_box = boxed_circuit.data[-1]
canonical_qubits = [
idx
for idx, qubit in enumerate(boxed_circuit.qubits)
if qubit in meas_box.qubits
]
c_2_p = {
c: p for c, p in enumerate(canonical_qubits)
} # canonical -> physical
p_2_v = {p: v for v, p in enumerate(layout)} # physical -> virtual
c_2_v = {c: p_2_v[p] for c, p in c_2_p.items()} # canonical -> virtual
meas_bases, bases_reverser = get_measurement_bases(obs_tilde_virtual)
meas_bases_canonical = [
np.array([base[c_2_v[c]] for c in range(num_qubits)], dtype=np.uint8)
for base in meas_bases
]

Langkah 3: Jalankan eksperimen kuantum​

Tentukan cara sampling dalam QuantumProgram​

Kita sekarang menyiapkan QuantumProgram, yang berfungsi sebagai wadah pusat untuk semua circuit dan konfigurasi sampling yang akan dieksekusi oleh Executor. Objek ini mendefinisikan bagaimana instance circuit yang diacak dihasilkan, di-batch, dan dieksekusi untuk menghasilkan hasil pengukuran yang digunakan dalam PNA.

QuantumProgram dapat berisi beberapa item, masing-masing terdiri dari circuit template dan objek samplex yang bersesuaian yang mendefinisikan bagaimana randomisasi diterapkan. Abstraksi ini memungkinkan Executor menangani seluruh alur kerja sebagai satu program modular — dari pembuatan circuit acak hingga pengumpulan dan agregasi shot.

Pada langkah ini, kita membuat QuantumProgram yang menjalankan eksperimen PNA kita menggunakan circuit template dan samplex yang telah kita bangun sebelumnya. Konfigurasi ini mencakup elemen-elemen berikut:

  • template_circuit: Circuit yang berisi semua gate yang diperlukan untuk mengimplementasikan semua randomisasi yang diinginkan (dari randomisasi twirling, parameter, dan sebagainya).

  • samplex: Objek yang mendefinisikan distribusi probabilitas atas semua kemungkinan randomisasi circuit yang darinya sampling dilakukan.

  • samplex_arguments: Binding yang diperlukan untuk sepenuhnya mendefinisikan samplex

    • basis_changes: Di sinilah kita menentukan sekumpulan basis untuk diukur yang akan mencakup semua suku Pauli dalam observable yang diukur.

    • noise_scales.ref: Kita mengatur skala setiap lapisan noise menjadi 0.0 untuk mencegah noise tambahan disuntikkan ke dalam sampel kita.

    • pauli_lindblad_maps: Diperlukan jika noise_scales diberikan. Ini hanya memetakan lapisan noise ke model noise yang terkait.

  • shape: Sebuah tuple shape untuk memperluas shape implisit yang didefinisikan oleh samplex_arguments. Sumbu non-trivial yang diperkenalkan oleh perluasan ini mengenumerasi randomisasi.

# Control the # of shots during execution
shots_per_randomization_exec = 64
num_randomizations_exec = 6144

# Zero out the noise to prevent noise from being injected during execution.
# We only added InjectNoise annotations so PNA could associate the noise
# to layers in the circuit
samplex_inputs = {f"noise_scales.{ref}": 0.0 for ref in refs_to_noise_models}
samplex_inputs |= {"pauli_lindblad_maps": refs_to_noise_models}

# Specify the bases to measure. The samplex exposes one basis-change input per
# ChangeBasis-annotated box; here a single all-Z basis covers every term. We
# look up the basis-change interface name rather than hardcoding an index, since
# the name depends on the circuit's box structure.
bases_broadcastable = np.expand_dims(np.array(meas_bases_canonical), axis=1)
samplex_inputs |= {
spec.name: bases_broadcastable
for spec in samplex.inputs().get_specs(r"^basis_changes\.")
}

# Convert samplex_inputs into a dict to pass to QuantumProgram
samplex_arguments = (
samplex.inputs().make_broadcastable().bind(**samplex_inputs)
)

# Instantiate the QuantumProgram with the specified parameters
program = QuantumProgram(shots=shots_per_randomization_exec)
program.append_samplex_item(
circuit=template_circuit,
samplex=samplex,
samplex_arguments=samplex_arguments,
shape=(num_randomizations_exec,),
)

Sampel circuit menggunakan Executor​

Sekarang setelah kita mendefinisikan QuantumProgram kita, mengeksekusi eksperimen menjadi mudah. Kita cukup membuat instance objek Executor, memberikannya backend, dan menjalankan programnya.

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

Langkah 4: Rekonstruksi dan analisis hasil​

Untuk menghitung expectation value yang telah dimitigasi errornya, kita melakukan hal berikut:

  • Menghitung faktor skala TREX berdasarkan noise yang dipelajari yang memengaruhi pengukuran,

  • Menghasilkan mask untuk hanya mempertahankan sampel yang telah di-post-select, dan

  • Menggunakan fungsi executor_expectation_values dari qiskit-addon-utils untuk menggabungkan semua data menjadi expectation value yang telah dimitigasi errornya.

# Computing the TREX factors
measurement_noise_map = noise_learner_result[2].to_pauli_lindblad_map()
trex_rescale_factors = trex_factors(measurement_noise_map, bases_reverser)

# Post-select the results
post_selector = PostSelector.from_circuit(
circuit=template_circuit, coupling_map=backend.coupling_map
)

# Compute the ps mask for filtering results
mask = post_selector.compute_mask(exec_results[0], strategy="edge")

# Compute expvals using post selected results
results = executor_expectation_values(
exec_results[0]["meas"],
bases_reverser,
meas_basis_axis=0,
avg_axis=1,
measurement_flips=exec_results[0]["measurement_flips.meas"],
pauli_signs=exec_results[0].get("pauli_signs", None),
postselect_mask=mask,
rescale_factors=trex_rescale_factors,
)

Bandingkan strategi mitigasi: PNA, PNA+TREX, PNA+PS, PNA+PS+TREX​

Kita menghitung dan memvisualisasikan expectation value untuk beberapa varian mitigasi menggunakan hasil Executor.

bases_reverser_unmit = {Pauli("Z" * num_qubits): [observable]}
args = [
(bases_reverser_unmit, None, None),
(bases_reverser, None, None),
(bases_reverser, None, trex_rescale_factors),
(bases_reverser, mask, None),
(bases_reverser, mask, trex_rescale_factors),
]

evs = []
for reverser, postsel_mask, factors in args:
# Compute expvals using post selected results
res_ps = executor_expectation_values(
exec_results[0]["meas"],
reverser,
meas_basis_axis=0,
avg_axis=1,
measurement_flips=exec_results[0]["measurement_flips.meas"],
pauli_signs=exec_results[0].get("pauli_signs", None),
postselect_mask=postsel_mask,
rescale_factors=factors,
)
res_ps = np.array(res_ps)
evs.append(res_ps[:, 0][0])

experiments = ["PNA", "PNA+TREX", "PNA+PS", "PNA+PS+TREX"]
colors = ["#d9d9d9", "#b0b0b0", "#7f7f7f", "#4c4c4c"]
plt.bar(experiments, evs[1:], color=colors)
plt.axhline(y=1, color="green", linestyle="--", linewidth=2, label="Ideal")
plt.axhline(
y=evs[0], color="red", linestyle="--", linewidth=2, label="Unmitigated"
)
plt.ylabel("Expectation value", fontsize=14)

plt.title(
r"30q Mirrored Ising, 10 Trotter steps, $\theta_{rx}=\frac{\pi}{8}$",
fontsize=14,
)
plt.legend(loc="upper left", bbox_to_anchor=(1.05, 1), borderaxespad=0.0)
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()

Output of the previous code cell

Hasil menunjukkan manfaat kumulatif dari menggabungkan teknik mitigasi error yang berbeda. Pendekatan PNA murni sudah memulihkan expectation value mendekati benchmark ideal, mengindikasikan bahwa mempropagasi inverse noise channel ke dalam observable secara efektif mengompensasi error two-qubit gate.

  • Menambahkan reweighting TREX (PNA+TREX) sedikit memperbaiki estimasi dengan mengoreksi ketidakseimbangan sampling dalam circuit yang diacak.

  • Post-selection (PNA+PS) memberikan peningkatan yang lebih terlihat dengan menyaring hasil pengukuran yang tidak konsisten yang kemungkinan besar berasal dari error residual.

  • Akhirnya, menggabungkan keduanya (PNA+PS+TREX) menghasilkan hasil yang paling akurat, hampir menyamai nilai ideal, menunjukkan bagaimana strategi mitigasi ini saling memperkuat.

Secara keseluruhan, perbandingan ini menyoroti bahwa PNA berfungsi sebagai fondasi yang kuat untuk estimasi expectation value yang noise-aware, sementara TREX dan post-selection menawarkan penyempurnaan komplementer untuk peningkatan akurasi lebih lanjut.

Langkah selanjutnya​

Jika Anda merasa karya ini menarik, Anda mungkin tertarik dengan materi berikut: