Lewati ke konten utama

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:

∣ψ⟩=H∣0⟩=12(∣0⟩+∣1⟩).|\psi\rangle = H|0\rangle = \frac{1}{\sqrt{2}}\left( |0\rangle +|1\rangle \right).

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.

A Bloch sphere with a state vector initially equal to the plus state. The state then precesses around the z axis while remaining in the xy plane, consistent with time evolution in the presence of a magnetic field oriented along the z axis.

Noise atau coupling dengan lingkungan dapat menyebabkan fase relatif antara basis state berubah. Artinya, probabilitas ∣0⟩|0\rangle dan ∣1⟩|1\rangle 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: B⃗=(0,0,B0)\vec{B} = (0,0,B_0).

Pertimbangkan apa yang terjadi pada state ∣+⟩|+\rangle seiring berjalannya waktu:

∣ψ(t)⟩=e−iHt/ℏ∣ψ(0)⟩→e−iZωBt/2∣ψ(0)⟩|\psi(t)\rangle = e^{-iHt/\hbar}|\psi(0)\rangle \rightarrow e^{-iZ\omega_B t/2}|\psi(0)\rangle

Di sini kita telah menggunakan fakta bahwa medan magnet dalam arah Z menyebabkan presesi mengelilingi sumbu Z dengan frekuensi ωB\omega_B 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 ∣ψ⟩|\psi\rangle, kita menemukan hal berikut:

∣ψ(t)⟩=e−iZωBt/212(∣0⟩+∣1⟩)=12(e−iωBt/2∣0⟩+eiωBt/2∣1⟩)|\psi(t)\rangle = e^{-iZ\omega_B t/2}\frac{1}{\sqrt{2}}\left( |0\rangle +|1\rangle \right) =\frac{1}{\sqrt{2}}\left( e^{-i\omega_B t/2}|0\rangle +e^{i\omega_B t/2}|1\rangle \right)

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.

Three panels, each showing a Bloch sphere. The first shows a pure quantum state vector, the plus state. The second shows a broadened, shorter region of state space, indicating a mixed state with imperfect phase information. The third shows a tiny region near the origin corresponding to complete dephasing.

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 2t02t_0. Pertimbangkan apa yang akan terjadi jika kita mengikuti resep ini:

  • Biarkan fase berubah selama waktu t0t_0

  • Terapkan X gate pada qubit

  • Biarkan coupling lingkungan yang sama terjadi untuk waktu t0t_0 lagi

  • Terapkan X gate kedua

Setelah evolusi waktu awal, kita akan memiliki tepat state di atas. Menerapkan X gate pertama kita memiliki:

X∣ψ(t0)⟩=12(e−iωBt0/2∣1⟩+eiωBt0/2∣0⟩)X|\psi(t_0)\rangle = \frac{1}{\sqrt{2}}\left( e^{-i\omega_B t_0/2}|1\rangle +e^{i\omega_B t_0/2}|0\rangle \right)

Sekarang ketika interval kedua sebesar t0t_0 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 t0t_0 lagi kita mendapatkan state:

e−iZωB(t−t0)/212(e−iωBt0/2∣1⟩+eiωBt0/2∣0⟩)=12(e−iωB(t−t0)/2eiωBt0/2∣1⟩+eiωB(t−t0)/2e−iωBt0/2∣0⟩)e^{-iZ\omega_B (t-t_0)/2}\frac{1}{\sqrt{2}}\left( e^{-i\omega_B t_0/2}|1\rangle +e^{i\omega_B t_0/2}|0\rangle \right) =\frac{1}{\sqrt{2}}\left( e^{-i\omega_B (t-t_0)/2}e^{i\omega_B t_0/2}|1\rangle +e^{i\omega_B (t-t_0)/2}e^{-i\omega_B t_0/2}|0\rangle \right)

Dan memasukkan t=2t0t = 2t_0 kita mendapatkan:

∣ψ(2t0)⟩=12(∣1⟩+∣0⟩)|\psi(2t_0)\rangle=\frac{1}{\sqrt{2}}\left(|1\rangle +|0\rangle \right)

Menerapkan X gate terakhir tidak melakukan apa pun dalam kasus ini, tetapi umumnya diperlukan:

∣ψ(2t0)⟩=12(∣0⟩+∣1⟩)=∣ψ(t=0)⟩|\psi(2t_0)\rangle = \frac{1}{\sqrt{2}}\left(|0\rangle +|1\rangle \right) = |\psi(t=0)\rangle

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 ∣+⟩|+\rangle. Periksa apakah langkah-langkah yang sama di atas juga mengembalikan state ∣+i⟩|+i\rangle ke state awalnya di bawah asumsi yang sama tentang medan magnet yang berubah lambat sepanjang arah z.

Jawaban
∣ψ(t)⟩=e−iZωBt/212(∣0⟩+i∣1⟩)=12(e−iωBt/2∣0⟩+ieiωBt/2∣1⟩)|\psi(t)\rangle = e^{-iZ\omega_B t/2}\frac{1}{\sqrt{2}}\left( |0\rangle +i|1\rangle \right) =\frac{1}{\sqrt{2}}\left( e^{-i\omega_B t/2}|0\rangle +i e^{i\omega_B t/2}|1\rangle \right)

Setelah evolusi waktu awal, kita akan memiliki tepat state di atas. Menerapkan X gate pertama kita memiliki:

X∣ψ(t0)⟩=12(e−iωBt0/2∣1⟩+ieiωBt0/2∣0⟩)X|\psi(t_0)\rangle = \frac{1}{\sqrt{2}}\left( e^{-i\omega_B t_0/2}|1\rangle +i e^{i\omega_B t_0/2}|0\rangle \right)

Sekarang ketika interval kedua sebesar t0t_0 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 t0t_0 lagi kita mendapatkan state:

e−iZωB(t−t0)/212(e−iωBt0/2∣1⟩+ieiωBt0/2∣0⟩)=12(e−iωB(t−t0)/2eiωBt0/2∣1⟩+ieiωB(t−t0)/2e−iωBt0/2∣0⟩)e^{-iZ\omega_B (t-t_0)/2}\frac{1}{\sqrt{2}}\left( e^{-i\omega_B t_0/2}|1\rangle +i e^{i\omega_B t_0/2}|0\rangle \right) =\frac{1}{\sqrt{2}}\left( e^{-i\omega_B (t-t_0)/2}e^{i\omega_B t_0/2}|1\rangle +i e^{i\omega_B (t-t_0)/2}e^{-i\omega_B t_0/2}|0\rangle \right)

Dan memasukkan t=2t0t = 2t_0 kita mendapatkan

∣ψ(2t0)⟩=12(∣1⟩+i∣0⟩)|\psi(2t_0)\rangle=\frac{1}{\sqrt{2}}\left(|1\rangle +i |0\rangle \right)

Menerapkan X gate terakhir, kita mendapatkan:

∣ψ(2t0)⟩=12(∣0⟩+i∣1⟩)=∣ψ(t=0)⟩=∣+i⟩|\psi(2t_0)\rangle = \frac{1}{\sqrt{2}}\left(|0\rangle +i |1\rangle \right) = |\psi(t=0)\rangle = |+i\rangle

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.

A quantum circuit showing a sequence of four quantum gates used in dynamical decoupling: an X gate, a Y gate, a second X gate, and a second Y gate.

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 ∣+⟩|+\rangle, yang kemudian tetap idle untuk waktu yang lama. Tanpa adanya error, menyiapkan state multi-qubit ∣ψ⟩=∣+⟩⊗N|\psi\rangle = |+\rangle^{\otimes N} 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:

f≡1N∑j=0N−1⟨Ψ∣Xj∣Ψ⟩f\equiv \frac{1}{N}\sum_{j=0}^{N-1}{\langle\Psi | X_j |\Psi\rangle}

di mana Xj≡III..X...IIX_j \equiv III..X...II dengan operator X pada posisi ke-jthj^{th} dari kanan dan ∣Ψ⟩|\Psi\rangle 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 ∣+⟩|+\rangle 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 ∣+⟩|+\rangle), menerapkan delay, merotasi menggunakan H gate, lalu mengukur. Ingat bahwa Hadamard gate (H) memetakan ∣+⟩|+\rangle ke ∣0⟩|0\rangle, jadi mengukur ∣0⟩|0\rangle dalam basis Z setelah Hadamard sesuai dengan qubit yang berada dalam state ∣+⟩|+\rangle 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")

Output of the previous code cell

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

Output of the previous code cell

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 ∣0⟩|0\rangle daripada ∣1⟩|1\rangle, 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()

Output of the previous code cell

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 ∣+⟩|+\rangle. 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
YXYX∣+⟩=YXYX(∣0⟩+∣1⟩)=YXY(∣0⟩+∣1⟩)=YX(i∣1⟩−i∣0⟩)=Y(i∣0⟩−i∣1⟩)=(−∣0⟩−∣1⟩)=−∣+⟩\begin{aligned} YXYX|+\rangle & = YXYX(|0\rangle+|1\rangle)\\ & = YXY(|0\rangle+|1\rangle)\\ & = YX(i|1\rangle-i|0\rangle)\\ & = Y(i|0\rangle-i|1\rangle)\\ & = (-|0\rangle-|1\rangle)\\ & =-|+\rangle \end{aligned}

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 Rx(θ0)R_x(\theta_0), yang merotasi mengelilingi sumbu X tepat sebesar θ0\theta_0. Tentu saja, implementasi gate tidak sempurna, dan rotasi aktualnya mungkin θ0+Δθ\theta_0+\Delta\theta untuk satu implementasi, dan bahkan bisa jadi Δθ\Delta\theta selalu memiliki tanda yang sama, dan mungkin memiliki besaran yang mirip di banyak aplikasi Rx(θ)R_x(\theta). Jadi penerapan berulang dari rotation gate dapat mengakibatkan akumulasi koheren dari banyak over-rotation (atau under-rotation) ini, Δθ\Delta \theta.

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.

Two images. First, vectors denoting errors arranged in a line such that they add up coherently to a large error. Second, vectors in random directions being added to yield a smaller net effect, as in incoherent error accumulation.

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 P∈{X,Y,Z,I}P \in \{X, Y, Z, I\} sebelum dan sesudah operasi gate yang diinginkan UU sedemikian rupa sehingga P1UP2=UP_1 U P_2 = U. Di sini, P1P_1 dan P2P_2 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 UU dan tujuannya adalah membuat aksi logis dari semua gate tetap sama dengan UU. Terkadang UU 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.

Four images showing pieces of four quantum circuits, each with two qubits. The first is a simple CNOT gate. The others each show a CNOT gate but surrounded by Pauli gates in a way that preserves the overall logic of a CNOT operation.

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 q0q_0 dan q1q_1 adalah:

∣ψinit⟩=a∣00⟩+b∣01⟩+c∣10⟩+d∣11⟩|\psi_\text{init}\rangle = a|00\rangle + b|01\rangle + c|10\rangle + d|11\rangle

Seperti biasa, kita menggunakan konvensi urutan qubit ∣q1,q0⟩|q_1,q_0\rangle. Menerapkan CX gate dengan q1q_1 sebagai target menghasilkan

CX∣ψinit⟩=∣ψfinal⟩=a∣00⟩+b∣11⟩+c∣10⟩+d∣01⟩CX|\psi_\text{init}\rangle = |\psi_\text{final}\rangle = a|00\rangle + b|11\rangle + c|10\rangle + d|01\rangle

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.

A CNOT operation on a two-qubit quantum circuit, surrounded by three X gates. Points are labeled a-d at the beginning of the circuit, after an X gate on the control qubit, after the CNOT gate, and after a final X gate is applied to each of the two qubits, respectively.

∣ψa⟩=a∣00⟩+b∣01⟩+c∣10⟩+d∣11⟩∣ψb⟩=a∣01⟩+b∣00⟩+c∣11⟩+d∣10⟩∣ψc⟩=a∣11⟩+b∣00⟩+c∣01⟩+d∣10⟩∣ψd⟩=a∣00⟩+b∣11⟩+c∣10⟩+d∣01⟩|\psi_a\rangle = a|00\rangle + b|01\rangle + c|10\rangle + d|11\rangle\\ |\psi_b\rangle = a|01\rangle + b|00\rangle + c|11\rangle + d|10\rangle\\ |\psi_c\rangle = a|11\rangle + b|00\rangle + c|01\rangle + d|10\rangle\\ |\psi_d\rangle = a|00\rangle + b|11\rangle + c|10\rangle + d|01\rangle

Ini persis ∣ψfinal⟩|\psi_\text{final}\rangle 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.

A quantum circuit with four qubits and three CNOT gates in a ladder arrangement. In twirling, each of these CNOT gates is surrounded by a different set of Pauli gates.

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.

A quantum circuit with two qubits. The initial state is labeled &quot;a&quot;, then an X gate acts on qubit 0 and a Y gate acts on qubit 1. After that, the state is labeled &quot;b&quot;. Then a CNOT gate acts with qubit 0 as the control and qubit 1 as the target. After that, CNOT gate the state is labeled &quot;c&quot;. Finally, a Y gate acts on qubit 0 and a Z gate acts on qubit 1. The final state is labeled &quot;d&quot;.

∣ψa⟩=a∣00⟩+b∣01⟩+c∣10⟩+d∣11⟩∣ψb⟩=ai∣11⟩+bi∣10⟩−ci∣01⟩−di∣00⟩∣ψc⟩=ai∣01⟩+bi∣10⟩−ci∣11⟩−di∣00⟩∣ψd⟩=ai(+1)(−i)∣00⟩+bi(−1)(i)∣11⟩−ci(−1)(−i)∣10⟩−di(+1)(i)∣01⟩∣ψd⟩=a∣00⟩+b∣11⟩+c∣10⟩+d∣01⟩\begin{aligned} |\psi_a\rangle & = a|00\rangle + b|01\rangle + c|10\rangle + d|11\rangle\\ |\psi_b\rangle & = ai|11\rangle + bi|10\rangle - ci|01\rangle - di|00\rangle\\ |\psi_c\rangle & = ai|01\rangle + bi|10\rangle - ci|11\rangle - di|00\rangle\\ |\psi_d\rangle & = ai(+1)(-i)|00\rangle + bi(-1)(i)|11\rangle - ci(-1)(-i)|10\rangle - di(+1)(i)|01\rangle\\ |\psi_d\rangle & = a|00\rangle + b|11\rangle + c|10\rangle + d|01\rangle \end{aligned}

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 ZtZ_t sebelum CNOT, dan ZtZ_t serta ZcZ_c 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 ∣+⟩|+\rangle), 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 ⟨X⟩\langle X \rangle 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 ∣0⟩|0\rangle sesuai dengan state yang berada dalam ∣+⟩|+\rangle tepat sebelum Hadamard gate terakhir, dan serupa untuk ∣1⟩|1\rangle dan ∣−⟩|-\rangle. Oleh karena itu, nilai ekspektasi kita ⟨X⟩\langle X \rangle dapat dihitung dengan sederhana dari count ∣0⟩|0\rangle dikurangi count ∣1⟩|1\rangle.

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

Output of the previous code cell

Kita dapat dengan jelas melihat bahwa sirkuit yang di-twirl menghasilkan nilai ekspektasi yang lebih dekat ke ideal ⟨X⟩=1\langle X \rangle = 1. 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")

Output of the previous code cell

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")

Output of the previous code cell

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).

A diagram of the layout of qubits on a quantum computer called ibm_fez. It shows how qubits are chosen to minimize swapping of information.

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 ∣0⟩⊗N|0\rangle^{\otimes N} dan setengah lainnya mengembalikan ∣1⟩⊗N|1\rangle^{\otimes N}. 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()

Output of the previous code cell

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 CX~\tilde{CX}:

CX~≡e−iϵK⋅CX\tilde{CX}\equiv e^{-i\epsilon K} \cdot CX

dengan KK adalah produk operator Pauli apa pun, seperti XX, ZZ, Z⊗XZ \otimes X, Z⊗NZ^{\otimes N}, 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 ZiZjZ_i Z_j dan Z⊗NZ^{\otimes N}. Dalam konteks menyiapkan state GHZ, error over-rotation yang relevan dari jenis ini adalah:

CX~≡e−iϵZcZt⋅CX\tilde{CX}\equiv e^{-i\epsilon Z_c Z_t} \cdot CX

Persiapan seluruh state GHZ NN-qubit akan terlihat seperti:

∣ψGHZ⟩=∏j=0N−2e−iϵZjZj+1⋅CXj,j+1H⊗N∣0⊗N⟩|\psi_\text{GHZ}\rangle=\prod_{j=0}^{N-2}{e^{-i\epsilon Z_j Z_{j+1}} \cdot CX_{j,j+1}}H^{\otimes N}|0^{\otimes N}\rangle

Setelah menyiapkan state GHZ, secara ideal state-nya adalah:

∣GHZ⊗N⟩=12(∣0…0⟩+∣1…1⟩).|\text{GHZ}^{\otimes N}\rangle=\frac{1}{\sqrt{2}}\left(|0\dots 0\rangle + |1\dots 1\rangle\right).

State ini adalah eigenstate simultan dari sekumpulan besar operator Pauli, termasuk yang berikut ini:

ZiZjfor all i≠j,Z_i Z_j \quad \text{for all } i \neq j,

dengan eigenvalue +1+1. Akibatnya, operator berbentuk e−iϵZiZje^{-i\epsilon Z_i Z_j} bekerja pada state GHZ sebagai perkalian dengan faktor fase e−iϵe^{-i\epsilon}, yang tidak memengaruhi observable GHZ standar seperti parity, X⊗NX^{\otimes N} kolektif, atau populasi basis komputasional. Jadi, meskipun error over-rotation e−iϵZjZj+1e^{-i\epsilon Z_j Z_{j+1}} 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 ZjZj+1Z_jZ_{j+1} yang deterministik. Sebaliknya, ia mengubah channel error koheren menjadi channel Pauli stokastik efektif. Akibatnya, channel error sekarang mencakup term seperti XX, YY, X⊗ZX \otimes Z, dan Y⊗XY \otimes X, 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 ∣0…0⟩|0\dots 0\rangle dan ∣1…1⟩|1\dots 1\rangle, 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 ∣0⟩⊗N|0\rangle^{\otimes N} dan ∣1⟩⊗N|1\rangle^{\otimes N}.

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")

Output of the previous code cell

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

Output of the previous code cell

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.