Penekanan dan penyesuaian error
Penekanan error dapat merujuk pada teknik apa pun yang mengantisipasi dan mencoba menghindari jenis noise dan error tertentu. Ini paling mudah dijelaskan melalui contoh konkret, tetapi perlu diingat bahwa metode ini tidak terbatas pada contoh yang ditunjukkan di sini, dan metode baru terus dieksplorasi. Terkadang tidak mungkin menekan error, tetapi mungkin untuk memengaruhi bagaimana error tersebut terakumulasi. Jika kita bisa membuat error terakumulasi lebih lambat, kita mungkin menyebutnya sebagai menekan error keseluruhan, tetapi lebih akurat jika dipandang sebagai penyesuaian noise. Dalam pelajaran ini kita membahas contoh kanonik dari penekanan error (dynamical decoupling) dan penyesuaian/pembentukan noise (Pauli twirling).
Video untuk menyertai pelajaran ini akan diluncurkan dalam beberapa hari ke depan, dan akan disematkan di sini.
Dekopling dinamis
Mari kita mulai dengan state yang sangat sederhana pada satu qubit, dan memperkenalkan model noise yang sederhana. Ini belum cukup untuk menjelaskan dynamical decoupling secara lengkap, tetapi memberi kita contoh yang jelas. Misalkan kita memiliki qubit yang disiapkan dalam state superposisi menggunakan Hadamard gate:
State ini divisualisasikan pada bola Bloch seperti ditunjukkan di bawah ini pada sisi kiri. Dalam kasus ideal dengan noise nol, qubit ini akan tetap dalam state ini sampai operasi berikutnya dilakukan. Namun, kita tahu dari pelajaran sebelumnya bahwa ini bukan yang kita amati. Noise menyebabkan informasi kuantum terdegradasi.

Noise atau coupling dengan lingkungan dapat menyebabkan fase relatif antara basis state berubah. Artinya, probabilitas dan tidak berubah, karena nilai absolut dari koefisiennya tidak berubah. Sebaliknya, fase dari amplitudo berubah, mengubah komponen real dan imajinernya. Untuk membuat diskusi ini lebih konkret, mari kita pertimbangkan satu jenis interaksi yang dapat menyebabkan hal ini: coupling ke medan magnet yang berorientasi pada arah Z: .
Pertimbangkan apa yang terjadi pada state seiring berjalannya waktu:
Di sini kita telah menggunakan fakta bahwa medan magnet dalam arah Z menyebabkan presesi mengelilingi sumbu Z dengan frekuensi yang bergantung pada momen magnetik efektif dan kekuatan medan magnet. Detailnya kurang penting dibanding fakta bahwa interaksi ini menyebabkan evolusi mengelilingi sumbu Z, menghasilkan akumulasi fase yang berlawanan untuk kedua basis state komputasional. Menerapkan operator ini pada setiap suku dalam , kita menemukan hal berikut:
Fase yang bergantung waktu ini sesuai dengan presesi mengelilingi sumbu Z dalam gambaran bola Bloch. Ini ditunjukkan pada bagian kanan gambar di atas.
Jika kita tahu bahwa interaksi ini terjadi dengan cara yang terkontrol, kita bisa memprediksinya. Untuk memodelkan jenis noise ini, kita sebaliknya mempertimbangkan distribusi kekuatan medan magnet yang mungkin, masing-masing terjadi dengan probabilitas klasik tertentu (sumber noise lain perlu dimodelkan secara berbeda). Kita perlu mempertimbangkan apa yang terjadi ketika ada probabilitas klasik bukan nol bahwa state belum berotasi sama sekali, dan juga ada probabilitas bahwa state telah berotasi dalam jumlah kecil, atau bahkan dalam jumlah besar. Distribusi kemungkinan rotasi ini adalah alasan penyebaran state dalam gambaran bola Bloch di bawah ini. Jika ada lebih banyak noise dephasing seperti ini, fase akan menjadi kurang terdefinisi dengan baik. Dalam batas dephasing yang kuat, qubit menjadi sepenuhnya dephased, sesuai dengan hilangnya koherensi kuantum yang tersimpan dalam state.

Tentu saja, dalam eksperimen kita tidak tahu coupling acak apa yang akan terjadi. Apa yang bisa kita lakukan mengenai hal ini?
Misalkan coupling lingkungan tetap kira-kira konstan selama interval waktu . Pertimbangkan apa yang akan terjadi jika kita mengikuti resep ini:
-
Biarkan fase berubah selama waktu
-
Terapkan X gate pada qubit
-
Biarkan coupling lingkungan yang sama terjadi untuk waktu lagi
-
Terapkan X gate kedua
Setelah evolusi waktu awal, kita akan memiliki tepat state di atas. Menerapkan X gate pertama kita memiliki:
Sekarang ketika interval kedua sebesar berlalu, interaksi yang sama terjadi. Tetapi sekarang amplitudo terkait dengan eigenstate Z yang berlawanan, artinya tanda rotasi mengelilingi sumbu Z telah berganti. Setelah membiarkan sistem berevolusi dalam waktu untuk lagi kita mendapatkan state:
Dan memasukkan kita mendapatkan:
Menerapkan X gate terakhir tidak melakukan apa pun dalam kasus ini, tetapi umumnya diperlukan:
Kita telah memulihkan state kuantum aslinya, termasuk fase relatifnya. Proses ini adalah contoh yang sangat sederhana dari dynamical decoupling.
Secara lebih umum, dynamical decoupling (DD) melibatkan penyisipan beberapa single-qubit gate untuk mengurangi efek interaksi dengan sistem di luar qubit (mendekopel qubit dari lingkungan). Refocusing lengkap dari semua kemungkinan riwayat fase kembali ke state aslinya, seperti ditunjukkan di atas, agak diidealkan, tetapi tetap merupakan skenario yang mungkin. Mari kita bahas kapan DD berguna dan apa saja peringatannya.
Periksa pemahaman Anda
Dalam teks di atas, kita melangkah melalui efek dari urutan DD XX pada state awal . Periksa apakah langkah-langkah yang sama di atas juga mengembalikan state ke state awalnya di bawah asumsi yang sama tentang medan magnet yang berubah lambat sepanjang arah z.
Jawaban
Setelah evolusi waktu awal, kita akan memiliki tepat state di atas. Menerapkan X gate pertama kita memiliki:
Sekarang ketika interval kedua sebesar berlalu, interaksi yang sama terjadi. Tetapi sekarang amplitudo terkait dengan eigenstate Z yang berlawanan, artinya tanda rotasi mengelilingi sumbu Z telah berganti. Setelah membiarkan sistem berevolusi dalam waktu untuk lagi kita mendapatkan state:
Dan memasukkan kita mendapatkan
Menerapkan X gate terakhir, kita mendapatkan:
Kapan menggunakan DD
Peringatan pertama dan paling jelas adalah bahwa kita mengasumsikan kanal yang murni dephasing dalam pembahasan kita. Interaksi dunia nyata biasanya menghasilkan campuran dephasing dan mekanisme error lainnya. Dalam pembahasan di atas, kita memilih untuk menerapkan X gate secara khusus. Ini disebut sebagai urutan XX dalam dynamical decoupling. Urutan khusus ini cocok untuk error yang murni dephasing. Tetapi ada urutan lain yang mungkin lebih berlaku secara luas, seperti XY4 (ditunjukkan pada diagram sirkuit di bawah) dan XY8 yang lebih kompleks.
Peringatan lain adalah bahwa dynamical decoupling menambahkan single-qubit gate, yang dapat menambahkan single-qubit error karena ketidaksempurnaan gate atau bahkan crosstalk. Tingkat single-qubit error ini biasanya jauh lebih rendah dibanding error multi-qubit gate, jadi ini biasanya bukan masalah besar, tetapi perlu diingat jika banyak qubit menggunakan DD berkali-kali sepanjang sirkuit Anda.
DD berguna ketika coupling eksternal memiliki waktu untuk memengaruhi state qubit. Karena qubit diisolasi dengan sangat hati-hati dan coupling eksternal seharusnya lemah, jenis noise ini paling terlihat ketika qubit diam dalam waktu lama. Untuk waktu idle yang singkat, efek DD bisa berupa penambahan single-qubit gate error sambil hanya menekan sedikit noise; fidelitas sirkuit Anda justru bisa berkurang.
Kesimpulan utama: Gunakan dynamical decoupling ketika qubit tetap idle untuk periode yang cukup lama, dan perhatikan jenis urutan DD yang digunakan.
Dynamical decoupling menggunakan Qiskit
Mari kita eksplorasi penggunaan DD dengan memeriksa kasus beberapa qubit yang disiapkan dalam state , yang kemudian tetap idle untuk waktu yang lama. Tanpa adanya error, menyiapkan state multi-qubit dan kemudian menerapkan Hadamard gate sebelum pengukuran (sehingga mengukur dalam basis X) seharusnya selalu menghasilkan 0. Dengan noise, hasil pengukuran akan menjadi 0 hanya sebagian dari waktu, bukan dengan probabilitas 100%. Kita menghitung nilai ekspektasi X rata-rata di berbagai qubit. Artinya, kita tertarik pada hal berikut:
di mana dengan operator X pada posisi ke- dari kanan dan adalah state dari seluruh sistem.
Sirkuit ini berisi delay yang disengaja, yang agak dibuat-buat untuk sebuah benchmark. Namun, sangat umum bagi sirkuit nyata untuk memiliki qubit yang idle selama sebagian eksekusi. Anda bisa menganggap ini sebagai model sederhana dari sirkuit yang lebih kompleks di mana beberapa qubit tetap idle selama sebagian komputasi. Jenis benchmarking berdasarkan evolusi state ini sering disebut sebagai Ramsey benchmarking.
Kita mulai dengan memuat paket yang diperlukan dan mengonfigurasi layanan.
# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-aer qiskit-ibm-runtime
# Load key packages
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import SamplerV2 as Sampler
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit
import numpy as np
# --------- Configuration ----------
service = QiskitRuntimeService() # assumes credentials are saved
backend = service.backend("ibm_fez") # adjust if needed
Sekarang kita mendefinisikan beberapa fungsi helper. Pertama, kita ingin membahas poin tentang waktu idle yang lama. Apa sebenarnya arti "lama" dalam konteks ini? Kita bisa saja melaporkan waktu idle dalam mikrodetik. Namun, berguna untuk mengetahui berapa banyak two-qubit gate yang bisa dieksekusi selama interval yang sama. Ini membuat waktu idle lebih langsung menunjukkan kedalaman sirkuit yang bisa saja dieksekusi selama periode yang sama. Fungsi helper pertama mendapatkan two-qubit gate native pada backend dan menentukan durasi gate tersebut.
Fungsi kedua hanya membuat sirkuit Ramsey (satu dengan qubit dalam state ), menerapkan delay, merotasi menggunakan H gate, lalu mengukur. Ingat bahwa Hadamard gate (H) memetakan ke , jadi mengukur dalam basis Z setelah Hadamard sesuai dengan qubit yang berada dalam state tepat sebelum Hadamard.
Fungsi terakhir kita mengonversi hasil count mentah dari hasil pengukuran 0 dan 1 menjadi nilai ekspektasi X.
from typing import Tuple
from qiskit.providers import Backend
from numpy.typing import NDArray
# --------- Utilities ----------
def detect_twoq_gate_and_duration(
backend: Backend, pair: tuple[int, int] = (0, 1)
) -> Tuple[str, float]:
props = backend.properties()
candidates = ["cx", "ecr", "cz"]
for name in candidates:
try:
dur = props.gate_length(name, list(pair))
if dur is not None:
return name, dur
except Exception:
pass
raise RuntimeError(
"Could not find a two-qubit gate duration among cx/ecr/cz on this backend."
)
def make_multiqubit_ramsey_circuit(n: int, delay_dt_ticks: int) -> QuantumCircuit:
q = QuantumRegister(n, "q")
c = ClassicalRegister(n, "c")
qc = QuantumCircuit(q, c)
qc.h(q)
qc.barrier()
for i in range(n):
qc.delay(delay_dt_ticks, q[i], unit="dt")
qc.barrier()
qc.h(q)
qc.measure(q, c)
return qc
def counts_to_x_expectations(counts: dict[str, int], n: int) -> NDArray[np.float64]:
total = sum(counts.values())
if total == 0:
return np.zeros(n)
p0 = np.zeros(n, dtype=float)
for bitstring, cnt in counts.items():
bits_rev = bitstring[::-1]
for i in range(n):
if bits_rev[i] == "0":
p0[i] += cnt
p0 /= total
return 2.0 * p0 - 1.0
Sekarang kita menentukan detail pengujian kita, termasuk jumlah qubit dan urutan gate yang akan digunakan dalam DD (dalam hal ini XX). Perhatikan khususnya bahwa kita mengatur optimization level ke nol. Dalam praktiknya, Anda sering memilih optimization level yang lebih tinggi; di sini kita menggunakan level 0 untuk memastikan efek dari error yang ditargetkan oleh DD tetap terlihat. Akhirnya, kita menentukan waktu karakteristik untuk two-qubit gate pada backend ini dan mencetak beberapa waktu yang relevan.
n_qubits = 10 # number of qubits to test in parallel
shots = 4096
opt_level = 0 # we want to ignore optimization for now
num_steps = 6 # number of delay points
gates_per_step = 20 # "equivalent 2q gates" per step
dd_sequence = "XX" # "XX" for your request; you might try "XY4" too
# --------- Derive timing: dt and 2q gate time ----------
dt = backend.dt # seconds per dt
twoq_name, t2q = detect_twoq_gate_and_duration(backend, (0, 1)) # seconds
delay_unit_dt = int(round(t2q / dt)) # dt ticks equivalent to one 2q gate
print(f"Backend: {backend.name}")
print(f"dt = {dt*1e9:.3f} ns per tick")
print(f"Using 2q gate '{twoq_name}' with duration ~ {t2q*1e9:.1f} ns")
print(f"One 2q gate ≈ {delay_unit_dt} dt ticks")
Backend: ibm_fez
dt = 4.000 ns per tick
Using 2q gate 'cz' with duration ~ 68.0 ns
One 2q gate ≈ 17 dt ticks
Sekarang kita membangun sirkuit kita dan mentranspilasinya untuk backend yang kita pilih.
# --------- Build circuits for a sweep of delays ----------
pm = generate_preset_pass_manager(optimization_level=opt_level, backend=backend)
equiv_gates_list = [
j * gates_per_step for j in range(num_steps)
] # e.g., 0, 100, 200, ...
delay_dt_list = [int(round(delay_unit_dt * m)) for m in equiv_gates_list]
delay_us_list = [(dt * d) * 1e6 for d in delay_dt_list] # for printing/plotting
circuits = []
for delay_dt in delay_dt_list:
qc = make_multiqubit_ramsey_circuit(n_qubits, delay_dt)
qc_isa = pm.run(qc) # ISA-level scheduling/placement; DD is handled at runtime
circuits.append(qc_isa)
print("Delay sweep (approx microseconds):", [f"{t:.2f}" for t in delay_us_list])
Delay sweep (approx microseconds): ['0.00', '1.36', '2.72', '4.08', '5.44', '6.80']
Kita sebaiknya memvisualisasikan setidaknya satu sirkuit untuk memastikan kita telah membuat kode sirkuit kita dengan state dan delay yang diinginkan. Mungkin lebih mudah untuk memvisualisasikan sirkuit pra-transpilasi qc, atau Anda bisa memeriksa yang sudah ditranspilasi qc_isa.
qc.draw("mpl")

Eksekusi
Kita akhirnya siap untuk mengeksekusi pada hardware. Kita menggunakan Sampler untuk mendapatkan banyak pengukuran dari setiap qubit, dan kita akan menggunakannya dua kali: sekali dengan DD dimatikan secara eksplisit, dan sekali dengan DD dihidupkan dan menggunakan urutan gate XX.
# --------- Run: NO DD ----------
sampler = Sampler(mode=backend)
sampler.options.default_shots = shots
sampler.options.dynamical_decoupling.enable = False
job = sampler.run(circuits)
res_nodd = job.result()
job_id = job.job_id() # job id for dd off/false
print("job number for no dd is ", job_id)
# --------- Run: WITH DD (XX) ----------
sampler = Sampler(mode=backend)
sampler.options.default_shots = shots
sampler.options.dynamical_decoupling.enable = True
sampler.options.dynamical_decoupling.sequence_type = (
dd_sequence # "XX" first; you can try "XY4" too
)
sampler.options.dynamical_decoupling.scheduling_method = "alap"
sampler.options.dynamical_decoupling.extra_slack_distribution = "middle"
job = sampler.run(circuits)
res_dd = job.result()
job_id = job.job_id() # job id for dd on/XX
print("job number for dd using XX is ", job_id)
Kita bisa mengekstrak count dari berbagai sirkuit.
# --------- Extract counts per circuit ----------
from typing import Iterable, Any
def extract_counts_list(res: Iterable[Any]) -> list[dict[str, int]]:
counts_list: list[dict[str, int]] = []
for r in res: # each r corresponds to one circuit
counts: dict[str, int] = r.data.c.get_counts()
counts_list.append(counts)
return counts_list
counts_list_nodd = extract_counts_list(res_nodd)
counts_list_dd = extract_counts_list(res_dd)
Pasca-pemrosesan
Sekarang kita memiliki count dari pengukuran, tetapi kita ingin mengubahnya menjadi nilai ekspektasi X lalu merata-ratakan nilai ekspektasi tersebut di semua qubit yang kita gunakan, untuk mempelajari tentang pelestarian informasi fase. Untuk itu kita menggunakan fungsi counts_to_x_expectations yang telah kita definisikan sebelumnya.
# --------- Compute X-expectations and a scalar contrast ----------
# For each circuit (each delay), compute per-qubit <X> and average absolute contrast.
xexp_nodd = []
xexp_dd = []
contrast_nodd = []
contrast_dd = []
for counts in counts_list_nodd:
x_vec = counts_to_x_expectations(counts, n_qubits)
xexp_nodd.append(x_vec)
contrast_nodd.append(float(np.mean(np.abs(x_vec)))) # average |<X>| across qubits
for counts in counts_list_dd:
x_vec = counts_to_x_expectations(counts, n_qubits)
xexp_dd.append(x_vec)
contrast_dd.append(float(np.mean(np.abs(x_vec))))
# --------- Print a small summary ----------
print("\n=== Summary (average |<X>| per delay) ===")
for m, d_us, c0, c1 in zip(equiv_gates_list, delay_us_list, contrast_nodd, contrast_dd):
print(
f"Delay ~ {m:4d} * {twoq_name} (~{d_us:7.2f} µs): NoDD={c0: .3f}, DD({dd_sequence})={c1: .3f}"
)
# Optionally, inspect per-qubit values for the last delay point
print("\nPer-qubit <X> (abs) at the longest delay:")
print("NoDD:", np.round(np.abs(xexp_nodd[-1]), 3))
print("DD :", np.round(np.abs(xexp_dd[-1]), 3))
=== Summary (average |<X>| per delay) ===
Delay ~ 0 * cz (~ 0.00 µs): NoDD= 0.954, DD(XX)= 0.961
Delay ~ 20 * cz (~ 1.36 µs): NoDD= 0.827, DD(XX)= 0.927
Delay ~ 40 * cz (~ 2.72 µs): NoDD= 0.793, DD(XX)= 0.900
Delay ~ 60 * cz (~ 4.08 µs): NoDD= 0.729, DD(XX)= 0.876
Delay ~ 80 * cz (~ 5.44 µs): NoDD= 0.661, DD(XX)= 0.854
Delay ~ 100 * cz (~ 6.80 µs): NoDD= 0.586, DD(XX)= 0.826
Per-qubit <X> (abs) at the longest delay:
NoDD: [0.05 0.744 0.712 0.867 0.844 0.234 0.473 0.755 0.59 0.587]
DD : [0.583 0.89 0.921 0.88 0.908 0.832 0.893 0.773 0.807 0.773]
Terakhir, mari kita plot hasil kita.
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
# Add values with no DD
ax.scatter(
equiv_gates_list, contrast_nodd, c="blue", linestyle="-", label="No DD", alpha=0.7
)
## Add values with DD
ax.scatter(
equiv_gates_list, contrast_dd, c="red", linestyle="-", label="With DD", alpha=0.7
)
# Add labels and plot
ax.set_xlabel("Idle Time in # of 2-qubit gates")
ax.set_ylabel("<X>")
ax.legend()
ax.set_title("Dephasing and DD")
ax.grid(True)
plt.show()

Seperti yang dapat Anda lihat, tanpa delay eksplisit nilai ekspektasi memang mendekati 1, yang kita harapkan jika semua informasi fase dipertahankan. Jika semua informasi fase hilang, tidak akan ada preferensi bagi rotasi akhir untuk menghasilkan daripada , dan nilai ekspektasi rata-rata akan mendekati nol. Dalam data, kita melihat bahwa seiring bertambahnya waktu delay, nilai ekspektasi rata-rata dari X menurun, mulai mendekati nol. Perhatikan bahwa DD sangat efektif di sini; nilai ekspektasi dengan DD biasanya lebih dari 20% lebih baik (lebih dekat ke 1) dibanding nilai tanpa DD. Tetapi perhatikan juga bahwa titik data delay pertama sesuai dengan delay yang kira-kira setara dengan 100 operasi two-qubit gate. Ini menegaskan kembali poin bahwa DD paling berguna ketika qubit tetap idle untuk periode yang relatif lama.
Periksa pemahaman Anda
Jika kita menerapkan DD menggunakan XY4 pada sirkuit yang sama seperti sebelumnya, apakah Anda mengharapkan hasilnya jauh lebih baik, jauh lebih buruk, atau kira-kira sama, dibanding DD menggunakan XX? Jelaskan.
Jawaban
Kira-kira sama, mungkin sedikit lebih buruk. Sirkuit yang kita gunakan memiliki state yang dirotasi ke bidang XY. State ini menyimpan informasi terutama dalam fasenya, membuatnya sangat sensitif terhadap error dephasing dibanding relaksasi T1. XY4 mungkin membantu dengan berbagai jenis error yang lebih luas, tetapi XX sudah dioptimalkan untuk membantu sirkuit yang kita gunakan. XY4 mungkin sama baiknya, tetapi tidak akan menambahkan apa pun yang substansial, atau fakta bahwa XY4 berisi lebih banyak gate mungkin memungkinkan error gate tambahan yang membuat hasilnya sedikit lebih buruk.
Hasil terakhir menggunakan urutan DD gate paling sederhana XX. Mari kita lihat cara menerapkan urutan yang lebih kompleks, XY4. Kita mendefinisikan Sampler di bagian selanjutnya.
# --------- Run: WITH DD (XY4) ----------
dd_sequence = "XY4"
sampler = Sampler(mode=backend)
sampler.options.default_shots = shots
sampler.options.dynamical_decoupling.enable = True
sampler.options.dynamical_decoupling.sequence_type = dd_sequence
sampler.options.dynamical_decoupling.scheduling_method = "alap"
sampler.options.dynamical_decoupling.extra_slack_distribution = "middle"
job = sampler.run(circuits)
res_xy4 = job.result()
job_id = job.job_id() # job id for dd on/XY4
print("job number for dd using Xy4 is ", job_id)
job number for dd using Xy4 is d6k6ti860irc7395d3hg
# --------- Extract counts per circuit ----------
counts_list_xy4 = extract_counts_list(res_xy4)
# --------- Compute X-expectations and a scalar contrast ----------
# For each circuit (each delay), compute per-qubit <X> and average absolute contrast.
xexp_xy4 = []
contrast_xy4 = []
for counts in counts_list_xy4:
x_vec = counts_to_x_expectations(counts, n_qubits)
xexp_xy4.append(x_vec)
contrast_xy4.append(float(np.mean(np.abs(x_vec)))) # average |<X>| across qubits
# --------- Print a small summary ----------
print("\n=== Summary (average |<X>| per delay) ===")
for m, d_us, c0, c1, c2 in zip(
equiv_gates_list, delay_us_list, contrast_nodd, contrast_dd, contrast_xy4
):
print(
f"Delay ~ {m:4d} * {twoq_name} (~{d_us:7.2f} µs): NoDD={c0: .3f}, DD({dd_sequence})={c1: .3f}"
)
# Optionally, inspect per-qubit values for the last delay point
print("\nPer-qubit <X> (abs) at the longest delay:")
print("NoDD:", np.round(np.abs(xexp_nodd[-1]), 3))
print("DD XX :", np.round(np.abs(xexp_dd[-1]), 3))
print("DD XY4 :", np.round(np.abs(xexp_xy4[-1]), 3))
=== Summary (average |<X>| per delay) ===
Delay ~ 0 * cz (~ 0.00 µs): NoDD= 0.968, DD(XY4)= 0.968
Delay ~ 100 * cz (~ 6.80 µs): NoDD= 0.612, DD(XY4)= 0.839
Delay ~ 200 * cz (~ 13.60 µs): NoDD= 0.462, DD(XY4)= 0.705
Delay ~ 300 * cz (~ 20.40 µs): NoDD= 0.339, DD(XY4)= 0.580
Delay ~ 400 * cz (~ 27.20 µs): NoDD= 0.225, DD(XY4)= 0.481
Delay ~ 500 * cz (~ 34.00 µs): NoDD= 0.204, DD(XY4)= 0.393
Per-qubit <X> (abs) at the longest delay:
NoDD: [0.002 0.208 0.447 0.034 0.286 0.306 0.322 0.044 0.163 0.232]
DD XX : [0.433 0.669 0.526 0.516 0.572 0.284 0.303 0.055 0.365 0.208]
DD XY4 : [0.38 0.662 0.521 0.538 0.621 0.252 0.353 0.038 0.239 0.073]
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
# Add values with no DD
ax.scatter(
equiv_gates_list, contrast_nodd, c="blue", linestyle="-", label="No DD", alpha=0.7
)
## Add values with DD using XX sequence
ax.scatter(
equiv_gates_list, contrast_dd, c="red", linestyle="-", label="With XX", alpha=0.7
)
## Add values with DD using XY4 sequence
ax.scatter(
equiv_gates_list,
contrast_xy4,
c="black",
linestyle="-",
label="With XY4",
alpha=0.7,
)
# Add labels and plot
ax.set_xlabel("Idle Time in # of 2-qubit gates")
ax.set_ylabel("<X>_av")
ax.legend()
ax.set_title("Dephasing and DD")
ax.grid(True)
plt.show()

Di sini kita melihat bahwa XY4 tidak berbeda secara signifikan dari XX. Mungkin sedikit lebih buruk karena gate tambahan dalam urutan XY4, tetapi yang lebih penting, kita sudah menjelaskan mengapa XX akan memiliki efek yang diinginkan dalam mempertahankan fase khususnya untuk state seperti . Tidak ada alasan untuk berpikir bahwa untuk state awal seperti itu, urutan yang berbeda akan meningkatkan hasil.
Periksa pemahaman Anda
Verifikasi bahwa urutan XY4 membiarkan state tidak berubah hingga fase global.
Jawaban
Pauli twirling
Kita sebaiknya mulai dengan mencatat bahwa Pauli twirling sering digunakan bukan sebagai teknik penekanan error, tetapi sebagai teknik pembentukan error: ini membuat noise/error berperilaku berbeda, terkadang lebih dapat diprediksi, untuk memungkinkan metode lain. Meskipun Pauli twirling tidak mencegah error, ini mungkin mencegah akumulasi koherennya.
Dalam sirkuit kuantum, berbagai sumber error yang berbeda bertambah bersama. Error dapat bertambah dengan cara yang berbeda, khususnya secara koheren dan secara inkoheren. Akumulasi error secara koheren berarti bahwa noise atau implementasi yang tidak sempurna cenderung mendorong error ke arah yang sama di berbagai lapisan dan gate. Contohnya adalah over-rotation koheren ketika menerapkan rotation gate.
Pertimbangkan rotation gate ideal, seperti , yang merotasi mengelilingi sumbu X tepat sebesar . Tentu saja, implementasi gate tidak sempurna, dan rotasi aktualnya mungkin untuk satu implementasi, dan bahkan bisa jadi selalu memiliki tanda yang sama, dan mungkin memiliki besaran yang mirip di banyak aplikasi . Jadi penerapan berulang dari rotation gate dapat mengakibatkan akumulasi koheren dari banyak over-rotation (atau under-rotation) ini, .
Akumulasi error inkoheren justru kebalikannya: error dalam arah acak dengan tanda acak, sedemikian rupa sehingga error di lapisan yang berbeda tidak selalu berinterferensi secara aditif, tetapi terkadang saling membatalkan atau bertambah secara kuadratur. Jelas error inkoheren terakumulasi lebih lambat dalam hal efek keseluruhan pada state qubit. Diagram sederhana dari ini ditunjukkan pada gambar di bawah. Ini adalah penyederhanaan. Error kuantum nyata tidak terbatas pada ruang Cartesian dua dimensi; tidak semua kontribusi error akan memiliki besaran yang sama, dan ada lebih banyak kompleksitas. Tetapi intuisi dari gambar seperti ini berguna: error koheren cenderung terakumulasi lebih cepat dibanding error inkoheren.

Seseorang sering kali dapat memperoleh hasil dengan fidelitas lebih tinggi dengan mengubah akumulasi error koheren menjadi akumulasi error inkoheren. Cara utama untuk mencapai ini disebut Pauli twirling.
Pauli twirling mengacu pada penambahan kombinasi Pauli gate sebelum dan sesudah operasi gate yang diinginkan sedemikian rupa sehingga . Di sini, dan bukan Pauli tunggal, tetapi kumpulan operator Pauli yang sering bekerja pada beberapa qubit. Anda mungkin terkadang melihat pernyataan bahwa aksi dari Pauli gate tambahan ini "setara dengan identitas". Tetapi ini tidak tepat dan berpotensi membingungkan. Pauli gate dipisahkan oleh dan tujuannya adalah membuat aksi logis dari semua gate tetap sama dengan . Terkadang disebut sebagai "payload" untuk membedakan operasi yang dimaksud ini dari gate yang ditambahkan untuk penekanan. Beberapa contoh Pauli twirling di sekitar CNOT gate ditunjukkan di bawah ini.
Mari kita telusuri satu contoh saja untuk memverifikasi bahwa twirling membiarkan efek logis dari payload tidak berubah. Tanpa kehilangan keumuman, misalkan dua-qubit state yang melibatkan dan adalah:
Seperti biasa, kita menggunakan konvensi urutan qubit . Menerapkan CX gate dengan sebagai target menghasilkan
Sekarang mari kita pertimbangkan sirkuit ketiga yang ditunjukkan, menggunakan X gate untuk Pauli twirling. Kita bisa saja mengalikan matriks-matriks tersebut bersama dan memverifikasi bahwa hasilnya adalah matriks CNOT. Alternatifnya, kita bisa melacak operasi pada state kuantum arbitrer melalui sirkuit, seperti yang kita lakukan di bawah ini. State pada titik-titik berbeda dalam sirkuit telah diberi label a-d.
Ini persis yang kita peroleh sebelumnya tanpa twirling. Memang, urutan gate yang di-twirl ini membiarkan aksi logis dari payload tidak berubah. Namun, jika urutan twirling valid yang berbeda dipilih secara acak dari lapisan ke lapisan, akumulasi error koheren dapat diubah menjadi akumulasi error stokastik (inkoheren) yang efektif. Untuk lebih jelas, seseorang tidak memilih satu pola twirling dan menggunakannya di seluruh sirkuit. Sebagai gantinya, urutan twirling valid yang berbeda dipilih untuk lapisan yang berbeda. Contoh dengan banyak lapisan entangling mungkin terlihat seperti ini.

Periksa pemahaman Anda
Verifikasi bahwa Pauli twirling pada panel keempat dalam gambar di atas juga membiarkan efek logis dari CNOT tidak berubah.
Jawaban
Kita mengikuti contoh di atas dan menunjukkan bahwa aksi pada state dua-qubit arbitrer mana pun setara dengan operasi CNOT. Kita akan merujuk pada titik-titik berlabel pada diagram ini.
Ini setara dengan aksi CNOT dengan qubit 0 sebagai kontrol dan qubit 1 sebagai target.
Bisakah Anda membuat urutan Pauli twirling untuk CNOT gate yang belum ditunjukkan di atas?
Jawaban
Ya, ada banyak lainnya. Satu contoh adalah sebelum CNOT, dan serta setelah CNOT.
Kapan menggunakan Pauli twirling
Seperti yang disajikan di sini, Pauli twirling hanya diterapkan pada multi-qubit gate. Menerapkan protokol serupa pada single-qubit gate akan memerlukan logika yang berbeda dan umumnya tidak berguna dalam praktiknya. Pauli twirling sendiri menggunakan beberapa single-qubit gate (Pauli gate). Pauli gate tambahan kemungkinan akan memperkenalkan lebih banyak error dibanding yang akan diperoleh dengan meng-acak akumulasi error koheren apa pun. Tingkat error yang terkait dengan two-qubit gate jauh lebih besar dibanding yang terkait dengan single-qubit gate. Selanjutnya, beberapa single-qubit gate bersifat non-Clifford, yang tidak dapat di-twirl sepenuhnya. Inilah sebabnya Qiskit menyertakan opsi Pauli twirling yang secara otomatis meng-twirl di sekitar two-qubit gate, bukan di sekitar single-qubit gate.
Ini tersirat dalam gambar di atas: Pauli twirling diimplementasikan di sekitar CX gate, tetapi tidak di sekitar Hadamard gate.
Mari kita lihat dua contoh Pauli twirling dalam aksi.
Pauli twirling untuk menekan akumulasi koheren
Untuk mengamati bagaimana Pauli twirling dapat mengubah akumulasi error koheren menjadi akumulasi inkoheren yang lebih lambat, kita membutuhkan sirkuit dan observable yang berfungsi sebagai stress test error koheren. Satu-satunya tujuannya adalah membuat two-qubit error yang terakumulasi secara koheren menjadi terlihat, lalu menunjukkan bagaimana Pauli twirling mengubah akumulasi koheren tersebut menjadi peluruhan stokastik.
CNOT (atau CZ) gate adalah sumber umum dari error koheren. Eksperimen paling sederhana yang bisa kita lakukan dalam kasus ini adalah menginisialisasi sebuah state (misalnya ), menerapkan lapisan pasangan CNOT gate (menggunakan fakta bahwa dua CNOT menghasilkan identitas), dan memeriksa bagaimana error terakumulasi seiring bertambahnya jumlah lapisan, baik dengan maupun tanpa Pauli twirling.
Observable yang menarik adalah pada satu qubit, yang kita plot sebagai fungsi dari jumlah pengulangan pasangan CNOT.
# --- Imports ---
import numpy as np
from qiskit import QuantumCircuit
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import SamplerV2 as Sampler
# Build the circuit with CX/CX identity per layer
def identity_cx_pairs(layers: int) -> QuantumCircuit:
q = QuantumRegister(2, "q")
c = ClassicalRegister(1, "c") # we only measure q0
qc = QuantumCircuit(q, c, name=f"N={layers}")
# |+> on q0
qc.h(q[0])
for _ in range(layers):
qc.barrier()
qc.cx(q[0], q[1])
qc.cx(q[0], q[1])
# Measure in X basis: H then measure q0
qc.h(q[0])
qc.measure(q[0], c[0])
return qc
Karena kita telah merotasi basis kita sebelum pengukuran, pengukuran sesuai dengan state yang berada dalam tepat sebelum Hadamard gate terakhir, dan serupa untuk dan . Oleh karena itu, nilai ekspektasi kita dapat dihitung dengan sederhana dari count dikurangi count .
# Compute <X> from SamplerV2 counts
def x_expect_from_counts(counts: dict[str, int]) -> float:
shots = sum(counts.values())
p0 = counts.get("0", 0) / shots
p1 = counts.get("1", 0) / shots
return p0 - p1 # <X> = P(0) - P(1) after H,measure
Kita memilih jumlah lapisan CNOT yang wajar untuk membiarkan error terakumulasi, membangun sirkuit kita, lalu mentranspilasinya.
# Choose the number of layers for the experiment
N_layers_list = [0, 1, 2, 3, 4, 5]
circuits = [identity_cx_pairs(n) for n in N_layers_list]
# Transpile to backend ISA so that primitives run native instructions
pm = generate_preset_pass_manager(backend=backend, optimization_level=0)
isa_circuits = [pm.run(c) for c in circuits]
Perlu diingat bahwa setiap lapisan terdiri dari lebih dari satu two-qubit gate. Pantau kedalaman two-qubit yang sudah ditranspilasi dengan menggunakan fungsi di bawah ini.
# We can check the 2-qubit depths of any of our circuits like this:
print(
"two-qubit depth",
isa_circuits[5].decompose().depth(lambda instr: len(instr.qubits) > 1),
)
two-qubit depth 15
# Configure two Samplers: (A) no twirling, (B) gate twirling
# - No DD, no measurement twirling in both (to isolate gate twirling)
# ------------------------------
shots = 8192
# (A) No twirling
sampler_no_twirl = Sampler(mode=backend)
# Ensure no extra suppression/mitigation:
sampler_no_twirl.options.dynamical_decoupling.enable = False
# Be explicit about twirling:
sampler_no_twirl.options.twirling.enable_gates = False
sampler_no_twirl.options.twirling.enable_measure = (
False # TREX-style measurement twirling off
)
sampler_no_twirl.options.default_shots = shots # default shots for this primitive
# (B) Gate twirling ON
sampler_twirl = Sampler(mode=backend)
sampler_twirl.options.dynamical_decoupling.enable = False
sampler_twirl.options.twirling.enable_gates = True # <-- enable Pauli gate twirling
sampler_twirl.options.twirling.enable_measure = False
sampler_twirl.options.default_shots = shots
# (Optional) Inspect options dicts if you’re curious
# print(asdict(sampler_no_twirl.options))
# print(asdict(sampler_twirl.options))
Sekarang kita menjalankan job.
# Run both jobs; extract counts; compute <X>
# Helper to run a sampler and compute <X> per circuit
def run_and_x_expect(sampler: Sampler, circ_list: list[QuantumCircuit]) -> list[float]:
job = sampler.run(
circ_list
) # shots taken from options.default_shots unless overridden
result = job.result()
# For SamplerV2, use join_data().get_counts() to combine registers if needed
exp_vals = []
for pub in result:
counts = pub.join_data().get_counts()
exp_vals.append(x_expect_from_counts(counts))
return exp_vals
x_no_twirl = run_and_x_expect(sampler_no_twirl, isa_circuits)
x_twirl = run_and_x_expect(sampler_twirl, isa_circuits)
# ------------------------------
# 6) Print a small table
# ------------------------------
print("\nN_layers <X> (no twirl) <X> (gate twirl)")
for n, a, b in zip(N_layers_list, x_no_twirl, x_twirl):
print(f"{n:7d} {a:14.6f} {b:14.6f}")
N_layers <X> (no twirl) <X> (gate twirl)
0 0.984375 0.987549
1 0.934326 0.936523
2 0.844238 0.892822
3 0.712158 0.879395
4 0.592529 0.844971
5 0.449463 0.785156
Terakhir, kita memvisualisasikan hasil ini.
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
# Add values using XX
ax.scatter(
N_layers_list, x_no_twirl, c="blue", linestyle="-", label="No twirl", alpha=0.7
)
## Add values with XY4
ax.scatter(N_layers_list, x_twirl, c="red", linestyle="-", label="Twirled", alpha=0.7)
# Add labels and plot
ax.set_xlabel("CX layers")
ax.set_ylabel("<X>")
ax.legend()
ax.set_title("Pauli twirling")
ax.grid(True)
plt.show()

Kita dapat dengan jelas melihat bahwa sirkuit yang di-twirl menghasilkan nilai ekspektasi yang lebih dekat ke ideal . Contoh ini telah memenuhi tujuannya, tetapi mari kita lanjutkan ke sesuatu yang lebih berguna: twirling dalam produksi state yang sangat entangled.
Pauli twirling dalam persiapan state GHZ
Contoh sebelumnya menunjukkan kasus yang meyakinkan untuk Pauli twirling dalam menghancurkan akumulasi koheren error yang terkait dengan CNOT gate. Produksi state GHZ menggunakan banyak CNOT gate untuk menghasilkan state yang sangat entangled yang berguna untuk banyak aplikasi komputasi kuantum. Mari kita eksplorasi bagaimana Pauli twirling membantu dalam konteks ini, dengan state GHZ yang ukurannya semakin besar.
# Imports if not already loaded in previous cells
# from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
# from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit
# Define a GHZ circuit building function, so that we can build GHZ states of increasing size.
def ghz_circuit(n: int) -> QuantumCircuit:
q = QuantumRegister(n, "q")
c = ClassicalRegister(n, "c")
qc = QuantumCircuit(q, c)
qc.h(q[0])
for i in range(n - 1):
qc.cx(q[i], q[i + 1])
qc.barrier()
qc.measure(q, c)
return qc
# Build a test state with 10 qubits to remind ourselves of GHZ structure.
num_qubits = 10
qc_ghz = ghz_circuit(num_qubits)
qc_isa = pm.run(qc_ghz)
qc_ghz.draw("mpl")

Sekarang kita membangun sirkuit kita dan mentranspilasinya. Dalam kasus ini, kita tidak memiliki gate berulang buatan yang tereduksi menjadi identitas. Jadi kita bisa membiarkan pass manager melakukan sedikit lebih banyak optimasi untuk kita. Kita akan mengaturnya ke level tiga.
# Set up a pass manager
opt_level = 3
# --------- Build circuits for a sweep of delays ----------
pm = generate_preset_pass_manager(optimization_level=opt_level, backend=backend)
# Build GHZ circuits of increasing size.
nmin = 5
nmax = 15
circuits = []
for n in range(nmin, nmax):
qc = ghz_circuit(n)
qc_isa = pm.run(qc)
circuits.append(qc_isa)
Kita bisa melihat bahwa optimizer memetakan sirkuit abstrak kita ke qubit 123, 124, 136, 142, dan 143.
circuits[0].draw("mpl")

Untuk memahami alasannya, mari kita lihat peta tata letak backend kita (dalam gambar ini, ibm_fez, tetapi Anda bisa melakukan analisis serupa pada backend mana pun).

Kita melihat bahwa qubit dipilih dalam sebuah rantai untuk meminimalkan swap gate dan dengan demikian kedalaman sirkuit. Selanjutnya, setiap CZ gate yang diimplementasikan dalam sirkuit berada di antara qubit yang berdekatan. Terakhir, kelima qubit memiliki tingkat error yang relatif rendah, termasuk tingkat error readout-assignment. Anda dapat memeriksa tingkat error ini pada backend mana pun di halaman Compute resources. Menemukan tata letak seperti itu tidak sulit untuk rantai linear sederhana, tetapi seiring masalah menjadi lebih kompleks, optimasi tata letak sirkuit menjadi lebih sulit dan lebih berharga.
Sekarang kita mengonfigurasi Sampler primitive kita. Kita mematikan alat penekanan/mitigasi lain untuk fokus pada Pauli twirling.
# Configure two Samplers: (A) no twirling, (B) gate twirling
# No DD, no measurement twirling in both (to isolate gate twirling)
shots = 8192
# (A) No twirling
sampler_no_twirl = Sampler(mode=backend)
# Ensure no extra suppression/mitigation:
sampler_no_twirl.options.dynamical_decoupling.enable = False
# Be explicit about twirling:
sampler_no_twirl.options.twirling.enable_gates = False
sampler_no_twirl.options.twirling.enable_measure = (
False # TREX-style measurement twirling off
)
sampler_no_twirl.options.default_shots = shots # default shots for this primitive
# (B) Gate twirling ON
sampler_twirl = Sampler(mode=backend)
sampler_twirl.options.dynamical_decoupling.enable = False
sampler_twirl.options.twirling.enable_gates = True # <-- enable Pauli gate twirling
sampler_twirl.options.twirling.enable_measure = False
sampler_twirl.options.default_shots = shots
# (Optional) Inspect options dicts if you’re curious
# print(asdict(sampler_no_twirl.options))
# print(asdict(sampler_twirl.options))
Terakhir, kita menjalankan job kita. Anda dapat secara opsional mencetak nomor job ID untuk pengambilan nanti.
job = sampler_twirl.run(circuits)
res_ghz_twirl = job.result()
job_id = job.job_id() # job id for twirling on/true
print("job number for twirling the ghz prep is ", job_id)
job = sampler_no_twirl.run(circuits)
res_ghz_no_twirl = job.result()
job_id = job.job_id() # job id for twirling off/false
print("job number for NO twirling the ghz prep is ", job_id)
job number for twirling the ghz prep is d7h967bjne2c7393s0b0
job number for NO twirling the ghz prep is d7h96f7b91ec73aufing
Kita mengekstrak count dari setiap basis state komputasional yang diukur untuk semua sirkuit, baik dengan maupun tanpa twirling.
# --------- Extract counts per circuit ----------
def extract_counts_list(res):
counts_list = []
for r in res: # each r corresponds to one circuit
# r.data.<classical_register_name>.get_counts()
counts = r.data.c.get_counts()
counts_list.append(counts)
return counts_list
counts_list_ghz_twirl = extract_counts_list(res_ghz_twirl)
counts_list_ghz_no_twirl = extract_counts_list(res_ghz_no_twirl)
print(counts_list_ghz_twirl[0])
print(counts_list_ghz_twirl[4])
{'11111': 3440, '11101': 87, '00000': 3434, '10111': 87, '00001': 168, '10011': 4, '11000': 102, '00011': 39, '01111': 93, '00111': 111, '01000': 90, '11110': 235, '00110': 6, '00010': 75, '11011': 30, '11100': 42, '10000': 82, '00101': 7, '10001': 2, '11001': 6, '11010': 5, '01010': 3, '01110': 7, '00100': 14, '10101': 1, '10110': 8, '01001': 6, '10100': 1, '01101': 4, '01011': 1, '10010': 2}
{'111111111': 2683, '000000000': 2976, '111000000': 53, '110000000': 74, '111111100': 47, '111011111': 76, '111111110': 195, '111111000': 67, '000000010': 66, '011111110': 10, '011110001': 1, '101110111': 7, '101011111': 9, '111110111': 87, '111100000': 96, '111011000': 2, '010000000': 60, '111111010': 3, '100000000': 110, '111111011': 32, '000000111': 51, '000100000': 84, '000011111': 82, '000000001': 176, '100000010': 6, '111100001': 3, '000000011': 36, '101111111': 98, '100000011': 2, '001111111': 75, '000001000': 60, '000011101': 4, '110111111': 38, '111111101': 72, '111100100': 3, '000100010': 1, '001011101': 1, '000000110': 8, '110000011': 1, '000111111': 48, '000000100': 23, '000001111': 65, '010000101': 2, '000100001': 8, '111110000': 51, '010001111': 1, '111000111': 2, '111000001': 2, '000111100': 1, '011111111': 116, '111110001': 1, '000011110': 10, '000010000': 23, '000101111': 3, '000101110': 1, '011110111': 4, '010000111': 7, '111101111': 36, '001000000': 40, '010100000': 3, '101111110': 8, '110111000': 5, '000000101': 8, '010000010': 4, '000110001': 2, '110111110': 4, '111100010': 2, '111010111': 2, '000001110': 5, '111110101': 3, '001110000': 4, '101100000': 5, '001111110': 8, '100000001': 5, '111011110': 3, '111110100': 5, '110001011': 1, '001110111': 4, '000010110': 2, '111001111': 2, '000011000': 5, '010111110': 1, '000101000': 4, '101000000': 4, '100111111': 4, '111110011': 2, '000101100': 1, '101111100': 2, '111011100': 1, '001111011': 3, '011111011': 3, '110100000': 5, '000001011': 3, '111110110': 5, '111111001': 5, '000001001': 8, '010000001': 5, '011111101': 2, '001010111': 1, '011101110': 1, '110110110': 1, '001111100': 3, '100001000': 3, '001000011': 1, '001011111': 6, '110000010': 2, '110010000': 1, '010011111': 2, '111101101': 3, '101110000': 1, '111100111': 6, '010111111': 5, '110110000': 2, '011011111': 2, '110011111': 1, '110000111': 6, '001000001': 2, '001100000': 3, '101101111': 1, '000111110': 5, '111011101': 2, '100100000': 2, '101111101': 3, '001001111': 1, '001010000': 2, '111001000': 1, '011110000': 2, '011101111': 3, '000001100': 1, '110101000': 1, '011000000': 4, '111101000': 3, '110000001': 4, '000010111': 4, '011111100': 1, '111000010': 1, '101011000': 1, '101111000': 2, '001101111': 1, '010001100': 1, '000100011': 1, '111110010': 1, '111101110': 1, '100001111': 1, '100011011': 1, '010110111': 1, '001110110': 2, '100000100': 1, '001000110': 1, '100011111': 2, '010010001': 1, '111010110': 1, '011110110': 1, '000111000': 2, '000100111': 1, '011010000': 1, '001111000': 2, '100010000': 1, '011111000': 2, '110111100': 2, '110110111': 1, '110001000': 1, '000110111': 1, '000101011': 1, '000110000': 1, '011100000': 3, '001000010': 1, '001001000': 1, '001000111': 1, '001111101': 3, '111101011': 1, '111010000': 1, '100000101': 1, '000010010': 1, '001011110': 1, '000011011': 1, '111101100': 1}
Kita tahu distribusi ideal dari state GHZ adalah yang di mana setengah shot mengembalikan dan setengah lainnya mengembalikan . Bangun ini untuk perbandingan.
ideal_dist = []
for n in range(nmin, nmax):
ideal_dist.append({"0" * n: int(shots / 2), "1" * n: int(shots / 2)})
Sekarang kita menggunakan fidelitas Hellinger sebagai ukuran kualitas dari state akhir kita.
from qiskit.quantum_info import hellinger_fidelity
num_qubits = []
fidelities_twirl = []
fidelities_no_twirl = []
for n in range(len(ideal_dist)):
num_qubits.append(nmin + n)
fidelities_twirl.append(hellinger_fidelity(counts_list_ghz_twirl[n], ideal_dist[n]))
fidelities_no_twirl.append(
hellinger_fidelity(counts_list_ghz_no_twirl[n], ideal_dist[n])
)
Terakhir kita plot hasil kita.
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
# Add values using no twirling
ax.scatter(
num_qubits,
fidelities_no_twirl,
c="blue",
linestyle="-",
label="No twirl",
alpha=0.7,
)
## Add values with twirling
ax.scatter(
num_qubits, fidelities_twirl, c="red", linestyle="-", label="Twirled", alpha=0.7
)
# Add labels and plot
ax.set_xlabel("Qubits in GHZ state")
ax.set_ylabel("Hellinger fidelity")
ax.legend()
ax.set_title("Pauli twirling in GHZ states")
ax.grid(True)
plt.show()

Hasil menggunakan Pauli twirling tidak lebih baik (bahkan sedikit lebih buruk) dibanding tanpa twirling. Apa yang terjadi?
Dua hal terjadi. Pertama, Pauli twirling tidak mengurangi jumlah total noise — melainkan, ini membentuk ulang error koheren dan sistematis menjadi error Pauli tipe stokastik, sehingga pertumbuhan error menjadi dapat diprediksi dan dimodelkan. Tidak pernah ada janji pengurangan error, kecuali dalam kasus khusus.
Kedua, dalam sirkuit GHZ, beberapa error koheren dapat sebagian saling membatalkan atau bertindak seperti pergeseran fase yang jinak karena simetri dari konstruksi GHZ; twirling menghilangkan perlindungan yang tidak disengaja ini dan menggantinya dengan noise Pauli stokastik yang tidak berkorelasi, sehingga fidelitas GHZ menjadi sedikit lebih buruk di bawah twirling.
Klaim kedua ini membutuhkan sedikit penjelasan. Klaimnya bukan bahwa sirkuit GHZ terlindungi dari semua jenis akumulasi error koheren, hanya beberapa jenis — dan bahwa dalam kasus-kasus tersebut, perlindungannya dihancurkan oleh twirling. Secara khusus, mari kita pertimbangkan over-rotation koheren yang terkait dengan CX gate. Mari kita sebut CX gate nyata dengan over-rotation sebagai :
dengan adalah produk operator Pauli apa pun, seperti , , , , dan lain-lain. Untuk state umum, over-rotation semacam ini bisa memengaruhi statistik pengukuran (dan karenanya ukuran fidelity state). Namun, sebagian dari ini membuat banyak observable GHZ standar tetap tidak berubah, termasuk operator seperti dan . Dalam konteks menyiapkan state GHZ, error over-rotation yang relevan dari jenis ini adalah:
Persiapan seluruh state GHZ -qubit akan terlihat seperti:
Setelah menyiapkan state GHZ, secara ideal state-nya adalah:
State ini adalah eigenstate simultan dari sekumpulan besar operator Pauli, termasuk yang berikut ini:
dengan eigenvalue . Akibatnya, operator berbentuk bekerja pada state GHZ sebagai perkalian dengan faktor fase , yang tidak memengaruhi observable GHZ standar seperti parity, kolektif, atau populasi basis komputasional. Jadi, meskipun error over-rotation bersifat koheren dan sistematis, error tersebut secara efektif tidak terlihat oleh pengukuran yang digunakan untuk menilai fidelity GHZ. Dalam hal ini, circuit GHZ memiliki proteksi koherensi yang tidak disengaja: error CX koheren tertentu bersifat commute dengan struktur state yang sedang disiapkan sehingga tidak menurunkan performa yang terukur.
Pauli twirling secara mendasar mengubah situasi ini. Twirling tidak mempertahankan error over-rotation koheren sebagai proses yang deterministik. Sebaliknya, ia mengubah channel error koheren menjadi channel Pauli stokastik efektif. Akibatnya, channel error sekarang mencakup term seperti , , , dan , yang tidak commute dengan stabilizer GHZ.
Ketika error Pauli stokastik ini terjadi, mereka menciptakan fault bit-flip dan phase-flip yang nyata pada qubit individu atau pasangan qubit. Error ini membawa state keluar dari subspace stabilizer GHZ, mengurangi interferensi antara dan , dan secara langsung menurunkan fidelity GHZ serta sinyal parity multi-qubit. Dengan kata lain, Pauli twirling menghilangkan struktur koheren dari error tapi juga menghilangkan pembatalan berbasis simetri yang sebelumnya membuat error tersebut tidak berbahaya. Hasilnya adalah state GHZ yang sedikit lebih buruk — bukan karena twirling menambah noise, tapi karena ia mengubah error koheren yang sebagian besar tidak berbahaya menjadi error stokastik yang benar-benar merusak.
Contoh ini menyoroti pelajaran penting: Pauli twirling bukan strategi peningkatan yang universal. Ia paling bermanfaat ketika error koheren terakumulasi di sepanjang circuit dengan cara yang merusak fidelity yang dibutuhkan. Dalam circuit yang sangat simetris seperti persiapan state GHZ, beberapa error koheren secara alami selaras dengan stabilizer state, dan mengacak error tersebut secara sengaja bisa menghilangkan proteksi tidak disengaja ini.
Gabungkan metode
Pada prinsipnya kita bisa menggabungkan Pauli twirling dengan dynamical decoupling. Kita belum menggunakan DD dalam kasus ini karena dua alasan: pertama, seharusnya tidak ada periode idle yang sangat panjang dalam circuit ini. Kedua, state GHZ tidak menyimpan sebagian besar informasinya dalam koherensi fase single-qubit, terutama jika kita hanya mengamati fidelity dalam bentuk hitungan bitstring, bukan dalam bentuk fase antara bitstring yang diinginkan dan .
Jika kita memperluas persiapan GHZ ke 100+ qubit, maka delay dalam mengukur qubit pertama/awal mungkin memang cukup panjang sehingga DD bisa berguna. Ini terutama benar jika kita memperhitungkan depth circuit yang sudah ditranspilasi.
Pauli twirling dalam struktur echo RTZ
Contoh terakhir kita tentang Pauli twirling dalam konteks mengurangi akumulasi error koheren menggunakan circuit dengan layer kombinasi X-CZ-X-CZ. Ini adalah urutan gate yang terkenal yang sering digunakan untuk membatalkan term error ZZ koheren pada gate dua-qubit, bahkan tanpa twirling. Tapi dengan twirling, kita bisa mengurangi atau mengacak bentuk lain dari akumulasi error koheren.
Kita akan mulai dengan mendefinisikan fungsi untuk membangun circuit mirip-RTZ dengan jumlah layer yang bervariasi.
from qiskit import QuantumCircuit
def rtz_echo_circuit(n_qubits: int, depth: int) -> QuantumCircuit:
"""
Construct an RTZ echo-style circuit.
Args:
n_qubits: Number of qubits in the circuit.
depth: Number of repeated echo layers.
Returns:
A QuantumCircuit implementing the echo sequence with measurements.
"""
q = QuantumRegister(n_qubits, "q")
c = ClassicalRegister(n_qubits, "c")
qc = QuantumCircuit(q, c)
for _ in range(depth):
qc.h(q)
for i in range(0, n_qubits - 1, 2):
qc.cz(q[i], q[i + 1])
qc.x(q)
for i in range(1, n_qubits - 1, 2):
qc.cz(q[i], q[i + 1])
qc.h(q)
qc.measure(q, c)
return qc
Sekarang kita membangun circuit dengan layer yang semakin bertambah, hingga suatu total depth dua-qubit yang sudah ditranspilasi yang masuk akal.
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
circuits = []
qcs = []
depths = list(range(3, 27, 4))
n_qubits = 10
opt_level = 0
pm = generate_preset_pass_manager(
optimization_level=opt_level,
backend=backend,
initial_layout=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
)
for d in depths:
qc = rtz_echo_circuit(n_qubits, d)
qcs.append(qc)
qc_isa = pm.run(qc)
circuits.append(qc_isa)
# We can check the 2-qubit depths of any of our circuits like this:
two_qubit_depths = []
for n in range(len(circuits)):
two_qubit_depths.append(
circuits[n].decompose().depth(lambda instr: len(instr.qubits) > 1)
)
print(two_qubit_depths)
[6, 14, 22, 30, 38, 46]
qcs[0].draw("mpl")
# circuits[0].draw("mpl")

Pada ujung yang tinggi, beberapa di antaranya cukup dalam. Mari kita gunakan AerSimulator tanpa noise model untuk mendapatkan state ideal di akhir masing-masing circuit ini. Kita kemudian bisa membandingkan hasil kita dari komputer kuantum nyata menggunakan Hellinger fidelity.
from qiskit_aer import AerSimulator
sim = AerSimulator()
ideal_results = sim.run(circuits, shots=8192).result()
ideal_counts = ideal_results.get_counts()
Sekarang kita mendefinisikan SamplerV2 dengan twirling, dan satu lagi tanpa twirling.
from qiskit_ibm_runtime import SamplerV2 as Sampler
shots = 8192
# --- No Twirling ---
sampler_no = Sampler(mode=backend)
sampler_no.options.twirling.enable_gates = False
sampler_no.options.twirling.enable_measure = False
sampler_no.options.default_shots = shots
# --- With Twirling ---
sampler_tw = Sampler(mode=backend)
sampler_tw.options.twirling.enable_gates = True
sampler_tw.options.twirling.enable_measure = False
# sampler_tw.options.twirling.num_randomizations = "auto"
sampler_tw.options.twirling.num_randomizations = 32
sampler_tw.options.twirling.strategy = "active-circuit"
sampler_tw.options.default_shots = shots
Sekarang kita menjalankan job kita.
# Each job took 17 sec (34 sec total) on ibm_fez. Your times might vary.
job_no = sampler_no.run(circuits)
job_tw = sampler_tw.run(circuits)
res_no = job_no.result()
res_tw = job_tw.result()
Kita mendapatkan counts dari masing-masing run pada komputer kuantum nyata.
counts_no = [r.data.c.get_counts() for r in res_no]
counts_tw = [r.data.c.get_counts() for r in res_tw]
Sekarang kita mencari Hellinger fidelity dengan membandingkan masing-masing run ini dengan hasil AerSimulator yang bebas noise.
from qiskit.quantum_info import hellinger_fidelity
f_no = [hellinger_fidelity(counts_no[i], ideal_counts[i]) for i in range(len(circuits))]
f_tw = [hellinger_fidelity(counts_tw[i], ideal_counts[i]) for i in range(len(circuits))]
Sekarang kita memvisualisasikan hasil kita.
import matplotlib.pyplot as plt
plt.figure(figsize=(8, 5))
plt.plot(two_qubit_depths, f_no, "o-", label="No Twirling")
plt.plot(two_qubit_depths, f_tw, "o-", label="With Twirling")
plt.xlabel("Two-qubit transpiled depth")
plt.ylabel("Hellinger Fidelity")
plt.title("RTZ Echo Circuit: Twirling vs No Twirling")
plt.legend()
plt.grid(True)
plt.show()

Sepanjang lesson ini, kita telah memeriksa kasus di mana Pauli twirling digunakan untuk membatasi akumulasi error koheren, sebuah konteks di mana ia bisa disebut error suppression. Namun, Pauli twirling sering kali lebih berguna sebagai alat untuk membentuk ulang perilaku error, mengubah error koheren menjadi bentuk yang lebih dapat diprediksi dan lebih mudah dimodelkan. Kegunaan ini akan menjadi lebih jelas dalam konteks teknik mitigasi error seperti zero-noise extrapolation (ZNE), yang bergantung pada penskalaan noise yang cukup dapat diprediksi. Ini dibahas di lesson berikutnya.