Pagpigil at pag-tailor sa error
Ang pagpigil sa error ay maaaring tumukoy sa anumang teknik na inaasahan at sinusubukang iwasan ang ilang uri ng noise at error. Mas madali itong ipaliwanag sa pamamagitan ng mga kongkretong halimbawa, pero tandaan na hindi limitado ang mga method na ito sa mga halimbawang ipinapakita dito, at patuloy na may mga bagong method na natutuklasan. Minsan hindi posibleng pigilan ang mga error, pero posibleng apektuhan kung paano sila mag-accumulate. Kung mapapabagal natin ang pag-accumulate ng error, pwede nating tawaging pagpigil sa overall error, pero mas tumpak itong tingnan bilang noise tailoring. Sa lesson na ito, tatalakayin natin ang mga canonical na halimbawa ng error suppression (dynamical decoupling) at ng noise tailoring/shaping (Pauli twirling).
Malapit nang maglabas ng video para sa lesson na ito sa susunod na ilang araw, at ilalagay ito dito.
Dynamical decoupling
Magsimula tayo sa isang napakasimpleng state sa isang qubit, at ipakilala natin ang isang simpleng noise model. Hindi ito sapat para ganap na ipaliwanag ang dynamical decoupling, pero nagbibigay ito ng malinaw na halimbawa. Ipagpalagay natin na may qubit tayong naka-prepare sa superposition state gamit ang isang Hadamard gate:
Ang state na ito ay naka-visualize sa Bloch sphere gaya ng ipinapakita sa ibaba sa kaliwa. Sa ideal na kaso na walang noise, mananatili ang qubit na ito sa state na ito hanggang sa susunod na operation. Pero alam natin mula sa naunang lesson na hindi ito ang ating napapansin. Ang noise ay nagpapababa sa kalidad ng quantum information.

Ang noise o environmental coupling ay maaaring magdulot ng pagbabago sa relative phase sa pagitan ng basis states. Ibig sabihin, ang mga probabilidad ng at ay hindi nagbabago, dahil hindi nagbabago ang absolute values ng kanilang mga coefficient. Sa halip, nagbabago ang mga phase ng amplitudes, na nagbabago sa kanilang real at imaginary components. Para gawing mas kongkreto ang talakayang ito, tingnan natin ang isang uri ng interaction na maaaring magdulot nito: ang pag-couple sa isang magnetic field na naka-orient sa Z direction: .
Tingnan natin ang mangyayari sa state na habang tumatagal ang oras:
Ginamit natin dito ang katotohanang ang magnetic field sa Z direction ay nagdudulot ng precession sa paligid ng Z axis na may frequency na nakadepende sa effective magnetic moment at lakas ng magnetic field. Hindi gaanong importante ang mga detalye kumpara sa katotohanang ang interaction na ito ay nagdudulot ng evolution sa paligid ng Z axis, na nagreresulta sa magkasalungat na phase accumulation para sa dalawang computational basis states. Sa pag-apply ng operator na ito sa bawat term sa , makikita natin ang sumusunod:
Ang time-dependent phase na ito ay tumutugma sa precession sa paligid ng Z axis sa Bloch sphere picture. Ipinapakita ito sa kanang bahagi ng figure sa itaas.
Kung alam nating nangyayari ang interaction na ito sa kontroladong paraan, mahuhulaan natin ito. Para i-model ang ganitong uri ng noise, sa halip ay isinasaalang-alang natin ang isang distribution ng posibleng magnetic field strengths, na bawat isa ay may classical probability (ang ibang pinagmumulan ng noise ay kailangang i-model nang iba). Kailangan nating isaalang-alang kung ano ang mangyayari kapag may non-zero classical probability na hindi umikot ang state, at may probability din na umikot ito nang kaunti, o kahit malaki. Ang distribution na ito ng posibleng rotations ang dahilan kung bakit kumakalat ang state sa Bloch sphere picture sa ibaba. Kung mas marami ang ganitong dephasing noise, mas hindi magiging well-defined ang phase. Sa limitasyon ng malakas na dephasing, ganap na madi-dephase ang qubit, na tumutugma sa pagkawala ng quantum coherence na naka-store sa state.

Siyempre, sa isang eksperimento ay wala tayong ideya kung anong random coupling ang mangyayari. Ano ang magagawa natin dito?
Ipagpalagay na ang environmental coupling ay nananatiling halos constant sa loob ng time interval na . Isaalang-alang kung ano ang mangyayari kung susundin natin ang prescription na ito:
-
Hayaang magbago ang phase sa loob ng oras na
-
Mag-apply ng X gate sa qubit
-
Hayaang mangyari ang parehong environmental coupling sa loob ng karagdagang oras na
-
Mag-apply ng pangalawang X gate
Pagkatapos ng unang time evolution, magkakaroon tayo mismo ng state sa itaas. Sa pag-apply ng unang X gate, makukuha natin:
Ngayon, kapag lumipas na ang pangalawang interval na , mangyayari ang parehong interaction. Pero ngayon, naka-associate ang amplitudes sa kabaligtaran na Z eigenstates, ibig sabihin nagbago ang sign ng rotation sa paligid ng Z axis. Pagkatapos hayaang mag-evolve ang system sa loob ng isa pang , makukuha natin ang state:
At sa pag-insert ng , makukuha natin:
Ang pag-apply ng huling X gate ay walang epekto sa kasong ito, pero kadalasan ay kailangan ito:
Nabawi natin ang orihinal na quantum state, kasama ang relative phase nito. Ang proseso na ito ay isang lalong simpleng halimbawa ng dynamical decoupling.
Sa mas pangkalahatang paraan, ang dynamical decoupling (DD) ay kinabibilangan ng pag-insert ng ilang single-qubit gates para bawasan ang epekto ng mga interaction sa mga system sa labas ng qubit (pag-decouple nito mula sa environment). Ang kumpletong refocusing ng lahat ng posibleng phase histories pabalik sa orihinal na state, gaya ng ipinapakita sa itaas, ay medyo idealized, pero posible pa rin itong scenario. Pag-usapan natin kung kailan kapaki-pakinabang ang DD at anong mga caveat ang mayroon.
Suriin ang iyong pagkaintindi
Sa teksto sa itaas, dinaan natin ang epekto ng XX DD sequence sa initial state na . Suriin kung ang parehong mga hakbang sa itaas ay nagbabalik din sa state na pabalik sa initial state nito sa ilalim ng parehong assumptions ng dahan-dahang nagbabagong magnetic field sa z direction.
Sagot
Pagkatapos ng unang time evolution, magkakaroon tayo mismo ng state sa itaas. Sa pag-apply ng unang X gate, makukuha natin:
Ngayon, kapag lumipas na ang pangalawang interval na , mangyayari ang parehong interaction. Pero ngayon, naka-associate ang amplitudes sa kabaligtaran na Z eigenstates, ibig sabihin nagbago ang sign ng rotation sa paligid ng Z axis. Pagkatapos hayaang mag-evolve ang system sa loob ng isa pang , makukuha natin ang state:
At sa pag-insert ng , makukuha natin
Sa pag-apply ng huling X gate, makukuha natin:
Kailan Gagamitin ang DD
Ang una at pinaka-obvious na caveat ay ipinagpalagay natin ang isang purong dephasing channel sa ating treatment. Ang mga totoong-mundong interaction ay karaniwang gumagawa ng halo ng dephasing at ibang error mechanisms. Sa treatment sa itaas, pinili nating mag-apply ng X gates nang partikular. Tinatawag itong XX sequence sa dynamical decoupling. Ang partikular na sequence na ito ay angkop para sa purong dephasing errors. Pero may ibang mga sequence na maaaring mas malawak ang applicability, tulad ng XY4 (ipinapakita sa circuit diagram sa ibaba) at ang mas complex na XY8.
Ang isa pang caveat ay ang dynamical decoupling ay nagdadagdag ng single-qubit gates, na maaaring magdagdag ng single-qubit errors dahil sa gate imperfections o kahit crosstalk. Ang mga single-qubit error rate na ito ay karaniwang mas mababa kaysa sa multi-qubit gate errors, kaya karaniwan itong hindi malaking alalahanin, pero dapat itong tandaan kung maraming qubits ang gumagamit ng DD nang maraming beses sa buong circuit mo.
Kapaki-pakinabang ang DD kapag may oras ang external coupling para maapektuhan ang state ng qubit. Dahil ang mga qubit ay maingat na naka-isolate at dapat mahina ang external couplings, ang ganitong uri ng noise ay pinaka-halata kapag ang isang qubit ay nagiging idle nang mahabang panahon. Para sa maiikling idle times, ang epekto ng DD ay maaaring magdagdag ng single-qubit gate errors habang napakaliit lang ng napipigilang noise; maaari pa ngang bumaba ang fidelity ng iyong circuit.
Susing dapat tandaan: Gamitin ang dynamical decoupling kapag nananatiling idle ang mga qubit nang sapat na katagalan, at bigyang-pansin ang uri ng DD sequence na ginagamit.
Dynamical decoupling gamit ang Qiskit
Tuklasin natin ang paggamit ng DD sa pamamagitan ng pagsusuri sa kaso ng ilang qubits na naka-prepare sa state, na nananatiling idle nang matagal. Sa kawalan ng errors, ang paghahanda ng multi-qubit state na at pagkatapos ay pag-apply ng Hadamard gate bago mag-measure (sa gayon ay nagme-measure sa X basis) ay dapat laging magbigay ng 0. Sa presensya ng noise, ang measurement outcome ay magiging 0 lamang sa isang bahagi ng oras sa halip na sa 100% probability. Kinakalkula natin ang average X expectation value sa maraming qubits. Ibig sabihin, interesado tayo sa sumusunod:
kung saan na may X operator sa na posisyon mula sa kanan at ay ang state ng buong system.
May sinasadyang delays ang circuit na ito, na medyo contrived para sa isang benchmark. Pero, napaka-karaniwan para sa totoong mga circuit na magkaroon ng mga qubits na naiidle sa bahagi ng execution. Maaari mong isipin ito bilang isang pinasimpleng modelo ng isang mas complex na circuit kung saan may mga qubits na nananatiling idle sa bahagi ng computation. Ang ganitong uri ng benchmarking na base sa evolution ng states ay kadalasang tinatawag na Ramsey benchmarking.
Magsimula tayo sa pag-load ng mga kailangang packages at pag-configure ng service.
# 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
Ngayon ay mag-define tayo ng ilang helper functions. Una, gusto nating tugunan ang punto tungkol sa mahahabang idle times. Ano ba talaga ang ibig sabihin ng "mahaba" sa context na ito? Puwede nating i-report na lang ang idle time sa microseconds. Pero, kapaki-pakinabang na malaman kung ilang two-qubit gates ang maaaring maisagawa sa parehong interval. Ginagawa nitong mas direktang indikasyon ang idle times sa circuit depth na maaaring naisagawa sa parehong period. Ang unang helper function ay kumukuha ng two-qubit gate na native sa backend at tinutukoy ang duration ng gate na iyon.
Ang pangalawang function ay simpleng gumagawa ng Ramsey circuit (isa na may mga qubits sa state), nagpapatupad ng delay, umiikot gamit ang H gate, at pagkatapos ay nagme-measure. Tandaan na ang Hadamard gate (H) ay nagma-map ng sa , kaya ang pag-measure ng sa Z basis pagkatapos ng Hadamard ay tumutugma sa qubit na naroon sa kaagad bago ang Hadamard.
Ang huling function natin ay nagko-convert ng raw counts ng 0 at 1 measurement outcomes sa isang expectation value ng 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
Ngayon ay itakda natin ang mga detalye ng ating test, kasama ang bilang ng qubits at ang gate sequence na gagamitin sa DD (sa kasong ito XX). Pansinin lalo na na itinakda natin ang optimization level sa zero. Sa praktika, madalas kang pipili ng mas mataas na optimization level; dito, ginagamit natin ang level 0 para matiyak na nananatiling visible ang mga epekto ng errors na target ng DD. Sa wakas, tinutukoy natin ang characteristic time para sa two-qubit gates sa backend na ito at nagpi-print ng ilang relevant na times.
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
Ngayon ay bubuuin natin ang ating mga circuits at i-transpile ang mga ito para sa napiling backend natin.
# --------- 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']
Dapat nating i-visualize ang hindi bababa sa isang circuit para matiyak na na-code natin ang circuit natin nang may nais na states at delays. Maaaring mas madali na i-visualize ang pre-transpiled circuit na qc, o maaari mong suriin ang na-transpile na qc_isa.
qc.draw("mpl")

Isagawa
Handa na tayo sa wakas na mag-execute sa hardware. Ginagamit natin ang Sampler para makakuha ng maraming measurements ng bawat qubit, at gagamitin natin ito nang dalawang beses: isang beses na naka-off ang DD, at isang beses na naka-on ang DD gamit ang XX gate sequence.
# --------- 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)
Puwede nating i-extract ang counts mula sa iba't ibang circuits.
# --------- 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)
Panghuling Pagproseso
Meron na tayong counts mula sa measurement, pero gusto nating gawin itong expectation value ng X at pagkatapos ay i-average ang mga expectation values na iyon sa lahat ng qubits na ginamit natin, para malaman ang preservation ng phase information. Para doon, ginagamit natin ang counts_to_x_expectations function na na-define natin kanina.
# --------- 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]
Sa wakas, i-plot natin ang mga resulta natin.
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()

Gaya ng makikita mo, kapag walang explicit na delay, malapit sa 1 talaga ang mga expectation values, na siyang inaasahan natin kung napreserba ang lahat ng phase information. Kung nawala ang lahat ng phase information, walang preference para sa huling rotation na magbunga ng kaysa , at malalapit sa zero ang average expectation value. Sa data, nakikita natin na habang tumataas ang delay times, bumababa ang average expectation value ng X, na nagsisimulang lumapit sa zero. Pansinin na epektibo talaga ang DD dito; ang mga expectation values na may DD ay karaniwang mas mahusay ng higit sa 20% (mas malapit sa 1) kaysa sa mga values na walang DD. Pero pansinin din na ang unang na-delay na data point ay tumutugma sa isang delay na halos katumbas ng 100 two-qubit gate operations. Pinapatibay nito ang punto na pinaka-kapaki-pakinabang ang DD kapag ang mga qubits ay nananatiling idle nang medyo mahabang panahon.
Suriin ang iyong pagkaintindi
Kung mag-apply tayo ng DD gamit ang XY4 sa parehong circuit gaya ng dati, inaasahan mo bang magbibigay ito ng resulta na mas mahusay, mas masama, o halos pareho, kumpara sa DD gamit ang XX? Ipaliwanag.
Sagot
Halos pareho, marahil bahagyang mas masama. Ang circuit na ginamit natin ay may states na na-rotate papunta sa XY plane. Ang state na ito ay nag-iimbak ng information pangunahin sa phase nito, kaya't sensitibo ito lalo na sa dephasing errors kaysa sa T1 relaxation. Maaaring makatulong ang XY4 sa mas malawak na variety ng errors, pero na-optimize na ang XX para tumulong sa circuit na ginagamit natin. Maaaring kasing husay ng XY4, pero walang idadagdag na malaki, o dahil sa mas maraming gates ang XY4, maaaring magdulot ito ng karagdagang gate errors na gumagawa sa resulta na bahagyang mas masama.
Ang huling resulta ay gumamit ng pinakasimpleng DD gate sequence na XX. Tingnan natin kung paano ipatupad ang mas complex na sequence, ang XY4. Magde-define tayo ng Sampler sa susunod na seksyon.
# --------- 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()

Dito nakikita natin na ang XY4 ay hindi gaanong naiiba sa XX. Maaaring bahagyang mas masama ito dahil sa karagdagang gates sa XY4 sequence, pero mas importante, naipaliwanag na natin kung bakit magkakaroon ang XX ng nais na epekto sa pagpreserba ng phase partikular para sa state gaya ng . Walang dahilan para isipin na para sa ganitong uri ng initial state, may ibang sequence na magpapabuti ng resulta.
Suriin ang iyong pagkaintindi
I-verify na ang XY4 sequence ay iniiwan ang state na hindi nagbabago hanggang sa global phase.
Sagot
Pauli twirling
Dapat nating simulan sa pagbanggit na ang Pauli twirling ay kadalasang ginagamit hindi bilang error-suppression technique, kundi bilang error-shaping technique: ginagawa nitong magkaiba ang kilos ng noise/errors, minsan mas predictable, para paganahin ang ibang mga method. Bagama't hindi pinipigilan ng Pauli twirling ang mga error, maaari nitong pigilan ang coherent na pag-accumulate ng mga ito.
Sa isang quantum circuit, maraming iba't ibang pinagmumulan ng error ang nagsasama-sama. Ang mga error ay maaaring magsama-sama sa iba't ibang paraan, lalo na sa paraang coherent at incoherent. Ang coherent accumulation ng errors ay nangangahulugang ang noise o imperpektong implementasyon ay palaging tumatawid sa parehong direksyon ng error sa maraming layers at gates. Isang halimbawa nito ay ang coherent over-rotation kapag nag-a-apply ng rotation gate.
Isaalang-alang ang isang ideal na rotation gate, tulad ng , na umiikot sa paligid ng X axis nang eksaktong . Siyempre, hindi perpekto ang gate implementation, at ang aktwal na rotation ay maaaring para sa isang implementation, at maaari pang mangyari na palaging pareho ang sign ng , at posibleng magkatulad ang magnitude sa maraming application ng . Kaya, ang paulit-ulit na application ng rotation gates ay maaaring magresulta sa coherent accumulation ng maraming over-rotations (o under-rotations) na ito, .
Ang incoherent error accumulation ay eksaktong kabaligtaran: mga error sa random na direksyon na may random na sign, kaya't ang mga error sa iba't ibang layers ay hindi laging nag-i-interfere nang additive, kundi minsan nagkakansela o nagsasama sa quadrature. Malinaw na mas mabagal mag-accumulate ang incoherent errors sa usaping overall effect sa state ng qubit. Isang toy diagram nito ang ipinapakita sa figure sa ibaba. Ito ay isang simplification. Ang totoong quantum errors ay hindi limitado sa isang two-dimensional Cartesian space; hindi lahat ng error contributions ay may parehong magnitude, at may mas maraming complexities. Pero kapaki-pakinabang ang intuition ng ganitong larawan: ang coherent errors ay mas mabilis mag-accumulate kaysa sa incoherent ones.

Madalas makakakuha ng mas mataas na fidelity na resulta sa pamamagitan ng pagpapalit ng coherent error accumulation papunta sa incoherent error accumulation. Isang pangunahing paraan para magawa ito ay tinatawag na Pauli twirling.
Ang Pauli twirling ay tumutukoy sa pagdaragdag ng mga kombinasyon ng Pauli gates bago at pagkatapos ng nais na gate operation sa paraang . Dito, ang at ay hindi single Paulis, kundi mga koleksyon ng Pauli operators na kadalasang kumikilos sa maraming qubits. Minsan ay makikita mong nasasabi na ang aksyon ng extra Pauli gates ay "katumbas ng identity". Pero hindi ito tumpak at maaaring nakakalito. Ang Pauli gates ay pinaghihiwalay ng at ang layunin ay panatilihing pareho ang logical action ng lahat ng gates sa . Minsan tinatawag na "payload" ang para ihiwalay ang intended operation na ito mula sa mga gates na idinagdag para sa suppression. Ipinapakita sa ibaba ang ilang halimbawa ng Pauli twirling sa paligid ng CNOT gate.
Dumaan tayo sa isang halimbawa lang para i-verify na ang twirling ay iniiwan ang logical effect ng payload na hindi nagbabago. Nang walang pagkawala ng generality, sabihin nating ang dalawang qubit states na kinabibilangan ng at ay:
Gaya ng dati, ginagamit natin ang qubit ordering convention na . Ang pag-apply ng CX gate na may bilang target ay nagbubunga ng
Ngayon isaalang-alang natin ang ikatlong circuit na ipinakita, gamit ang X gates para sa Pauli twirling. Puwede lang nating i-multiply ang mga matrices at i-verify na nagbubunga sila ng CNOT matrix. Alternatibo, puwede nating subaybayan ang operation sa isang arbitrary quantum state sa buong circuit, gaya ng ginagawa natin sa ibaba. Ang mga states sa iba't ibang punto sa circuit ay na-label na a-d.
Ito ay eksaktong na nakuha natin dati na walang twirling. Sa katunayan, ang twirled gate sequence na ito ay iniiwan ang logical action ng payload na hindi nagbabago. Gayunpaman, kung may iba't ibang valid twirling sequences na pinipili nang random mula layer papunta sa layer, ang coherent error accumulation ay maaaring i-convert papunta sa effectively stochastic (incoherent) error accumulation. Para linawin, hindi ka pumipili ng iisang twirling pattern at ginagamit ito sa buong circuit. Sa halip, iba't ibang valid twirling sequences ang pinipili para sa iba't ibang layers. Ang isang halimbawa na may maraming entangling layers ay maaaring ganito ang itsura.

Suriin ang iyong pagkaintindi
I-verify na ang Pauli twirling sa ikaapat na panel sa figure sa itaas ay iniiwan din ang logical effect ng CNOT na hindi nagbabago.
Sagot
Sinusundan natin ang halimbawa sa itaas at ipinapakita na ang action sa anumang arbitrary two-qubit state ay katumbas ng isang CNOT operation. Ire-refer natin ang mga naka-label na points sa diagram na ito.
Ito ay katumbas ng action ng isang CNOT na may qubit 0 bilang control at qubit 1 bilang target.
Maaari ka bang bumuo ng Pauli twirling sequence para sa CNOT gate na hindi ipinakita sa itaas?
Sagot
Oo, maraming iba pa. Isang halimbawa ay bago ang CNOT, at isang at pagkatapos ng CNOT.
Kailan Gagamitin ang Pauli Twirling
Gaya ng ipinakita dito, ang Pauli twirling ay inaapply lamang sa multi-qubit gates. Ang pag-apply ng katulad na protocol sa single-qubit gates ay mangangailangan ng ibang logic at karaniwang hindi kapaki-pakinabang sa praktika. Ang Pauli twirling mismo ay gumagamit ng ilang single-qubit gates (ang Pauli gates). Ang mga karagdagang Pauli gates ay malamang na magdulot ng mas maraming error kaysa sa makukuha sa pag-randomize ng anumang coherent error accumulation. Ang error rates na kaugnay sa two-qubit gates ay mas malaki kaysa sa mga kaugnay sa single-qubit gates. Bukod dito, ang ilang single-qubit gates ay non-Clifford, na hindi puwedeng ganap na ma-twirl. Ito ang dahilan kung bakit may mga Pauli twirling options ang Qiskit na awtomatikong nagti-twirl sa paligid ng two-qubit gates, at hindi sa paligid ng single-qubit gates.
Implicit ito sa figure sa itaas: ipinatupad ang Pauli twirling sa paligid ng CX gates, pero hindi sa paligid ng Hadamard gate.
Tingnan natin ang dalawang halimbawa ng Pauli twirling sa aksyon.
Pauli twirling para pigilan ang coherent accumulation
Para obserbahan kung paano mape-convert ng Pauli twirling ang coherent error accumulation papunta sa mas mabagal na incoherent accumulation, gusto natin ng isang circuit at observable na gagana bilang isang coherent-error stress test. Ang tanging layunin ay gawing visible ang coherently accumulating two-qubit errors, at pagkatapos ay ipakita kung paano ico-convert ng Pauli twirling ang coherent buildup na iyon papunta sa stochastic decay.
Ang CNOT (o CZ) gates ay isang karaniwang pinagmumulan ng coherent errors. Ang pinakasimpleng eksperimento na magagawa natin sa kasong ito ay ang pag-initialize ng state (sabihin ), pag-apply ng layers ng paired CNOT gates (gamit ang katotohanang ang dalawang CNOTs ay nagbubunga ng identity), at pagsuri kung paano nag-a-accumulate ang errors habang tumataas ang bilang ng layers, kapwa may at walang Pauli twirling.
Ang observable na interesado tayo ay sa isang qubit, na ipino-plot natin bilang function ng bilang ng repetitions ng CNOT pairs.
# --- 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
Dahil na-rotate natin ang basis natin bago ang measurements, ang measurement ng ay tumutugma sa state na naroon sa kaagad bago ang huling Hadamard gate, at katulad nito para sa at . Kaya, ang expectation value natin na ay puwedeng simpleng kalkulahin mula sa counts ng minus ang counts ng .
# 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
Pumipili tayo ng makatwirang bilang ng CNOT layers kung saan papayagan nating mag-accumulate ang error, buuin ang ating mga circuits, at pagkatapos ay i-transpile ang mga ito.
# 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]
Tandaan na ang bawat layer ay binubuo ng higit sa isang two-qubit gate. I-monitor ang transpiled, two-qubit depth gamit ang function sa ibaba.
# 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))
Ngayon ay patakbuhin natin ang mga jobs.
# 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
Sa wakas, i-visualize natin ang mga resultang ito.
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()

Malinaw nating makikita na ang twirled circuit ay nagbibigay ng expectation value na mas malapit sa ideal na . Naglingkod ang halimbawang ito para sa layunin nito, pero lumipat tayo sa mas kapaki-pakinabang na bagay: ang twirling sa produksyon ng highly entangled states.
Pauli twirling sa GHZ state preparation
Ipinakita ng naunang halimbawa ang isang matibay na kaso para sa Pauli twirling sa pagsira sa coherent accumulation ng errors na kaugnay ng CNOT gates. Ang produksyon ng GHZ state ay gumagamit ng maraming CNOT gates para makabuo ng highly entangled states na kapaki-pakinabang para sa maraming quantum computing applications. Tuklasin natin kung paano tumutulong ang Pauli twirling sa context na ito, gamit ang GHZ states na tumataas ang laki.
# 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")

Ngayon ay bubuuin natin ang ating mga circuits at i-transpile ang mga ito. Sa kasong ito, wala tayong artipisyal na paulit-ulit na gates na nagre-reduce sa identity. Kaya puwede nating hayaan ang pass manager na mag-optimize nang mas marami para sa atin. Itatakda natin ito sa level three.
# 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)
Makikita natin na ang optimizer ay nag-map ng abstract circuit natin sa qubits 123, 124, 136, 142, at 143.
circuits[0].draw("mpl")

Para maintindihan kung bakit, tingnan natin ang isang mapa ng layout ng backend natin (sa larawang ito, ibm_fez, pero puwede kang gumawa ng katulad na analysis sa anumang backend).

Makikita natin na ang mga qubits ay pinili sa isang chain para maminimize ang swap gates at sa gayon ay ang circuit depth. Bukod dito, ang bawat CZ gate na ipinatupad sa circuit ay sa pagitan ng magkatabing qubits. Sa wakas, lahat ng limang qubits ay may medyo mababang error rates, kasama ang readout-assignment error rates. Puwede mong suriin ang mga error rates na ito sa anumang backend sa Compute resources page. Ang paghahanap ng ganitong layout ay hindi mahirap para sa isang simpleng linear chain, pero habang lumalala ang complexity ng mga problema, ang optimization ng circuit layout ay nagiging mas mahirap at mas mahalaga.
Ngayon ay i-configure natin ang Sampler primitive natin. Iko-off natin ang ibang suppression/mitigation tools para mag-focus sa 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))
Sa wakas, patakbuhin natin ang mga jobs natin. Puwede mong optional na i-print ang job ID numbers para sa retrieval sa hinaharap.
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
Ina-extract natin ang counts ng bawat computational basis state na na-measure para sa lahat ng circuits, kapwa may at walang 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}
Alam natin na ang ideal na distribution ng isang GHZ state ay ang isa kung saan kalahati ng shots ay nagbabalik ng at ang kalahati naman ay nagbabalik ng . Buuin ito para sa comparison.
ideal_dist = []
for n in range(nmin, nmax):
ideal_dist.append({"0" * n: int(shots / 2), "1" * n: int(shots / 2)})
Gagamitin na natin ang Hellinger fidelity bilang sukatan ng kalidad ng huling state natin.
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])
)
Sa wakas, i-plot natin ang mga resulta natin.
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()

Ang mga resulta gamit ang Pauli twirling ay hindi mas mahusay (at kahit bahagyang mas masama) kaysa sa walang twirling. Ano ang nangyari?
Dalawang bagay ang nangyari. Una, ang Pauli twirling ay hindi binabawasan ang kabuuang dami ng noise — sa halip, binabago nito ang shape ng coherent, systematic errors papunta sa stochastic Pauli-type errors, para maging predictable at modelable ang paglaki ng error. Wala talagang pangako ng pagbawas ng error, maliban sa mga espesyal na kaso.
Pangalawa, sa mga GHZ circuits, ang ilang coherent errors ay maaaring bahagyang magkansela o kumilos tulad ng benign phase shifts dahil sa symmetry ng GHZ construction; inaalis ng twirling ang aksidenteng proteksyon na ito at pinapalitan ito ng uncorrelated stochastic Pauli noise, kaya bahagyang lumalala ang GHZ fidelity sa ilalim ng twirling.
Ang ikalawang claim na ito ay nangangailangan ng ilang paliwanag. Ang claim ay hindi na ang mga GHZ circuits ay protektado mula sa lahat ng uri ng coherent error accumulation, ilan lamang — at sa mga kasong iyon, sinisira ng twirling ang proteksyon. Partikular, isaalang-alang natin ang coherent over-rotation na kaugnay ng CX gates. Tawagin natin ang totoong CX gate na may over-rotation bilang :
kung saan ang ay anumang produkto ng mga Pauli operator, tulad ng , , , , atbp. Para sa mga pangkalahatang state, maaaring apektuhan ng alinman sa mga over-rotation na ito ang measurement statistics (at sa gayon ang mga sukatan ng state fidelity). Ang isang subset ng mga ito, gayunpaman, ay hindi nagbabago sa maraming karaniwang GHZ observable, kabilang ang mga operator tulad ng at . Sa konteksto ng paghahanda ng isang GHZ state, ang mga kaugnay na over-rotation error ng ganitong uri ay:
Ang paghahanda ng buong -qubit GHZ state ay magiging ganito:
Pagkatapos ihanda ang GHZ state, ang state ay dapat sana:
Ang state na ito ay sabay-sabay na eigenstate ng isang malaking set ng mga Pauli operator, kasama na ang mga sumusunod:
na may eigenvalue na . Bilang resulta, ang isang operator na may anyong ay kumikilos sa GHZ state bilang pagpaparami sa phase factor na , na hindi nakaaapekto sa karaniwang mga GHZ observable tulad ng parity, collective , o computational‑basis populations. Sa gayon, bagama't ang mga over‑rotation error na ay coherent at systematic, hindi ito nakikita ng mga measurement na ginagamit upang sukatin ang GHZ fidelity. Sa ganitong diwa, natatangkilik ng GHZ circuit ang isang aksidenteng coherence protection: ang ilang coherent CX error ay commute sa istruktura ng state na inihahanda kaya hindi nito binabawasan ang nasusukat na performance.
Talagang binabago ng Pauli twirling ang sitwasyong ito. Ang twirling ay hindi nagpapanatili sa coherent over‑rotation error bilang deterministic na na proseso. Sa halip, kina-convert nito ang coherent error channel patungo sa isang epektibong stochastic Pauli channel. Bilang resulta, kasama na ngayon sa error channel ang mga termino tulad ng , , , at , na hindi commute sa mga GHZ stabilizer.
Kapag nangyari ang mga stochastic Pauli error na ito, lumilikha sila ng aktwal na bit‑flip at phase‑flip fault sa indibidwal na qubit o pares ng qubit. Inilalabas ng mga error na ito ang state mula sa GHZ stabilizer subspace, binabawasan ang interference sa pagitan ng at , at direktang binababa ang GHZ fidelity at ang multi‑qubit parity signals. Sa madaling salita, inaalis ng Pauli twirling ang coherent na istruktura ng error ngunit inaalis din nito ang symmetry‑based na cancellation na dati'y nagpapagaan sa mga error na iyon. Ang resulta ay bahagyang mas masamang GHZ state — hindi dahil nagdaragdag ng noise ang twirling, kundi dahil kina-convert nito ang isang halos hindi nakakapinsalang coherent error patungo sa tunay na nakakasirang stochastic error.
Itinatampok ng halimbawang ito ang isang mahalagang aral: ang Pauli twirling ay hindi isang unibersal na estratehiya sa pagpapahusay. Pinaka-kapaki-pakinabang ito kapag ang mga coherent error ay naiipon sa buong circuit sa paraang mapanira sa kinakailangang fidelity. Sa mga lubhang simetriko na circuit tulad ng paghahanda ng GHZ state, ang ilang coherent error ay natural na naka-align sa mga stabilizer ng state, at ang sadyang pag-randomize sa mga ito ay maaaring mag-alis sa aksidenteng proteksyon na ito.
Pagsamahin ang mga pamamaraan
Sa prinsipyo, maaaring pagsamahin ang Pauli twirling at dynamical decoupling. Hindi namin ginamit ang DD sa kasong ito dahil sa dalawang dahilan: una, hindi dapat magkaroon ng napakahabang idle period sa circuit na ito. Pangalawa, hindi iniimbak ng mga GHZ state ang karamihan ng kanilang impormasyon sa single-qubit phase coherence, lalo na kung ang fidelity ay pinagmamasdan lamang batay sa mga bitstring count, at hindi batay sa phase sa pagitan ng nais na mga bitstring na at .
Kung palalawigin natin ang paghahanda ng GHZ sa 100+ qubit, maaaring maging sapat na mahaba ang mga delay sa pagsukat sa una/unang mga qubit upang maging kapaki-pakinabang ang DD. Totoo ito lalo na kapag isinaalang-alang natin ang transpiled depth ng circuit.
Pauli twirling sa RTZ echo structure
Ang huli naming halimbawa ng Pauli twirling sa konteksto ng pagbawas ng akumulasyon ng coherent error ay gumagamit ng circuit na may mga layer ng X-CZ-X-CZ na kombinasyon. Ito ay isang kilalang gate sequence na madalas gamitin upang kanselahin ang coherent ZZ error term sa two-qubit gates, kahit walang twirling. Ngunit sa twirling, mababawasan o marandomize natin ang ibang anyo ng akumulasyon ng coherent error.
Magsisimula tayo sa pagdedefine ng isang function upang bumuo ng mga RTZ-like circuit na may iba't ibang bilang ng layer.
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
Ngayon ay bumubuo tayo ng mga circuit na may dumaraming layer, hanggang sa makatwirang kabuuang transpiled two-qubit depth.
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")

Sa mataas na dulo, medyo malalim ang ilan sa mga ito. Gamitin natin ang AerSimulator nang walang noise model upang makuha ang ideal na state sa dulo ng bawat isa sa mga circuit na ito. Pagkatapos ay maaari nating i-benchmark ang ating mga resulta mula sa tunay na quantum computer gamit ang Hellinger fidelity.
from qiskit_aer import AerSimulator
sim = AerSimulator()
ideal_results = sim.run(circuits, shots=8192).result()
ideal_counts = ideal_results.get_counts()
Ngayon ay magdedefine tayo ng isang SamplerV2 na may twirling, at isa na walang 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
Ngayon ay pinapatakbo natin ang ating mga job.
# 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()
Kinukuha natin ang mga count mula sa bawat run sa tunay na quantum computer.
counts_no = [r.data.c.get_counts() for r in res_no]
counts_tw = [r.data.c.get_counts() for r in res_tw]
Ngayon ay hahanapin natin ang Hellinger fidelity sa pamamagitan ng paghahambing ng bawat run na ito sa mga resulta ng noise-free AerSimulator.
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))]
Ngayon ay bibisualisahin natin ang ating mga resulta.
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()

Sa buong araling ito, sinuri natin ang mga kaso kung saan ginagamit ang Pauli twirling upang limitahan ang akumulasyon ng coherent error, isang konteksto kung saan maaari itong ilarawan bilang error suppression. Gayunpaman, ang Pauli twirling ay mas kapaki-pakinabang kadalasan bilang isang kasangkapan para sa muling paghuhubog ng ugali ng error, kina-convert ang mga coherent error patungo sa isang anyong mas mahuhulaan at mas madaling i-model. Ang kapaki-pakinabang na ito ay magiging mas malinaw sa konteksto ng mga teknik sa error-mitigation tulad ng zero-noise extrapolation (ZNE), na umaasa sa makatwirang mahuhulaang noise scaling. Tinatalakay ito sa susunod na aralin.