Penyekatan dan penyesuaian ralat
Penyekatan ralat boleh merujuk kepada mana-mana teknik yang menjangka dan cuba mengelakkan jenis hingar dan ralat tertentu. Ini paling mudah diterangkan melalui contoh konkrit, tetapi ingat bahawa kaedah ini tidak terhad kepada contoh yang ditunjukkan di sini, dan kaedah baharu sentiasa diterokai. Kadangkala tidak mungkin untuk menyekat ralat, tetapi mungkin untuk mempengaruhi cara ia terkumpul. Jika kita boleh membuat ralat terkumpul dengan lebih perlahan, kita mungkin menggambarkan ini sebagai menyekat ralat keseluruhan, tetapi ia lebih tepat dilihat sebagai penyesuaian hingar. Dalam pelajaran ini kami merangkumi contoh kanonik penyekatan ralat (penyahgandingan dinamik) dan penyesuaian/pembentukan hingar (Pauli twirling).
Video untuk melengkapi pelajaran ini akan dilancarkan dalam beberapa hari akan datang, dan akan dibenamkan di sini.
Penyahgandingan dinamik
Mari kita mulakan dengan keadaan yang sangat mudah pada satu qubit, dan perkenalkan model hingar yang mudah. Ini tidak mencukupi untuk menerangkan sepenuhnya penyahgandingan dinamik, tetapi ia memberi kita contoh yang jelas. Andaikan kita mempunyai qubit yang disediakan dalam keadaan superposisi menggunakan get Hadamard:
Keadaan ini divisualisasikan pada sfera Bloch seperti yang ditunjukkan di bawah di sebelah kiri. Dalam kes ideal dengan sifar hingar, qubit ini akan kekal dalam keadaan ini sehingga operasi seterusnya dijalankan. Walau bagaimanapun, kita tahu daripada pelajaran sebelumnya bahawa ini bukan apa yang kita perhatikan. Hingar menyebabkan maklumat kuantum merosot.

Hingar atau gandingan persekitaran boleh menyebabkan fasa relatif antara keadaan asas berubah. Iaitu, kebarangkalian dan tidak berubah, kerana nilai mutlak pekali mereka tidak berubah. Sebaliknya, fasa amplitud berubah, mengubah komponen nyata dan khayalan mereka. Untuk menjadikan perbincangan ini lebih konkrit, mari kita pertimbangkan satu jenis interaksi yang boleh menyebabkan ini: gandingan kepada medan magnet yang berorientasikan dalam arah Z: .
Pertimbangkan apa yang berlaku kepada keadaan apabila masa berlalu:
Di sini kita telah menggunakan fakta bahawa medan magnet dalam arah Z menyebabkan presesi mengelilingi paksi Z dengan frekuensi yang bergantung pada momen magnet berkesan dan kekuatan medan magnet. Butiran adalah kurang penting berbanding fakta bahawa interaksi ini menyebabkan evolusi mengelilingi paksi Z, menghasilkan pengumpulan fasa bertentangan untuk kedua-dua keadaan asas pengkomputeran. Menggunakan operator ini kepada setiap sebutan dalam , kita dapati yang berikut:
Fasa bergantung masa ini sepadan dengan presesi mengelilingi paksi Z dalam gambaran sfera Bloch. Ini ditunjukkan pada separuh kanan rajah di atas.
Jika kita tahu bahawa interaksi ini berlaku dengan cara yang terkawal, kita boleh meramalkannya. Untuk memodelkan jenis hingar ini, kita sebaliknya mempertimbangkan taburan kekuatan medan magnet yang mungkin, setiap satu berlaku dengan kebarangkalian klasik tertentu (sumber hingar lain perlu dimodelkan secara berbeza). Kita perlu mempertimbangkan apa yang berlaku apabila terdapat kebarangkalian klasik bukan sifar bahawa keadaan tidak berputar langsung, dan juga sesetengah kebarangkalian bahawa ia telah berputar sedikit, atau bahkan dengan banyak. Taburan putaran yang mungkin ini adalah sebab penyebaran keadaan dalam gambaran sfera Bloch di bawah. Jika terdapat lebih banyak hingar penyahfasaan sebegini, fasa akan menjadi kurang jelas takrifannya. Dalam had penyahfasaan kuat, qubit menjadi sepenuhnya dinyahfasakan, sepadan dengan kehilangan koherens kuantum yang disimpan dalam keadaan tersebut.

Sudah tentu, dalam eksperimen kita tidak tahu gandingan rawak apa yang akan berlaku. Apa yang boleh kita lakukan mengenai ini?
Andaikan gandingan persekitaran kekal kira-kira malar sepanjang selang masa . Pertimbangkan apa yang akan berlaku jika kita mengikut preskripsi ini:
-
Benarkan fasa berubah untuk masa
-
Gunakan get X pada qubit
-
Benarkan gandingan persekitaran yang sama berlaku untuk masa lanjutan
-
Gunakan get X kedua
Selepas evolusi masa awal, kita akan mempunyai tepat keadaan di atas. Menggunakan get X pertama kita ada:
Kini apabila selang kedua berlalu, interaksi yang sama berlaku. Tetapi kini amplitud dikaitkan dengan eigenstate Z yang bertentangan, bermakna tanda putaran mengelilingi paksi Z telah bertukar. Selepas membenarkan sistem berkembang dalam masa untuk yang lain kita ada keadaan:
Dan memasukkan kita ada:
Menggunakan get X terakhir tidak melakukan apa-apa dalam kes ini, tetapi ia amnya diperlukan:
Kita telah memulihkan keadaan kuantum asal, termasuk fasa relatifnya. Proses ini adalah contoh yang amat mudah bagi penyahgandingan dinamik.
Secara lebih umum, penyahgandingan dinamik (DD) melibatkan penyisipan beberapa get satu-qubit untuk mengurangkan kesan interaksi dengan sistem di luar qubit (menyahgandingkannya daripada persekitaran). Penumpuan semula sepenuhnya bagi semua sejarah fasa yang mungkin kembali kepada keadaan asal, seperti yang ditunjukkan di atas, agak diidealkan, tetapi ia masih senario yang mungkin. Mari kita bincangkan bila DD berguna dan apakah amaran yang wujud.
Semak kefahaman anda
Dalam teks di atas, kami melangkah melalui kesan jujukan DD XX pada keadaan awal . Semak sama ada langkah yang sama di atas juga mengembalikan keadaan kembali kepada keadaan awalnya di bawah andaian yang sama iaitu medan magnet yang berubah perlahan sepanjang arah z.
Jawapan
Selepas evolusi masa awal, kita akan mempunyai tepat keadaan di atas. Menggunakan get X pertama kita ada:
Kini apabila selang kedua berlalu, interaksi yang sama berlaku. Tetapi kini amplitud dikaitkan dengan eigenstate Z yang bertentangan, bermakna tanda putaran mengelilingi paksi Z telah bertukar. Selepas membenarkan sistem berkembang dalam masa untuk yang lain kita ada keadaan:
Dan memasukkan kita ada
Menggunakan get X terakhir, kita ada:
Bila menggunakan DD
Amaran pertama dan paling jelas ialah kita mengandaikan saluran penyahfasaan tulen dalam rawatan kita. Interaksi dunia sebenar biasanya menghasilkan gabungan penyahfasaan dan mekanisme ralat lain. Dalam rawatan di atas, kami memilih untuk menggunakan get X secara khusus. Ini dirujuk sebagai jujukan XX dalam penyahgandingan dinamik. Jujukan tertentu ini sesuai untuk ralat penyahfasaan tulen. Tetapi terdapat jujukan lain yang mungkin lebih boleh digunakan secara meluas, seperti XY4 (ditunjukkan dalam gambar rajah litar di bawah) dan XY8 yang lebih kompleks.
Amaran lain ialah penyahgandingan dinamik menambah get satu-qubit, yang boleh menambah ralat satu-qubit disebabkan oleh ketidaksempurnaan get atau juga crosstalk. Kadar ralat satu-qubit ini biasanya jauh lebih rendah daripada ralat get berbilang-qubit, jadi ini biasanya bukan kebimbangan utama, tetapi ia adalah sesuatu yang perlu diingat jika banyak qubit menggunakan DD berkali-kali sepanjang litar anda.
DD berguna apabila gandingan luaran mempunyai masa untuk mempengaruhi keadaan qubit. Kerana qubit disekat dengan teliti dan gandingan luaran sepatutnya lemah, jenis hingar ini paling ketara apabila qubit terbiar untuk tempoh yang lama. Untuk masa terbiar yang singkat, kesan DD boleh menambah ralat get satu-qubit sambil menyekat hingar yang sangat sedikit; ketepatan litar anda sebenarnya boleh berkurangan.
Perkara penting: Gunakan penyahgandingan dinamik apabila qubit kekal terbiar untuk tempoh yang cukup lama, dan beri perhatian kepada jenis jujukan DD yang digunakan.
Penyahgandingan dinamik menggunakan Qiskit
Mari kita terokai penggunaan DD dengan mengkaji kes beberapa qubit yang disediakan dalam keadaan , yang kemudian kekal terbiar untuk masa yang lama. Tanpa ralat, menyediakan keadaan berbilang-qubit dan kemudian menggunakan get Hadamard sebelum pengukuran (dengan itu mengukur dalam asas X) sepatutnya sentiasa menghasilkan 0. Dengan hingar, hasil pengukuran akan menjadi 0 hanya sebahagian daripada masa dan bukannya dengan kebarangkalian 100%. Kami mengira nilai jangkaan purata X merentas berbilang qubit. Iaitu, kami berminat dengan yang berikut:
di mana dengan operator X pada kedudukan ke- dari kanan dan ialah keadaan keseluruhan sistem.
Litar ini mengandungi kelewatan yang disengajakan, yang agak dibuat-buat untuk penanda aras. Walau bagaimanapun, adalah sangat biasa untuk litar sebenar mengandungi qubit yang terbiar semasa sebahagian daripada pelaksanaan. Anda boleh menganggap ini sebagai model ringkas bagi litar yang lebih kompleks di mana sesetengah qubit kekal terbiar untuk sebahagian daripada pengiraan. Jenis penanda aras ini berdasarkan evolusi keadaan sering dirujuk sebagai penanda aras Ramsey.
Kami bermula dengan memuatkan pakej yang diperlukan dan mengkonfigurasi perkhidmatan.
# 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 kami mentakrifkan beberapa fungsi pembantu. Pertama, kami mahu menangani perkara mengenai masa terbiar yang lama. Apa sebenarnya maksud "lama" dalam konteks ini? Kami boleh sekadar melaporkan masa terbiar dalam mikrosaat. Walau bagaimanapun, berguna untuk mengetahui berapa banyak get dua-qubit yang boleh dilaksanakan sepanjang selang yang sama itu. Ini menjadikan masa terbiar lebih menunjukkan secara langsung kedalaman litar yang boleh dilaksanakan sepanjang tempoh yang sama. Fungsi pembantu pertama mendapatkan get dua-qubit asli kepada backend dan menentukan tempoh get tersebut.
Fungsi kedua hanya mencipta litar Ramsey (satu dengan qubit dalam keadaan ), melaksanakan kelewatan, memutar menggunakan get H, dan kemudian mengukur. Ingat bahawa get Hadamard (H) memetakan kepada , jadi mengukur dalam asas Z selepas Hadamard sepadan dengan qubit yang berada dalam sejurus sebelum Hadamard.
Fungsi terakhir kami menukar kiraan mentah bagi hasil pengukuran 0 dan 1 kepada nilai jangkaan 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 kami menentukan butiran ujian kami, termasuk bilangan qubit dan jujukan get yang akan digunakan dalam DD (dalam kes ini XX). Perhatikan terutamanya bahawa kami menetapkan tahap pengoptimuman kepada sifar. Dalam praktiknya, anda sering akan memilih tahap pengoptimuman yang lebih tinggi; di sini kami menggunakan tahap 0 untuk memastikan kesan ralat yang disasarkan oleh DD kekal kelihatan. Akhir sekali, kami menentukan masa ciri untuk get dua-qubit pada backend ini dan mencetak beberapa masa yang berkaitan.
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 kami membina litar kami dan mentranspil untuk backend pilihan kami.
# --------- 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']
Kami perlu memvisualisasikan sekurang-kurangnya satu litar untuk memastikan kami telah mengekod litar kami dengan keadaan dan kelewatan yang dikehendaki. Mungkin lebih mudah untuk memvisualisasikan litar pra-transpil qc, atau anda boleh menyemak litar yang ditranspil qc_isa.
qc.draw("mpl")

Laksanakan
Akhirnya kami bersedia untuk melaksanakan pada perkakasan. Kami menggunakan Sampler untuk mendapatkan banyak pengukuran bagi setiap qubit, dan kami akan menggunakannya dua kali: sekali dengan DD dimatikan secara eksplisit, dan sekali dengan DD dihidupkan dan menggunakan jujukan get 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)
Kami boleh mengekstrak kiraan daripada pelbagai litar.
# --------- 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-pemprosesan
Sekarang kami mempunyai kiraan daripada pengukuran, tetapi kami mahu menukarkannya kepada nilai jangkaan X dan kemudian purata nilai jangkaan tersebut merentas semua qubit yang kami gunakan, untuk mengetahui tentang pemeliharaan maklumat fasa. Untuk itu kami menggunakan fungsi counts_to_x_expectations kami yang ditakrifkan sebelum ini.
# --------- 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]
Akhirnya, mari kita plotkan hasil kami.
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 anda lihat, tanpa kelewatan eksplisit, nilai jangkaan memang hampir dengan 1, yang kami jangkakan jika semua maklumat fasa dipelihara. Jika semua maklumat fasa hilang, tidak akan ada keutamaan untuk putaran akhir menghasilkan berbanding , dan nilai jangkaan purata akan menghampiri sifar. Dalam data, kita lihat bahawa apabila masa kelewatan meningkat, nilai jangkaan purata X berkurangan, mula menghampiri sifar. Perhatikan bahawa DD sangat berkesan di sini; nilai jangkaan dengan DD biasanya lebih daripada 20% lebih baik (lebih hampir dengan 1) berbanding nilai tanpa DD. Tetapi perhatikan juga bahawa titik data pertama yang dilewatkan sepadan dengan kelewatan yang lebih kurang setara dengan 100 operasi get dua-qubit. Ini mengukuhkan lagi bahawa DD paling berguna apabila qubit kekal terbiar untuk tempoh yang agak lama.
Semak kefahaman anda
Jika kita menggunakan DD menggunakan XY4 pada litar yang sama seperti sebelum ini, adakah anda menjangkakan ia akan menghasilkan keputusan yang jauh lebih baik, jauh lebih teruk, atau lebih kurang sama, seperti DD menggunakan XX? Terangkan.
Jawapan
Lebih kurang sama, mungkin sedikit lebih teruk. Litar yang kami gunakan mempunyai keadaan yang diputarkan ke dalam satah XY. Keadaan ini menyimpan maklumat terutamanya dalam fasanya, menjadikannya amat sensitif kepada ralat penyahfasaan berbanding pelonggaran T1. XY4 mungkin membantu dengan pelbagai jenis ralat yang lebih luas, tetapi XX sudah dioptimumkan untuk membantu litar yang kami gunakan. XY4 mungkin sama baiknya, tetapi tidak akan menambah apa-apa yang substansial, atau hakikat bahawa XY4 mengandungi lebih banyak get mungkin membolehkan ralat get tambahan menjadikan hasil sedikit lebih teruk.
Hasil terakhir menggunakan jujukan get DD paling mudah XX. Mari kita lihat cara melaksanakan jujukan yang lebih kompleks, XY4. Kami mentakrifkan Sampler dalam bahagian seterusnya.
# --------- 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 lihat bahawa XY4 tidak jauh berbeza daripada XX. Ia mungkin sedikit lebih teruk disebabkan get tambahan dalam jujukan XY4, tetapi yang lebih penting, kami sudah menerangkan sebab XX akan mempunyai kesan yang dikehendaki dalam memelihara fasa khusus untuk keadaan seperti . Tiada sebab untuk berfikir bahawa untuk keadaan awal sebegitu, jujukan yang berbeza akan meningkatkan hasil.
Semak kefahaman anda
Sahkan bahawa jujukan XY4 meninggalkan keadaan tidak berubah sehingga fasa global.
Jawapan
Pauli twirling
Kita perlu mulakan dengan menyatakan bahawa Pauli twirling sering digunakan bukan sebagai teknik penyekatan ralat, tetapi sebagai teknik pembentukan ralat: ia membuat hingar/ralat berkelakuan berbeza, kadangkala lebih boleh diramal, untuk membolehkan kaedah lain. Walaupun Pauli twirling tidak menghalang ralat, ia mungkin menghalang pengumpulan koheren mereka.
Dalam litar kuantum, pelbagai sumber ralat yang berbeza bertambah bersama. Ralat boleh bertambah dengan cara yang berbeza, terutamanya secara koheren dan secara tidak koheren. Pengumpulan koheren ralat bermaksud bahawa hingar atau pelaksanaan yang tidak sempurna semuanya cenderung untuk menggerakkan ralat dalam arah yang sama merentas berbilang lapisan dan get. Satu contoh ialah lebih-putaran koheren apabila menggunakan get putaran.
Pertimbangkan get putaran ideal, seperti , yang memutar mengelilingi paksi X dengan tepat . Sudah tentu, pelaksanaan get tidak sempurna, dan putaran sebenar mungkin untuk satu pelaksanaan, dan mungkin juga berlaku bahawa sentiasa mempunyai tanda yang sama, dan mungkin serupa dari segi magnitud merentas banyak penggunaan . Oleh itu penggunaan berulang get putaran boleh menghasilkan pengumpulan koheren bagi banyak lebih-putaran (atau kurang-putaran) ini, .
Pengumpulan ralat tidak koheren adalah sebaliknya: ralat dalam arah rawak dengan tanda rawak, sedemikian rupa sehingga ralat dalam lapisan berbeza tidak sentiasa mengganggu secara aditif, tetapi kadangkala saling batal atau bertambah secara kuadratur. Jelas sekali ralat tidak koheren terkumpul dengan lebih perlahan dari segi kesan keseluruhan pada keadaan qubit. Rajah mainan ini ditunjukkan dalam rajah di bawah. Ini adalah penyederhanaan. Ralat kuantum sebenar tidak terhad kepada ruang Cartesian dua dimensi; tidak semua sumbangan ralat akan mempunyai magnitud yang sama, dan terdapat lebih banyak kerumitan. Tetapi intuisi gambaran sebegini adalah berguna: ralat koheren cenderung terkumpul lebih cepat daripada ralat tidak koheren.

Seseorang selalunya boleh mendapatkan hasil dengan ketepatan lebih tinggi dengan menukar pengumpulan ralat koheren kepada pengumpulan ralat tidak koheren. Cara utama untuk mencapai ini dipanggil Pauli twirling.
Pauli twirling merujuk kepada penambahan gabungan get Pauli sebelum dan selepas operasi get yang dikehendaki sedemikian rupa sehingga . Di sini, dan bukan Pauli tunggal, tetapi koleksi operator Pauli yang selalunya bertindak pada berbilang qubit. Anda kadangkala mungkin melihat ia dinyatakan bahawa tindakan get Pauli tambahan adalah "setara dengan identiti". Tetapi ini tidak tepat dan berpotensi mengelirukan. Get Pauli dipisahkan oleh dan matlamatnya adalah untuk meninggalkan tindakan logik semua get sama dengan . Kadangkala dipanggil "muatan" untuk membezakan operasi yang dimaksudkan ini daripada get yang ditambah untuk penyekatan. Beberapa contoh Pauli twirling mengelilingi get CNOT ditunjukkan di bawah.
Mari kita lalui hanya satu contoh untuk mengesahkan bahawa twirling meninggalkan kesan logik muatan tidak berubah. Tanpa kehilangan keumuman, biarkan keadaan dua-qubit yang melibatkan dan ialah:
Seperti biasa, kami menggunakan konvensyen susunan qubit . Menggunakan get CX dengan sebagai sasaran menghasilkan
Sekarang mari kita pertimbangkan litar ketiga yang ditunjukkan, menggunakan get X untuk Pauli twirling. Kita boleh sekadar mendarabkan matriks bersama-sama dan mengesahkan bahawa mereka menghasilkan matriks CNOT. Sebagai alternatif, kita boleh mengesan operasi pada keadaan kuantum sewenang-wenangnya melalui litar, seperti yang kita lakukan di bawah. Keadaan pada titik berbeza dalam litar telah dilabelkan a-d.
Ini adalah tepat yang kita peroleh sebelum ini tanpa twirling. Sesungguhnya, jujukan get yang di-twirl ini meninggalkan tindakan logik muatan tidak berubah. Walau bagaimanapun, jika jujukan twirling yang sah berbeza dipilih secara rawak dari lapisan ke lapisan, pengumpulan ralat koheren boleh ditukar menjadi pengumpulan ralat stokastik (tidak koheren) secara berkesan. Untuk kejelasan, seseorang tidak memilih satu corak twirling dan menggunakannya sepanjang litar. Sebaliknya, jujukan twirling yang sah berbeza dipilih untuk lapisan yang berbeza. Contoh dengan banyak lapisan penjeratan mungkin kelihatan seperti ini.

Semak kefahaman anda
Sahkan bahawa Pauli twirling dalam panel keempat dalam rajah di atas juga meninggalkan kesan logik CNOT tidak berubah.
Jawapan
Kami mengikut contoh di atas dan menunjukkan bahawa tindakan pada mana-mana keadaan dua-qubit sewenang-wenangnya adalah setara dengan operasi CNOT. Kami akan merujuk kepada titik berlabel dalam gambar rajah ini.
Ini setara dengan tindakan CNOT dengan qubit 0 sebagai kawalan dan qubit 1 sebagai sasaran.
Bolehkah anda mencipta jujukan Pauli twirling untuk get CNOT yang tidak ditunjukkan di atas?
Jawapan
Ya, terdapat banyak yang lain. Satu contoh ialah sebelum CNOT, dan dan selepas CNOT.
Bila menggunakan Pauli twirling
Seperti yang dibentangkan di sini, Pauli twirling hanya digunakan pada get berbilang-qubit. Menggunakan protokol yang serupa pada get satu-qubit memerlukan logik yang berbeza dan amnya tidak berguna dalam praktik. Pauli twirling sendiri menggunakan beberapa get satu-qubit (get Pauli). Get Pauli tambahan berkemungkinan memperkenalkan lebih banyak ralat berbanding yang diperolehi daripada merawakkan mana-mana pengumpulan ralat koheren. Kadar ralat yang dikaitkan dengan get dua-qubit adalah jauh lebih besar berbanding yang dikaitkan dengan get satu-qubit. Selanjutnya, sesetengah get satu-qubit adalah bukan-Clifford, yang tidak boleh di-twirl sepenuhnya. Inilah sebabnya Qiskit merangkumi pilihan Pauli twirling yang secara automatik men-twirl mengelilingi get dua-qubit, dan bukan mengelilingi get satu-qubit.
Ini tersirat dalam rajah di atas: Pauli twirling dilaksanakan mengelilingi get CX, tetapi tidak mengelilingi get Hadamard.
Mari kita lihat dua contoh Pauli twirling beraksi.
Pauli twirling untuk menyekat pengumpulan koheren
Untuk memerhati bagaimana Pauli twirling boleh menukar pengumpulan ralat koheren kepada pengumpulan tidak koheren yang lebih perlahan, kami mahukan litar dan boleh cerap yang berfungsi sebagai ujian tekanan ralat-koheren. Tujuan tunggalnya adalah untuk menjadikan ralat dua-qubit yang terkumpul secara koheren kelihatan, dan kemudian menunjukkan bagaimana Pauli twirling menukar pengumpulan koheren itu kepada kereputan stokastik.
Get CNOT (atau CZ) adalah sumber ralat koheren yang biasa. Eksperimen paling mudah yang boleh kami lakukan dalam kes ini adalah untuk memulakan keadaan (katakan ), menggunakan lapisan get CNOT berpasangan (menggunakan fakta bahawa dua CNOT menghasilkan identiti), dan menyemak bagaimana ralat terkumpul apabila bilangan lapisan meningkat, kedua-duanya dengan dan tanpa Pauli twirling.
Boleh cerap yang menarik minat ialah pada satu qubit, yang kami plotkan sebagai fungsi bilangan 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
Kerana kami telah memutar asas kami sebelum pengukuran, pengukuran sepadan dengan keadaan yang berada dalam sejurus sebelum get Hadamard akhir, dan serupa untuk dan . Oleh itu, nilai jangkaan kami boleh dikira dengan mudah daripada kiraan ditolak kiraan .
# 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
Kami memilih bilangan lapisan CNOT yang munasabah untuk membenarkan ralat terkumpul, membina litar kami, kemudian mentranspilnya.
# 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]
Ingat bahawa setiap lapisan terdiri daripada lebih daripada satu get dua-qubit. Pantau kedalaman dua-qubit yang ditranspil dengan menggunakan fungsi di bawah.
# 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 kami 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
Akhirnya, kami 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()

Kami boleh melihat dengan jelas bahawa litar yang di-twirl menghasilkan nilai jangkaan yang lebih hampir dengan ideal . Contoh ini memenuhi tujuannya, tetapi mari kita beralih kepada sesuatu yang lebih berguna: twirling dalam penghasilan keadaan yang sangat terjerat.
Pauli twirling dalam penyediaan keadaan GHZ
Contoh sebelumnya menunjukkan kes yang meyakinkan untuk Pauli twirling dalam memusnahkan pengumpulan koheren ralat yang dikaitkan dengan get CNOT. Penghasilan keadaan GHZ menggunakan banyak get CNOT untuk menghasilkan keadaan yang sangat terjerat yang berguna untuk banyak aplikasi pengkomputeran kuantum. Mari kita terokai bagaimana Pauli twirling membantu dalam konteks ini, dengan keadaan GHZ yang saiznya semakin meningkat.
# 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 kami membina litar kami dan mentranspilnya. Dalam kes ini, kami tidak mempunyai sebarang get berulang secara buatan yang berkurangan kepada identiti. Jadi kami boleh membenarkan pass manager melakukan sedikit lebih pengoptimuman untuk kami. Kami akan menetapkannya kepada tahap 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)
Kami boleh melihat bahawa pengoptimum memetakan litar abstrak kami kepada qubit 123, 124, 136, 142, dan 143.
circuits[0].draw("mpl")

Untuk memahami sebabnya, mari kita lihat peta susun atur backend kami (dalam imej ini, ibm_fez, tetapi anda boleh melakukan analisis setara pada mana-mana backend).

Kita lihat bahawa qubit dipilih dalam rantai untuk meminimumkan get swap dan dengan itu kedalaman litar. Selanjutnya, setiap get CZ yang dilaksanakan dalam litar adalah antara qubit bersebelahan. Akhir sekali, kesemua lima qubit mempunyai kadar ralat yang agak rendah, termasuk kadar ralat penetapan-bacaan. Anda boleh menyemak kadar ralat ini pada mana-mana backend pada halaman Sumber pengkomputeran. Mencari susun atur sedemikian tidak sukar untuk rantai linear yang mudah, tetapi apabila masalah menjadi lebih kompleks, pengoptimuman susun atur litar menjadi lebih sukar dan lebih bernilai.
Sekarang kami mengkonfigurasi primitif Sampler kami. Kami mematikan alat penyekatan/mitigasi lain untuk memfokus 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))
Akhirnya, kami menjalankan job kami. Anda boleh secara pilihan mencetak nombor ID job untuk pengambilan semula kemudian.
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
Kami mengekstrak kiraan setiap keadaan asas pengkomputeran yang diukur untuk semua litar, kedua-duanya dengan dan 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}
Kami tahu taburan ideal keadaan GHZ ialah separuh daripada shot mengembalikan dan separuh lagi mengembalikan . Bina 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 kami menggunakan ketepatan Hellinger sebagai ukuran kualiti keadaan akhir kami.
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])
)
Akhirnya kami plotkan hasil kami.
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 (dan malah sedikit lebih teruk) berbanding tanpa twirling. Apa yang berlaku?
Dua perkara berlaku. Pertama, Pauli twirling tidak mengurangkan jumlah hingar keseluruhan — sebaliknya, ia membentuk semula ralat koheren dan sistematik kepada ralat jenis Pauli stokastik, supaya pertumbuhan ralat menjadi boleh diramal dan boleh dimodelkan. Tidak pernah ada janji pengurangan ralat, kecuali dalam kes khas.
Kedua, dalam litar GHZ, sesetengah ralat koheren boleh sebahagiannya saling batal atau bertindak seperti anjakan fasa yang tidak berbahaya disebabkan simetri pembinaan GHZ; twirling menghapuskan perlindungan kebetulan ini dan menggantikannya dengan hingar Pauli stokastik yang tidak berkorelasi, jadi ketepatan GHZ menjadi sedikit lebih teruk di bawah twirling.
Dakwaan kedua ini memerlukan sedikit penjelasan. Dakwaannya bukanlah bahawa litar GHZ dilindungi daripada semua jenis pengumpulan ralat koheren, hanya sesetengah jenis — dan dalam kes tersebut perlindungan itu dimusnahkan oleh twirling. Secara khusus, mari kita pertimbangkan lebih-putaran koheren yang dikaitkan dengan get CX. Mari kita namakan get CX sebenar dengan lebih-putaran :
dengan ialah sebarang hasil darab operator Pauli, seperti , , , , dan sebagainya. Untuk keadaan umum, mana-mana putaran-lebih ini boleh menjejaskan statistik pengukuran (dan seterusnya ukuran kesetiaan keadaan). Walau bagaimanapun, sebahagian daripadanya tidak menjejaskan banyak boleh amat GHZ piawai, termasuk operator seperti dan . Dalam konteks penyediaan keadaan GHZ, ralat putaran-lebih jenis ini yang relevan adalah:
Penyediaan keseluruhan keadaan GHZ -qubit akan kelihatan seperti:
Selepas menyediakan keadaan GHZ, keadaan tersebut secara ideal adalah:
Keadaan ini ialah eigenkeadaan serentak bagi satu set besar operator Pauli, termasuk yang berikut:
dengan eigennilai . Hasilnya, operator dalam bentuk bertindak ke atas keadaan GHZ sebagai pendaraban dengan faktor fasa , yang tidak menjejaskan boleh amat GHZ piawai seperti pariti, kolektif, atau populasi asas-pengkomputeran. Oleh itu, walaupun ralat putaran-lebih adalah koheren dan sistematik, ia secara efektifnya tidak kelihatan kepada pengukuran yang digunakan untuk menilai kesetiaan GHZ. Dalam erti kata ini, litar GHZ menikmati perlindungan koherens secara kebetulan: sesetengah ralat CX koheren berkomut dengan struktur keadaan yang sedang disediakan dan oleh itu tidak menjejaskan prestasi yang diukur.
Twirling Pauli secara asasnya mengubah situasi ini. Twirling tidak mengekalkan ralat putaran-lebih koheren sebagai proses yang deterministik. Sebaliknya, ia menukar saluran ralat koheren kepada saluran Pauli stokastik yang efektif. Hasilnya, saluran ralat kini merangkumi terma seperti , , , dan , yang tidak berkomut dengan penstabil GHZ.
Apabila ralat Pauli stokastik ini berlaku, ia mencipta kegagalan songsang-bit dan songsang-fasa yang sebenar pada qubit individu atau pasangan qubit. Ralat ini mengeluarkan keadaan tersebut daripada subruang penstabil GHZ, mengurangkan interferens antara dan , dan secara langsung menurunkan kesetiaan GHZ serta isyarat pariti multi-qubit. Dalam erti kata lain, twirling Pauli menghapuskan struktur koheren ralat tetapi turut menghapuskan pembatalan berasaskan simetri yang sebelum ini menjadikan ralat itu tidak berbahaya. Hasilnya ialah keadaan GHZ yang sedikit lebih teruk — bukan kerana twirling menambah hingar, tetapi kerana ia menukar ralat koheren yang kebanyakannya tidak berbahaya kepada ralat stokastik yang benar-benar merosakkan.
Contoh ini menyerlahkan satu pengajaran penting: twirling Pauli bukan strategi penambahbaikan universal. Ia paling bermanfaat apabila ralat koheren terkumpul sepanjang litar dengan cara yang merosakkan kesetiaan yang diperlukan. Dalam litar yang amat simetri seperti penyediaan keadaan GHZ, sesetengah ralat koheren secara semula jadi selari dengan penstabil keadaan tersebut, dan merawak-acakkan ralat itu secara sengaja boleh menghapuskan perlindungan kebetulan ini.
Gabungkan kaedah
Pada dasarnya, seseorang boleh menggabungkan twirling Pauli dengan penyahgandingan dinamik. Kami tidak menggunakan DD dalam kes ini atas dua sebab: pertama, tidak sepatutnya terdapat tempoh diam yang amat panjang dalam litar ini. Kedua, keadaan GHZ tidak menyimpan kebanyakan maklumatnya dalam koheren fasa satu-qubit, terutamanya jika kita hanya memerhatikan kesetiaan dari segi kiraan rentetan-bit, dan bukan dari segi fasa antara rentetan-bit yang dikehendaki dan .
Jika kita hendak memanjangkan penyediaan GHZ kepada 100+ qubit, maka kelewatan dalam mengukur qubit pertama/awal mungkin memang cukup panjang sehingga DD boleh berguna. Ini terutamanya benar apabila kita mengambil kira kedalaman litar yang telah ditranspilasi.
Pauli twirling dalam struktur gema RTZ
Contoh terakhir kami bagi twirling Pauli dalam konteks mengurangkan pengumpulan ralat koheren menggunakan litar dengan lapisan gabungan X-CZ-X-CZ. Ini ialah jujukan get yang terkenal yang sering digunakan untuk membatalkan terma ralat ZZ koheren dalam get dua-qubit, walaupun tanpa twirling. Tetapi dengan twirling, kita boleh mengurangkan atau merawak-acakkan bentuk lain pengumpulan ralat koheren.
Kita akan mulakan dengan mentakrifkan fungsi untuk membina litar seakan-RTZ dengan bilangan lapisan yang berbeza-beza.
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 membina litar dengan lapisan yang semakin bertambah, sehingga ke kedalaman dua-qubit yang telah ditranspilasi yang munasabah.
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 hujung yang tinggi, sesetengah daripadanya agak dalam. Mari kita gunakan AerSimulator tanpa model hingar untuk mendapatkan keadaan ideal pada penghujung setiap litar ini. Kita kemudiannya boleh menanda aras keputusan kita daripada komputer kuantum sebenar menggunakan kesetiaan Hellinger.
from qiskit_aer import AerSimulator
sim = AerSimulator()
ideal_results = sim.run(circuits, shots=8192).result()
ideal_counts = ideal_results.get_counts()
Sekarang kita mentakrifkan 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 kiraan daripada setiap larian pada komputer kuantum sebenar.
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 kesetiaan Hellinger dengan membandingkan setiap larian ini dengan keputusan AerSimulator bebas-hingar.
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 menggambarkan keputusan 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 pelajaran ini, kita telah meneliti kes di mana twirling Pauli digunakan untuk mengehadkan pengumpulan ralat koheren, konteks di mana ia mungkin diterangkan sebagai penindasan ralat. Walau bagaimanapun, twirling Pauli sering lebih berguna sebagai alat untuk membentuk semula tingkah laku ralat, menukar ralat koheren kepada bentuk yang lebih boleh diramal dan lebih mudah dimodelkan. Kegunaan ini akan menjadi lebih jelas dalam konteks teknik mitigasi ralat seperti ekstrapolasi hingar-sifar (ZNE), yang bergantung pada penskalaan hingar yang agak boleh diramal. Ini dibincangkan dalam pelajaran seterusnya.