Lumaktaw sa pangunahing nilalaman

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:

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

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.

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

Ang noise o environmental coupling ay maaaring magdulot ng pagbabago sa relative phase sa pagitan ng basis states. Ibig sabihin, ang mga probabilidad ng ∣0⟩|0\rangle at ∣1⟩|1\rangle 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: B⃗=(0,0,B0)\vec{B} = (0,0,B_0).

Tingnan natin ang mangyayari sa state na ∣+⟩|+\rangle habang tumatagal ang oras:

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

Ginamit natin dito ang katotohanang ang magnetic field sa Z direction ay nagdudulot ng precession sa paligid ng Z axis na may frequency ωB\omega_B 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 ∣ψ⟩|\psi\rangle, makikita natin ang sumusunod:

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

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.

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

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 2t02t_0. Isaalang-alang kung ano ang mangyayari kung susundin natin ang prescription na ito:

  • Hayaang magbago ang phase sa loob ng oras na t0t_0

  • Mag-apply ng X gate sa qubit

  • Hayaang mangyari ang parehong environmental coupling sa loob ng karagdagang oras na t0t_0

  • 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:

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

Ngayon, kapag lumipas na ang pangalawang interval na t0t_0, 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 t0t_0, makukuha natin ang state:

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

At sa pag-insert ng t=2t0t = 2t_0, makukuha natin:

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

Ang pag-apply ng huling X gate ay walang epekto sa kasong ito, pero kadalasan ay kailangan ito:

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

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 ∣+⟩|+\rangle. Suriin kung ang parehong mga hakbang sa itaas ay nagbabalik din sa state na ∣+i⟩|+i\rangle pabalik sa initial state nito sa ilalim ng parehong assumptions ng dahan-dahang nagbabagong magnetic field sa z direction.

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

Pagkatapos ng unang time evolution, magkakaroon tayo mismo ng state sa itaas. Sa pag-apply ng unang X gate, makukuha natin:

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

Ngayon, kapag lumipas na ang pangalawang interval na t0t_0, 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 t0t_0, makukuha natin ang state:

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

At sa pag-insert ng t=2t0t = 2t_0, makukuha natin

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

Sa pag-apply ng huling X gate, makukuha natin:

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

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.

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

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 ∣+⟩|+\rangle state, na nananatiling idle nang matagal. Sa kawalan ng errors, ang paghahanda ng multi-qubit state na ∣ψ⟩=∣+⟩⊗N|\psi\rangle = |+\rangle^{\otimes N} 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:

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

kung saan Xj≡III..X...IIX_j \equiv III..X...II na may X operator sa jthj^{th} na posisyon mula sa kanan at ∣Ψ⟩|\Psi\rangle 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 ∣+⟩|+\rangle 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 ∣+⟩|+\rangle state), nagpapatupad ng delay, umiikot gamit ang H gate, at pagkatapos ay nagme-measure. Tandaan na ang Hadamard gate (H) ay nagma-map ng ∣+⟩|+\rangle sa ∣0⟩|0\rangle, kaya ang pag-measure ng ∣0⟩|0\rangle sa Z basis pagkatapos ng Hadamard ay tumutugma sa qubit na naroon sa ∣+⟩|+\rangle 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")

Output of the previous code cell

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

Output of the previous code cell

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

Output of the previous code cell

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

Pauli twirling​

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 Rx(θ0)R_x(\theta_0), na umiikot sa paligid ng X axis nang eksaktong θ0\theta_0. Siyempre, hindi perpekto ang gate implementation, at ang aktwal na rotation ay maaaring θ0+Δθ\theta_0+\Delta\theta para sa isang implementation, at maaari pang mangyari na palaging pareho ang sign ng Δθ\Delta\theta, at posibleng magkatulad ang magnitude sa maraming application ng Rx(θ)R_x(\theta). Kaya, ang paulit-ulit na application ng rotation gates ay maaaring magresulta sa coherent accumulation ng maraming over-rotations (o under-rotations) na ito, Δθ\Delta \theta.

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.

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

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 P∈{X,Y,Z,I}P \in \{X, Y, Z, I\} bago at pagkatapos ng nais na gate operation UU sa paraang P1UP2=UP_1 U P_2 = U. Dito, ang P1P_1 at P2P_2 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 UU at ang layunin ay panatilihing pareho ang logical action ng lahat ng gates sa UU. Minsan tinatawag na "payload" ang UU 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.

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

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 q0q_0 at q1q_1 ay:

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

Gaya ng dati, ginagamit natin ang qubit ordering convention na ∣q1,q0⟩|q_1,q_0\rangle. Ang pag-apply ng CX gate na may q1q_1 bilang target ay nagbubunga ng

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

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.

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

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

Ito ay eksaktong ∣ψfinal⟩|\psi_\text{final}\rangle 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.

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

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.

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

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

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 ZtZ_t bago ang CNOT, at isang ZtZ_t at ZcZ_c 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 ∣+⟩|+\rangle), 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 ⟨X⟩\langle X \rangle 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 ∣0⟩|0\rangle ay tumutugma sa state na naroon sa ∣+⟩|+\rangle kaagad bago ang huling Hadamard gate, at katulad nito para sa ∣1⟩|1\rangle at ∣−⟩|-\rangle. Kaya, ang expectation value natin na ⟨X⟩\langle X \rangle ay puwedeng simpleng kalkulahin mula sa counts ng ∣0⟩|0\rangle minus ang counts ng ∣1⟩|1\rangle.

# Compute <X> from SamplerV2 counts

def x_expect_from_counts(counts: dict[str, int]) -> float:
shots = sum(counts.values())
p0 = counts.get("0", 0) / shots
p1 = counts.get("1", 0) / shots
return p0 - p1 # <X> = P(0) - P(1) after H,measure

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

Output of the previous code cell

Malinaw nating makikita na ang twirled circuit ay nagbibigay ng expectation value na mas malapit sa ideal na ⟨X⟩=1\langle X \rangle = 1. 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")

Output of the previous code cell

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

Output of the previous code cell

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

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

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 ∣0⟩⊗N|0\rangle^{\otimes N} at ang kalahati naman ay nagbabalik ng ∣1⟩⊗N|1\rangle^{\otimes N}. 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()

Output of the previous code cell

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

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

kung saan ang KK ay anumang produkto ng mga Pauli operator, tulad ng XX, ZZ, Z⊗XZ \otimes X, Z⊗NZ^{\otimes N}, 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 ZiZjZ_i Z_j at Z⊗NZ^{\otimes N}. Sa konteksto ng paghahanda ng isang GHZ state, ang mga kaugnay na over-rotation error ng ganitong uri ay:

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

Ang paghahanda ng buong NN-qubit GHZ state ay magiging ganito:

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

Pagkatapos ihanda ang GHZ state, ang state ay dapat sana:

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

Ang state na ito ay sabay-sabay na eigenstate ng isang malaking set ng mga Pauli operator, kasama na ang mga sumusunod:

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

na may eigenvalue na +1+1. Bilang resulta, ang isang operator na may anyong e−iϵZiZje^{-i\epsilon Z_i Z_j} ay kumikilos sa GHZ state bilang pagpaparami sa phase factor na e−iϵe^{-i\epsilon}, na hindi nakaaapekto sa karaniwang mga GHZ observable tulad ng parity, collective X⊗NX^{\otimes N}, o computational‑basis populations. Sa gayon, bagama't ang mga over‑rotation error na e−iϵZjZj+1e^{-i\epsilon Z_j Z_{j+1}} 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 ZjZj+1Z_jZ_{j+1} 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 XX, YY, X⊗ZX \otimes Z, at Y⊗XY \otimes X, 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 ∣0…0⟩|0\dots 0\rangle at ∣1…1⟩|1\dots 1\rangle, 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 ∣0⟩⊗N|0\rangle^{\otimes N} at ∣1⟩⊗N|1\rangle^{\otimes N}.

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

Output of the previous code cell

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

Output of the previous code cell

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.