Lewati ke konten utama
Proyek ini masih dalam tahap beta. Menemukan bug? Buat isu GitHub. Punya pertanyaan atau ide? Tanya di Discussions — kami sangat menghargai masukan kamu!
Bagaimana kualitas terjemahan di halaman ini?

Resonansi magnetik nuklir dengan Qiskit

Untuk modul Qiskit in Classrooms ini, siswa harus punya lingkungan Python yang berfungsi dengan paket-paket berikut terpasang:

  • qiskit v2.1 atau lebih baru
  • qiskit-ibm-runtime v0.40.1 atau lebih baru
  • qiskit-aer v0.17 atau lebih baru
  • scipy dan matplotlib (untuk fitting dan plotting)
  • pylatexenc (untuk merender diagram circuit)

Untuk menyiapkan dan memasang paket-paket di atas, lihat panduan Install Qiskit. Untuk menjalankan job di komputer kuantum sungguhan, siswa harus membuat akun IBM Quantum® dengan mengikuti langkah-langkah di panduan Set up your IBM Cloud® account.

Modul ini sudah diuji dan memakai sekitar 5 menit waktu QPU. Ini hanya perkiraan, dan penggunaan sebenarnya mungkin berbeda.

Pendahuluan​

Ketika seorang kimiawan menjalankan eksperimen NMR, mereka menaruh sampel spin nuklir dalam medan magnet kuat, menerapkan pulsa radio-frequency (RF), lalu mengukur sinyal yang kembali. Teknik ini bertumpu pada beberapa ide dari mekanika kuantum partikel spin-1/2 dalam medan magnet. Spin punya dua level energi yang dipisahkan oleh energi yang sesuai dengan frekuensi Larmor tertentu. Pulsa resonan pada frekuensi itu membuat spin miring ke bidang transversal, di mana ia berpresesi dan menginduksi sinyal yang kemudian meluruh dengan dua waktu karakteristik, T1T_1 dan T2T_2.

Qubit superkonduktor adalah versi rekayasa dari objek yang sama: sistem kuantum dua level yang bisa dikendalikan. State ∣0⟩|0\rangle dan ∣1⟩|1\rangle-nya adalah analog dari spin-up dan spin-down, gate yang kita terapkan untuk memutarnya adalah analog dari pulsa RF, dan ia kehilangan koherensi dengan T1T_1 dan T2T_2-nya sendiri. Ini bukan sekadar perbandingan longgar; beberapa prosesor kuantum eksperimental paling awal pada akhir 1990-an dan awal 2000-an memakai NMR.

Di modul ini kamu akan memakai komputer kuantum sungguhan untuk menjalankan eksperimen karakterisasi yang sama dengan yang dilakukan seorang spektroskopis NMR (atau insinyur kuantum yang mengkalibrasi hardware):

  • Rabi / nutasi — menggerakkan spin dan mengamatinya berputar. Beginilah cara mengkalibrasi rotasi π\pi atau π/2\pi/2.

  • T1T_1 — relaksasi spin-kisi, peluruhan spin tereksitasi kembali ke kesetimbangan.

  • T2T_2 — relaksasi spin-spin (dephasing), diukur dengan eksperimen Ramsey dan difokus ulang dengan Hahn echo.

  • Spektroskopi NMR (pergeseran kimia dan kopling JJ) — mensimulasikan molekul kecil pada beberapa qubit dan membaca spektrum multipletnya untuk mendapatkan kembali konektivitas melalui aturan n+1n+1.

Untuk setiap konsep, pertama-tama kita berikan teori minimalnya, lalu mengimplementasikan eksperimennya di Qiskit mengikuti alur kerja empat langkah Qiskit patterns.

Teori​

Partikel spin-1/2 dalam medan magnet​

Inti atom spin-1/2 punya momen magnetik μ=γℏI\boldsymbol{\mu} = \gamma \hbar \mathbf{I}, dengan γ\gamma adalah rasio giromagnetik. Dalam medan statis B0B_0 sepanjang zz, kedua state spin terpecah dalam energi (efek Zeeman):

E±=∓12γℏB0,ΔE=γℏB0=ℏω0.E_{\pm} = \mp \tfrac{1}{2}\gamma \hbar B_0, \qquad \Delta E = \gamma \hbar B_0 = \hbar \omega_0 .

Celah energi ini mendefinisikan frekuensi Larmor (resonansi) ω0=γB0\omega_0 = \gamma B_0. Momen magnetik berpresesi mengelilingi B0B_0 pada frekuensi yang sama ini.

Untuk memutar spin, kita menerapkan medan RF berosilasi pada (atau dekat) ω0\omega_0. Dalam kerangka yang berputar pada frekuensi drive (rotating frame), drive resonan memutar vektor Bloch mengelilingi sumbu di bidang xx-yy. Seberapa jauh ia berputar (flip angle) ditentukan oleh kekuatan dan durasi pulsa. Dua pilihan yang paling penting: pulsa 90∘90^\circ memiringkan spin +1/2+1/2 ke bidang transversal, dan pulsa 180∘180^\circ memutarnya penuh ke −1/2-1/2.

Menerjemahkan antara jargon NMR dan komputasi kuantum​

Qubit adalah sistem dua level, secara matematis identik dengan spin-1/2 dalam medan magnet — jadi meskipun kita berjalan di prosesor kuantum dan bukan di inti atom, fisikanya adalah fisika yang sama yang mendasari NMR. Hanya kosakatanya yang berbeda. Tabel ini membantumu berkomunikasi dengan kimiawan fisika maupun spesialis komputasi kuantum.

NMR / spin-1/2Prosesor kuantum (qubit)
Spin-up / spin-down∣0⟩\ket{0} / ∣1⟩\ket{1}
Celah Zeeman ΔE=ℏω0\Delta E = \hbar\omega_0Energi transisi qubit ℏω01\hbar\omega_{01}
Frekuensi Larmor ω0\omega_0Frekuensi qubit ω01\omega_{01}
Pulsa RF resonan (flip angle θ\theta)Gate rotasi single-qubit RX(θ)R_X(\theta)
Pulsa 90∘90^\circ / 180∘180^\circRX(π/2)R_X(\pi/2) / RX(π)R_X(\pi)
Relaksasi spin-kisi T1T_1Waktu relaksasi energi qubit T1T_1
Relaksasi spin-spin T2T_2Waktu dephasing qubit T2T_2
NutasiOsilasi Rabi
Free induction decay (FID), Hahn echoUrutan Ramsey, echo

Semua yang ada di bawah ini adalah latihan mengukur kolom kanan dan membacanya sebagai kolom kiri.

Siapkan lingkungan Qiskit​

Kita mengikuti alur kerja Qiskit patterns sepanjang modul:

  1. Petakan masalah ke circuit kuantum.

  2. Optimalkan circuit untuk eksekusi (transpile ke backend).

  3. Eksekusi memakai primitif IBM Quantum.

  4. Post-process dan analisis.

Pertama kita muat layanan runtime dan pilih backend sungguhan yang paling sepi antreannya. Kalau kamu belum menyimpan kredensial, lihat halaman dokumentasi tentang cara menyimpan kredensial login.

# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util

_needed = {"matplotlib": "matplotlib", "numpy": "numpy", "qiskit": "qiskit", "qiskit_aer": "qiskit-aer", "qiskit_ibm_runtime": "qiskit-ibm-runtime", "scipy": "scipy"}
_missing = [pip for module, pip in _needed.items()
if importlib.util.find_spec(module) is None]
# One at a time, so a package that fails to install does not block the others
for _pip in _missing:
%pip install -q {_pip}
if not _missing:
print("\u2713 All required packages are installed")
# Load the IBM Quantum Compute Service and select the least-busy real backend
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler, Batch

# After setup, this picks up your saved credentials automatically:
service = QiskitRuntimeService()

backend = service.least_busy(operational=True, simulator=False, min_num_qubits=156)
print(f"Selected backend: {backend.name}")
Selected backend: ibm_kingston

Pakai cell di bawah untuk beralih antara hardware sungguhan dan simulator lokal untuk seluruh notebook. Menjalankan di hardware adalah inti modul ini, tetapi kalau ada masalah konektivitas atau penggunaan, simulator bisa jadi cadangan. Setel USE_HARDWARE = True untuk menjalankan di QPU yang dipilih di atas.

import numpy as np
from itertools import combinations
from qiskit_aer import AerSimulator
from qiskit.transpiler import generate_preset_pass_manager
from qiskit_ibm_runtime import EstimatorV2 as Estimator

USE_HARDWARE = True # <-- set True to run on the QPU selected above
USE_BATCH = False # Safer for a class with pauses; set True for an uninterrupted run

if USE_HARDWARE:
run_backend = backend # the real QPU selected above
else:
# Local testing mode: an Aer simulator that mimics the selected device -- its noise model,
# coupling map, and qubit count -- while layout choices still use real calibration data.
run_backend = AerSimulator.from_backend(backend)

# --- Choose connected qubits with low readout error. --------------------------------------
def readout_error(q):
props = backend.target["measure"].get((q,))
return props.error if props is not None and props.error is not None else 1.0

coupling_map = backend.coupling_map or backend.target.build_coupling_map()
edges = {tuple(sorted(edge)) for edge in coupling_map.get_edges() if edge[0] != edge[1]}

# AX needs one connected pair.
AX_LAYOUT = list(min(edges, key=lambda edge: sum(readout_error(q) for q in edge)))

# AX2 needs one qubit connected to two neighbours; the first entry is the central A qubit.
neighbors = {q: set() for q in range(backend.num_qubits)}
for q0, q1 in edges:
neighbors[q0].add(q1)
neighbors[q1].add(q0)
ax2_layouts = (
[a, x1, x2]
for a, adjacent in neighbors.items()
for x1, x2 in combinations(adjacent, 2)
)
AX2_LAYOUT = min(ax2_layouts, key=lambda layout: sum(readout_error(q) for q in layout))

# Use a long-lived qubit for the single-qubit experiments.
def best_qubit(backend):
return max(
range(backend.num_qubits), key=lambda q: backend.qubit_properties(q).t2 or 0.0
)

QUBIT = best_qubit(backend)
qp = backend.qubit_properties(QUBIT)
print(
f"Using qubit {QUBIT} for single-qubit experiments: "
f"T1 = {qp.t1 * 1e6:.0f} us, T2 = {qp.t2 * 1e6:.0f} us"
)
print(f"Using physical qubits {AX_LAYOUT} for AX")
print(f"Using physical qubits {AX2_LAYOUT} for AX2 (A first, then the two X spins)")

pm_q = generate_preset_pass_manager(
optimization_level=1, backend=run_backend, initial_layout=[QUBIT]
)
pm_ax = generate_preset_pass_manager(
optimization_level=1, backend=run_backend, initial_layout=AX_LAYOUT
)
pm_ax2 = generate_preset_pass_manager(
optimization_level=1, backend=run_backend, initial_layout=AX2_LAYOUT
)

# Job mode is the default because a classroom pause can deactivate a Batch. On the Open plan,
# a Batch has a 10-minute maximum lifetime and deactivates after 1 minute with no queued job.
batch = Batch(backend=run_backend) if USE_BATCH else None
execution_mode = batch if batch is not None else run_backend
sampler = Sampler(mode=execution_mode)
estimator = Estimator(mode=execution_mode)
estimator.options.resilience_level = 0 # raw expectation values, no error mitigation
estimator.options.default_shots = 1000

mode_name = "Batch" if USE_BATCH else "individual jobs"
device_name = "hardware" if USE_HARDWARE else "local simulator (runtime local mode)"
print(f"Ready. Running on {device_name} using {mode_name}.")

Eksperimen: osilasi Rabi (nutasi)​

Teori​

Gerakkan spin pada resonansi dan vektor Blochnya berputar stabil dari kutub ke kutub — ia bernutasi. Jika kita menerapkan rotasi dengan flip angle θ\theta mengelilingi sumbu-xx pada qubit yang mulai dari ∣0⟩|0\rangle, probabilitas menemukannya di ∣1⟩|1\rangle adalah:

P1(θ)=sin⁡2 ⁣(θ2)=12(1−cos⁡θ).P_1(\theta) = \sin^2\!\left(\tfrac{\theta}{2}\right) = \tfrac{1}{2}\bigl(1 - \cos\theta\bigr).

Inilah kurva Rabi. Maksimum pertamanya adalah pulsa π\pi (inversi penuh, pulsa 180∘180^\circ); pulsa π/2\pi/2 adalah seperempat putaran (pulsa 90∘90^\circ yang memiringkan spin ke bidang transversal). Dalam NMR, kurva ini adalah cara kamu mengkalibrasi pulsa.

Catatan eksperimen

Eksperimen Rabi yang sebenarnya menyapu amplitudo atau durasi drive terhadap waktu. Itu membutuhkan kontrol tingkat pulsa, yang dihapus di Qiskit v2.0. Di sini kita justru menyapu sudut rotasi θ\theta dari gate RX(θ)R_X(\theta). Kurvanya dan identifikasi π\pi/π/2\pi/2 identik; satu-satunya perbedaan adalah kita memilih sudut secara langsung, bukan mengkalibrasi panjang pulsa. Pada hardware dengan fractional gate, RX(θ)R_X(\theta) untuk θ\theta sembarang berjalan secara native.

Tampilan bola Bloch​

Animasi di bawah menunjukkan nutasi: drive resonan memutar vektor Bloch secara stabil mengelilingi sumbu-xx, membawanya dari ∣0⟩|0\rangle turun ke arah ∣1⟩|1\rangle dan kembali lagi. Inilah gerakan yang diukur oleh kurva Rabi.

Petakan masalah ke circuit​

Kita membangun satu circuit berparameter, RX(θ)R_X(\theta) diikuti pengukuran, dan nanti mengevaluasinya pada banyak sudut sekaligus.

from qiskit import QuantumCircuit
from qiskit.circuit import Parameter

theta = Parameter("theta")
rabi = QuantumCircuit(1, 1)
rabi.rx(theta, 0)
rabi.measure(0, 0)
rabi.draw("mpl")

Output of the previous code cell

Optimalkan untuk eksekusi​

Transpile ke circuit Instruction Set Architecture (ISA) untuk backend yang dipilih.

rabi_isa = pm_q.run(rabi)

Eksekusi​

Kita menyapu θ\theta dari 00 sampai 4π4\pi (dua siklus Rabi penuh). Dengan memberikan array nilai parameter sebagai satu Primitive Unified Bloc (PUB), semuanya dijalankan dalam satu job.

angles = np.linspace(0, 4 * np.pi, 41)
job = sampler.run([(rabi_isa, angles.reshape(-1, 1))], shots=1000)
rabi_result = job.result()

Post-process dan analisis​

Ekstrak P1P_1 pada setiap sudut dan bandingkan dengan sin⁡2(θ/2)\sin^2(\theta/2).

import matplotlib.pyplot as plt

ba = rabi_result[0].data.c # BitArray, one entry per angle
shots = ba.num_shots
p1 = np.array([ba.get_counts(i).get("1", 0) / shots for i in range(len(angles))])

plt.figure(figsize=(7, 4))
plt.plot(angles, p1, "o", label="measured")
plt.plot(angles, np.sin(angles / 2) ** 2, "-", label=r"$\sin^2(\theta/2)$")
for k, name in [(np.pi / 2, r"$\pi/2$ (90°)"), (np.pi, r"$\pi$ (180°)")]:
plt.axvline(k, color="gray", ls="--", lw=1)
plt.text(k, 1.02, name, ha="center", fontsize=9)
plt.xlabel(r"flip angle $\theta$ (rad)")
plt.ylabel(r"$P_1$")
plt.title("Rabi / nutation curve")
plt.legend()
plt.tight_layout()
plt.show()

Output of the previous code cell

Uji pemahamanmu​

Pada kurva yang kamu ukur, P1P_1 pertama kali mencapai maksimum pada pulsa π\pi (θ=π\theta = \pi). Sekarang misalkan sebuah perangkat salah kalibrasi, sehingga sudut nominal θ\theta pada sumbu-x sebenarnya menghasilkan rotasi sebesar 1.05 θ1.05\,\theta.

(a) Pada sudut nominal berapa maksimum pertama P1P_1 sekarang muncul? (b) Apakah satu pulsa π\pi saja cara yang sensitif untuk mendeteksi error 5% ini?

Jawaban

(a) Maksimum muncul ketika rotasi sebenarnya sama dengan π\pi, yaitu pada sudut nominal θ=π/1.05≈0.95π\theta = \pi/1.05 \approx 0.95\pi — puncak sedikit bergeser ke kiri.

(b) Tidak. Pulsa π\pi nominal menghasilkan rotasi sebenarnya sebesar 1.05π1.05\pi, sehingga P1=sin⁡2(1.05π/2)≈0.994P_1 = \sin^2(1.05\pi/2) \approx 0.994 alih-alih 11 — perubahan yang terlalu kecil untuk terlihat di tengah noise pengukuran. Inilah sebabnya, untuk mengkalibrasi dengan presisi, para ahli spektroskopi NMR memperkuat galatnya dengan menerapkan rotasi berkali-kali (misalnya metode 360∘360^\circ, atau rangkaian pulsa panjang), sehingga galat kecil per pulsa menumpuk menjadi deviasi besar yang bisa diukur. Kita akan kembali ke ide ini di soal tantangan.

Eksperimen: T1T_1 (relaksasi spin-kisi)​

Teori​

Kalau dibiarkan, spin yang tereksitasi akan melepaskan energi ke sekitarnya ("kisi") dan berelaksasi kembali ke kesetimbangan termal. Populasi keadaan tereksitasi meluruh secara eksponensial dengan waktu relaksasi spin-kisi T1T_1:

P1(τ)=e−τ/T1.P_1(\tau) = e^{-\tau/T_1}.

Bagi seorang kimiawan, T1T_1 menentukan jeda daur ulang yang harus ditunggu di antara pemindaian untuk NMR kuantitatif, dan juga menggambarkan gerak berputar molekul serta dinamikanya. Pada qubit, ini adalah waktu relaksasi energi, dan merupakan salah satu dari dua angka utama untuk memodelkan efek dekoherensi di komputer kuantum.

Eksperimennya. Siapkan ∣1⟩|1\rangle dengan gate XX (pulsa π\pi), tunggu jeda variabel τ\tau, lalu ukur. Fraksi yang masih berada di ∣1⟩|1\rangle menggambarkan peluruhannya.

Tampilan bola Bloch​

Relaksasi T1T_1 mengembalikan keadaan tereksitasi ke kesetimbangan, sehingga vektor Bloch meluncur dari ∣1⟩|1\rangle kembali ke ∣0⟩|0\rangle sepanjang sumbu zz.

Bangun dan transpile pemindaian jeda​

Durasi jeda harus berupa nilai konkret (dijadwalkan dalam waktu hardware), jadi kita membuat satu circuit untuk setiap jeda.

# Scale the T1 delay sweep to the qubit's reported coherence, and snap each delay to an integer
# number of samples (dt) so there are no "duration rounded" warnings (as in the Ramsey/echo cells).
T1_us = (backend.qubit_properties(QUBIT).t1 or 100e-6) * 1e6 # reported T1 (us)
delays_us = np.linspace(0, 5 * T1_us, 20) # 0 .. ~5 T1 captures the full decay
print(f"T1 sweep: 0..{5*T1_us:.0f} us in {len(delays_us)} points")

dt = backend.dt
t1_circuits = []
for tau in delays_us:
n_dt = int(round(tau * 1e-6 / dt)) # integer samples -> no rounding warning
qc = QuantumCircuit(1, 1)
qc.x(0) # pi pulse: prepare |1>
qc.delay(n_dt, 0, unit="dt") # wait tau
qc.measure(0, 0)
t1_circuits.append(qc)

t1_isa = pm_q.run(t1_circuits)
T1 sweep: 0..1393 us in 20 points

Eksekusi​

t1_job = sampler.run([(c,) for c in t1_isa], shots=1000)
t1_res = t1_job.result()

Analisis — fit eksponensial​

from scipy.optimize import curve_fit

p1_t1 = np.array(
[r.data.c.get_counts().get("1", 0) / r.data.c.num_shots for r in t1_res]
)

def decay(t, A, T1, B):
return A * np.exp(-t / T1) + B

p0 = [1.0, 100.0, 0.0]
popt, _ = curve_fit(decay, delays_us, p1_t1, p0=p0, maxfev=10000)
T1_fit = popt[1]

plt.figure(figsize=(7, 4))
plt.plot(delays_us, p1_t1, "o", label="measured")
tt = np.linspace(0, delays_us.max(), 200)
plt.plot(tt, decay(tt, *popt), "-", label=rf"fit: $T_1$ = {T1_fit:.1f} µs")
plt.xlabel("delay τ (µs)")
plt.ylabel(r"$P_1$")
plt.title("$T_1$ relaxation")
plt.legend()
plt.tight_layout()
plt.show()
print(f"Measured T1 = {T1_fit:.1f} microseconds")

Output of the previous code cell

Measured T1 = 225.3 microseconds

Waktu T1T_1 yang tepat berbeda dari satu eksperimen ke eksperimen lain dan dari satu backend ke backend lain, tapi hasilnya seharusnya sekitar beberapa ratus mikrodetik.

Cek pemahamanmu​

Di NMR kuantitatif, kamu diminta menunggu sekitar 5 T15\,T_1 di antara pemindaian. Dengan hukum peluruhan, berapa fraksi magnetisasi kesetimbangan yang sudah pulih setelah 5 T15\,T_1?

Jawaban

Setelah 5 T15\,T_1, sisa deviasi dari kesetimbangan adalah e−5≈0.0067e^{-5}\approx 0.0067, artinya sekitar 99.3% sudah pulih.

Eksperimen: T2T_2, T2∗T_2^*​

Teori​

T1T_1 mengukur hilangnya energi. T2T_2 mengukur hilangnya koherensi fase di bidang transversal. Kita bisa mengamatinya dengan eksperimen Ramsey: pulsa π/2\pi/2 memutar semua spin ke ekuator, semuanya menunjuk ke arah yang sama. Mereka berpresesi Larmor, tapi masing-masing berpresesi pada frekuensi yang sedikit berbeda, sehingga menyebar di sepanjang ekuator dan sinyal transversal netto meluruh, dengan waktu peluruhan karakteristik T2∗T_2^*.

Catatan untuk pembaca kuantum: Gambaran penyebaran ini menunjukkan satu cara kehilangan koherensi: sebaran fase pasti yang saling merata-ratakan. Perlu diketahui bahwa ini bukan satu-satunya mekanisme. Koherensi juga bisa hilang karena fase menjadi terjerat dengan lingkungan, sehingga fase itu bukan lagi nilai pasti yang dimiliki sistem. Kamu tidak bisa membedakan kedua mekanisme hanya dengan melihat peluruhan sinyal, jadi gambaran penyebaran ini adalah intuisi yang bagus. Hati-hati jangan membacanya sebagai cerita yang lengkap atau harfiah.

Dua efek yang membuat spin kehilangan fase:

  • Dekoherensi ireversibel (atau secara efektif ireversibel) yang muncul dari kopling tak terkendali dengan derajat kebebasan lain, dicirikan oleh T2T_2 sejati. Di NMR, ini umumnya berasal dari gerak molekul yang memodulasi kopling antar spin. Di komputasi kuantum, ini sesuai dengan kopling tak diinginkan antara qubit dan lingkungan.
  • Offset frekuensi statis atau yang berubah perlahan dan reversibel, seperti inhomogenitas medan, variasi frekuensi antar qubit, dan galat kalibrasi. Digabung dengan T2T_2 sejati, ini menghasilkan waktu peluruhan semu yang lebih pendek T2∗T_2^* yang teramati dalam eksperimen Ramsey.

Lalu bagaimana kita mendapatkan T2T_2 sejati kalau eksperimen Ramsey hanya memberi tahu T2∗T_2^*? Eksperimen kedua, yang disebut gema Hahn, membatalkan semua noise tipe kedua untuk menemukan T2T_2 sejati. Awalnya sama dengan eksperimen Ramsey, dengan pulsa π/2\pi/2, lalu spin menyebar seperti sebelumnya. Setelah beberapa waktu τ\tau, pulsa π\pi diterapkan. Pulsa ini membalik akumulasi fase yang disebabkan offset frekuensi statis, sehingga spin terfokus kembali setelah jeda kedua dengan durasi yang sama, τ\tau.

T2∗T_2^* dan T2T_2 sama-sama angka penting, baik dalam konteks kimia maupun komputasi kuantum. Dalam kimia, T2∗T_2^* menentukan lebar garis untuk eksperimen NMR standar (kamu akan melihat artinya di bagian berikutnya). T2T_2 bisa memberi tahu banyak hal tentang molekul, seperti gerak berputar molekul, dan perubahan T2T_2 bisa menandakan bahwa molekul terikat pada sesuatu yang lebih besar. Dalam komputasi kuantum, kita umumnya ingin T2T_2 dan T2∗T_2^* sepanjang mungkin, supaya ada lebih banyak waktu untuk menjalankan eksperimen sebelum dekoherensi menguasai sistem. Satu keluarga teknik berbasis gema Hahn memperpanjang waktu ini dengan membatalkan noise reversibel, sehingga circuit idealnya hanya dibatasi oleh T2T_2 sejati, bukan T2∗T_2^*. Ini disebut "dynamical decoupling."

Implementasi​

Saat kita mengimplementasikan eksperimen Ramsey dan gema Hahn pada qubit, pembacaannya sedikit berbeda dari kasus NMR. Di NMR, kamu bisa langsung mengamati medan berosilasi yang diinduksi oleh spin yang berpresesi pada frekuensi Larmor di bidang transversal. Tapi dengan qubit, kita hanya bisa mengukur proyeksinya sepanjang sumbu ZZ, yaitu apakah ∣0⟩\vert 0 \rangle atau ∣1⟩\vert 1 \rangle. Karena itu, untuk kedua eksperimen, kita menutup dengan pulsa π/2\pi/2 tambahan lalu pengukuran. Pulsa π/2\pi/2 ini menerjemahkan informasi fase qubit menjadi populasi.

Di bawah ini kita menjalankan tiap eksperimen untuk menentukan T2∗T_2^* dan T2T_2.

Ramsey (T2∗T_2^{*}): Dua pulsa π/2\pi/2 dengan waktu jeda τ\tau​

Pulsa π/2\pi/2 pertama memiringkan vektor Bloch ke ekuator, dan pulsa berikutnya menerjemahkan fase vektor Bloch menjadi populasi. Karena fase berubah terhadap waktu saat vektor Bloch berpresesi, kita melihat pola sinusoidal pada populasi sebagai fungsi waktu presesi τ\tau. Saat qubit kehilangan koherensi fase, amplitudo kurva sinus ini seharusnya meluruh.

Beberapa kehalusan lagi untuk pembaca kuantum: Dalam eksperimen komputasi kuantum, osilasi yang kita lihat sebenarnya bukan pada frekuensi Larmor, yang berada di rentang GHz dan terlalu cepat untuk kita resolusikan. Eksperimen kuantum beroperasi dalam kerangka berputar: kerangka yang berputar bersama qubit pada (hampir) frekuensi Larmor, sehingga presesi cepat tidak terlihat dan vektor Bloch tampak nyaris diam. Untuk melihat fringe, kita sengaja mengatur kerangka berputar sedikit menyimpang dari frekuensi Larmor. Vektor Bloch lalu tampak berpresesi pada detuning kecil itu, dan kita mendapat sinyal berosilasi pada frekuensi detuning. Di bawah, kita tidak menetapkan detuning pada satu nilai; kita memilihnya sehingga sekitar selusin fringe mengisi jendela pengukuran (yang ukurannya disesuaikan dengan waktu koherensi qubit), sehingga osilasi mudah disampel tanpa aliasing. Satu kerumitan lagi: di Qiskit kita tidak punya kontrol tingkat pulsa yang diperlukan untuk mengubah frekuensi pulsa (yang menentukan frekuensi kerangka berputar). Sebagai gantinya, kita menambahkan detuning efektif secara manual, dengan menggunakannya untuk mengatur fase pulsa Ramsey kedua. Agak dibuat-buat, tapi hasilnya sama dan peluruhan yang kita amati tetap T2∗T_2^* yang asli.

Tampilan bola Bloch​

Pulsa π/2\pi/2 memiringkan spin ke ekuator; masing-masing lalu berpresesi dengan laju yang sedikit berbeda dan mereka menyebar, sehingga vektor netto (merah) mengecil saat kehilangan fase — peluruhan ini adalah T2∗T_2^*.

# Adaptive Ramsey: scale the window to the reported coherence and choose the detuning so a fixed
# number of fringes span it. This decouples the point count from the window -- a long window stays
# cheap and cannot alias (we keep 8 samples per fringe).
T2_us = (backend.qubit_properties(QUBIT).t2 or 100e-6) * 1e6 # reported (echo) T2 (us)
ramsey_span = 0.5 * T2_us # window = half the reported T2
n_fringes = 12 # fringes across the window
f_detune = n_fringes / (
ramsey_span * 1e-6
) # adaptive detuning (Hz) -> low, so a long window is cheap
samp_per_fringe = 8
n_ramsey = n_fringes * samp_per_fringe + 1 # ~97 points, fixed regardless of T2
ramsey_delays_us = np.linspace(0, ramsey_span, n_ramsey)
print(
f"Ramsey sweep: 0..{ramsey_span:.0f} us in {n_ramsey} points "
f"(detuning {f_detune/1e3:.1f} kHz, {samp_per_fringe} samples/fringe)"
)

dt = backend.dt # seconds per sample
delays_dt = [int(round(t * 1e-6 / dt)) for t in ramsey_delays_us] # integer samples
actual_delays_us = np.array(delays_dt) * dt * 1e6 # true (snapped) delays for fitting

ramsey_circ = []
for n_dt, tau_us in zip(delays_dt, actual_delays_us):
qc = QuantumCircuit(1, 1)
qc.rx(np.pi / 2, 0)
qc.delay(n_dt, 0, unit="dt") # integer duration -> no rounding warning
qc.rz(
2 * np.pi * f_detune * tau_us * 1e-6, 0
) # detuning phase (uses the true delay)
qc.rx(np.pi / 2, 0)
qc.measure(0, 0)
ramsey_circ.append(qc)

ramsey_isa = pm_q.run(ramsey_circ)
ramsey_res = sampler.run([(c,) for c in ramsey_isa], shots=1000).result()
p1_ramsey = np.array(
[r.data.c.get_counts().get("1", 0) / r.data.c.num_shots for r in ramsey_res]
)

ramsey_circ[len(ramsey_circ) // 2].draw("mpl") # draw a sample circuit (mid-sweep)
Ramsey sweep: 0..248 us in 97 points (detuning 48.4 kHz, 8 samples/fringe)

Output of the previous code cell

Gema Hahn (T2T_2): Tambahkan pulsa π\pi pemfokus ulang di tengah​

Tampilan bola Bloch​

Gema Hahn: spin menyebar seperti sebelumnya, lalu pulsa π\pi membalikkannya, sehingga presesi lanjutan mereka memfokuskan ulang mereka kembali bersama (vektor merah tumbuh lagi), memulihkan koherensi yang hilang akibat dephasing statis.

# Scale the echo sweep to the reported (echo) T2. The echo signal is monotonic (no fringes),
# so only the range matters here; 0 .. ~2.5 T2 shows the full decay for a clean fit.
T2_us = (backend.qubit_properties(QUBIT).t2 or 100e-6) * 1e6
echo_delays_us = np.linspace(0, 2.5 * T2_us, 24) # total free-evolution time
print(f"Echo sweep: 0..{2.5*T2_us:.0f} us in {len(echo_delays_us)} points")

dt = backend.dt
half_dt = [
int(round(t / 2 * 1e-6 / dt)) for t in echo_delays_us
] # samples per half-delay
actual_echo_us = np.array(half_dt) * 2 * dt * 1e6 # true total delay for fitting

echo_circ = []
for n_half in half_dt:
qc = QuantumCircuit(1, 1)
qc.rx(np.pi / 2, 0)
qc.delay(n_half, 0, unit="dt") # half the free-evolution, integer samples
qc.rx(np.pi, 0) # refocusing pi pulse
qc.delay(n_half, 0, unit="dt")
qc.rx(-np.pi / 2, 0)
qc.measure(0, 0)
echo_circ.append(qc)

echo_isa = pm_q.run(echo_circ)
echo_res = sampler.run([(c,) for c in echo_isa], shots=1000).result()
p1_echo = np.array(
[r.data.c.get_counts().get("1", 0) / r.data.c.num_shots for r in echo_res]
)

echo_circ[len(echo_circ) // 2].draw("mpl") # draw a sample circuit
Echo sweep: 0..1240 us in 24 points

Output of the previous code cell

Analisis: Fit dan bandingkan kedua peluruhan​

# Initial guesses / bounds derived from the reported coherence so the fits track the scaled axes.
T2_us = (backend.qubit_properties(QUBIT).t2 or 100e-6) * 1e6

# Ramsey: decaying oscillation -> extract T2*
def ramsey_fn(t, A, T2s, f, phi, B):
return A * np.exp(-t / T2s) * np.cos(2 * np.pi * f * t + phi) + B

rp0 = [0.4, T2_us / 2, f_detune * 1e-6, 0.0, 0.5] # T2* guess ~ half the echo T2
rbounds = (
[0, 0.1, 0, -np.pi, 0],
[1, 2 * T2_us, 5, np.pi, 1],
) # keep the fit from collapsing to ~0
rpopt, _ = curve_fit(
ramsey_fn, ramsey_delays_us, p1_ramsey, p0=rp0, bounds=rbounds, maxfev=20000
)
T2_star = abs(rpopt[1])

# Echo: simple exponential envelope -> extract T2
def env(t, A, T2, B):
return A * np.exp(-t / T2) + B

epopt, _ = curve_fit(env, echo_delays_us, p1_echo, p0=[0.5, T2_us, 0.0], maxfev=10000)
T2 = abs(epopt[1])

fig, ax = plt.subplots(1, 2, figsize=(12, 4))
ax[0].plot(ramsey_delays_us, p1_ramsey, "o")
tt = np.linspace(0, ramsey_delays_us.max(), 400)
ax[0].plot(tt, ramsey_fn(tt, *rpopt), "-")
ax[0].set_title(rf"Ramsey: $T_2^*$ = {T2_star:.1f} µs")
ax[0].set_xlabel("τ (µs)")
ax[0].set_ylabel(r"$P_1$")
ax[1].plot(echo_delays_us, p1_echo, "o")
tt2 = np.linspace(0, echo_delays_us.max(), 400)
ax[1].plot(tt2, env(tt2, *epopt), "-")
ax[1].set_title(rf"Hahn echo: $T_2$ = {T2:.1f} µs")
ax[1].set_xlabel("τ (µs)")
plt.tight_layout()
plt.show()
print(f"T2* (Ramsey) = {T2_star:.1f} µs ; T2 (echo) = {T2:.1f} µs")

Output of the previous code cell

T2* (Ramsey) = 171.2 µs ; T2 (echo) = 326.0 µs

Di sini kita melihat osilasi Ramsey dan sinyal gema Hahn meluruh, seperti yang diharapkan. Skala waktu masing-masing peluruhan ini adalah T2∗T_2^* dan T2T_2.

Cek pemahamanmu​

Biasanya kamu akan mendapati T2≳T2∗T_2 \gtrsim T_2^{*}. Jelaskan alasannya dalam satu kalimat, dan sebutkan efek fisik apa yang dihilangkan oleh pulsa π\pi pada gema Hahn, serta apa yang tidak bisa dihilangkannya.

Jawaban

T2∗T_2^{*} mencakup dephasing ireversibel dan sebaran statis reversibel, sehingga meluruh lebih cepat; pulsa π\pi pada gema memfokuskan ulang dephasing statis dan reversibel (inhomogenitas medan / offset frekuensi tetap), menyisakan hanya dephasing spin-spin ireversibel, yaitu T2T_2 sejati. Gema tidak bisa membatalkan fluktuasi yang berubah di antara kedua paruh urutan.

Spektroskopi NMR sebuah molekul: Pergeseran kimia dan kopling JJ​

Dari satu spin ke sebuah molekul​

Eksperimen sejauh ini mengkarakterisasi satu spin yang terisolasi. NMR kimia sungguhan membaca sebuah molekul (banyak spin berkopling sekaligus) dan menggunakan spektrum yang dihasilkan untuk menyimpulkan fitur struktur molekul. Ini bisa memberi tahu kita tentang dua efek:

  • Pergeseran kimia. Setiap inti yang berbeda secara kimia berpresesi pada frekuensi νi\nu_i yang sedikit berbeda, karena elektron di sekitarnya menyaring medan yang diterapkan dengan jumlah yang berbeda. Jadi posisi puncak mengidentifikasi lingkungan kimia (misalnya proton CH3_3 versus proton aromatik).

  • Kopling skalar JJ. Interaksi melalui ikatan antara dua spin yang memecah setiap puncak menjadi multiplet. Inti dengan nn tetangga ekuivalen terpecah menjadi n+1n+1 garis dengan intensitas binomial, yaitu aturan n+1n+1. Pola pemecahan mengungkap konektivitas (inti mana yang berikatan), dan jaraknya adalah konstanta kopling JJ.

Eksperimennya​

Untuk memodelkan ini di komputer kuantum kita, kita perlu mengimplementasikan Hamiltonian spin pada qubit. Dalam limit kopling lemah dan medan tinggi (selisih pergeseran kimia ≫\gg kopling), bagian XXXX dan YYYY dari kopling saling merata-ratakan, menyisakan hanya ZZZZ. Jadi Hamiltonian-nya adalah (satuan frekuensi sudut, ℏ=1\hbar=1):

H=∑iπ νi Zi⏟chemical shift  +  ∑i<jπ2 Jij ZiZj⏟J-coupling.H = \underbrace{\sum_i \pi\,\nu_i\, Z_i}_{\text{chemical shift}} \;+\; \underbrace{\sum_{i<j} \tfrac{\pi}{2}\,J_{ij}\, Z_iZ_j}_{J\text{-coupling}}.

Setiap inti adalah satu qubit: pergeseran kimia adalah rotasi ZZ satu-qubit, dan kopling adalah interaksi ZZZZ dua-qubit.

Eksperimen berjalan sebagai berikut. Pulsa π/2\pi/2 memiringkan setiap spin ke bidang transversal, dan spin berpresesi serta berkopling selama waktu tt. Kita merekam magnetisasi transversal kompleks s(t)=⟨X⟩+i⟨Y⟩s(t) = \langle X\rangle + i\langle Y\rangle yang dijumlahkan atas semua spin. Seperti pada eksperimen Ramsey di atas, magnetisasi transversal ini menggambarkan sinusoid yang meluruh sebagai fungsi waktu presesi tt. Saat kita mengambil transformasi Fourier dari sinyal, kita mendapat spektrum dengan puncak pada pergeseran kimia νi\nu_i yang terpecah oleh kopling skalar JijJ_{ij}.

catatan

Kita mensimulasikan evolusi molekul dengan memetakan waktu tt ke sudut rotasi: tt yang lebih besar berarti rotasi ZZ dan ZZZZ yang lebih besar, bukan circuit yang lebih panjang atau lebih dalam. Jadi meskipun keadaan yang disimulasikan memang maju seiring tt (itulah yang membuat free induction decay (FID) berosilasi), qubit fisik tidak pernah menganggur lebih lama dan tidak ada dekoherensi yang bergantung pada tt. Karena itu FID hasil simulasi tidak pernah meluruh dengan sendirinya. Untuk FID ideal yang sangat panjang tak hingga, garis spektrum akan sangat tajam tak hingga; jendela sampling terbatas kita justru memberi mereka resolusi terbatas dan bisa menimbulkan kebocoran spektral. Garis NMR sungguhan memiliki lebar terbatas yang ditentukan oleh T2∗T_2^*. Kita menambahkannya kembali dengan mengalikan FID dengan jendela e−t/T2∗e^{-t/T_2^{*}} sebelum transformasi Fourier. Ini disebut apodisasi pelebaran garis, yang diterapkan para ahli spektroskopi saat pemrosesan untuk menentukan lebar garis (menukar resolusi dengan rasio sinyal-terhadap-noise). (Sel opsional di Appendix modul ini menunjukkan alternatif Trotterized, di mana peluruhan justru berasal dari dekoherensi nyata yang tumbuh seiring kedalaman circuit.)

Sistem AX​

Mari mulai dengan sistem AX, yaitu molekul yang terdiri dari dua spin berkopling yang berbeda secara kimia. Label A dan X menunjukkan dua lingkungan spin yang berbeda secara kimia. Hurufnya sembarang, tapi fakta bahwa keduanya berjauhan dalam alfabet menggambarkan perbedaan kimia mereka.

Petakan dan optimalkan​

Kita menulis satu fungsi yang, dengan offset pergeseran kimia νi\nu_i dan kopling JijJ_{ij}, mengembalikan circuit persiapan keadaan yang terparameterisasi. Pulsa π/2\pi/2 memiringkan spin ke ekuator, lalu rotasi ZZ mensimulasikan evolusi keadaan di bawah suku pergeseran kimia dan kopling JJ. Setiap νi\nu_i adalah offset presesi kecil dalam Hz relatif terhadap referensi yang dipilih, atau carrier kerangka berputar. Di sini kita menetapkannya secara manual; pada molekul sungguhan, pelindungan elektron mengubah medan magnet lokal dan menggeser resonansi tiap inti relatif terhadap referensi itu. Ini dimodelkan oleh gate RZR_Z satu-qubit, satu per spin, dan setiap νi\nu_i menentukan di mana puncak spin itu berada dalam spektrum. Gate ZZZZ dua-qubit menerapkan kopling JijJ_{ij}, yang memecah setiap puncak menjadi multiplet dan menentukan jarak antar garisnya.

from qiskit import QuantumCircuit
from qiskit.circuit import Parameter
from qiskit.quantum_info import SparsePauliOp

t = Parameter(
"t"
) # evolution time (arbitrary units; the frequencies below are in matching units)

def fid_circuit(offsets, couplings):
"""State-preparation circuit for a set of spins: a pi/2 pulse, then evolution under the
chemical-shift + J-coupling Hamiltonian for time t. No measurement -- the Estimator reads
<X> and <Y> directly.

offsets : list of chemical-shift frequencies nu_i, one per qubit
couplings : dict {(i, j): J_ij}
"""
n = len(offsets)
qc = QuantumCircuit(n)
qc.ry(np.pi / 2, range(n)) # pi/2 pulse: tip every spin into the transverse plane
for i, nu in enumerate(offsets):
qc.rz(2 * np.pi * nu * t, i) # chemical-shift precession -> line at nu_i
for (i, j), J in couplings.items():
qc.rzz(np.pi * J * t, i, j) # J-coupling -> splitting of size J
return qc

def transverse_obs(n):
"""Total transverse observables (sum over spins): (sum_i X_i, sum_i Y_i)."""
ox = SparsePauliOp.from_sparse_list(
[("X", [i], 1.0) for i in range(n)], num_qubits=n
)
oy = SparsePauliOp.from_sparse_list(
[("Y", [i], 1.0) for i in range(n)], num_qubits=n
)
return ox, oy

# AX system: two distinct chemical shifts, one coupling
ax_offsets = [6.0, -6.0] # chemical shifts nu_1, nu_2 (Hz)
ax_couplings = {(0, 1): 2.0} # J_12 = 2 Hz

ax_isa = pm_ax.run(fid_circuit(ax_offsets, ax_couplings))
fid_circuit(ax_offsets, ax_couplings).draw("mpl")

Eksekusi dan pasca-pemrosesan​

Kita mengevaluasi circuit pada grid waktu evolusi, dan Estimator mengembalikan ⟨X⟩\langle X\rangle dan ⟨Y⟩\langle Y\rangle di setiap titik. Kita menggabungkannya menjadi FID kompleks ⟨X⟩+i⟨Y⟩\langle X\rangle + i\langle Y\rangle, menerapkan jendela apodisasi, dan mentransformasi Fourier hasilnya untuk mendapatkan spektrum.

# --- time grid ---
n_t = 128 # number of FID samples
dt = 1 / 32 # time step -> Nyquist frequency = 1/(2 dt) = 16 Hz
times = np.arange(n_t) * dt

# --- Step 3-4: Estimator returns <X> and <Y> directly (both quadratures in one job) ---
def run_fid(isa_circ, n, est):
"""Complex FID s(t) = sum_i <X_i> + i <Y_i> over the time grid, via the Estimator."""
ox, oy = transverse_obs(n)
ox = ox.apply_layout(isa_circ.layout) # map observables onto the transpiled qubits
oy = oy.apply_layout(isa_circ.layout)
params = times.reshape(-1, 1)
res = est.run(
[(isa_circ, ox, params), (isa_circ, oy, params)]
).result() # both quadratures, one job
return res[0].data.evs + 1j * res[1].data.evs

def spectrum(fid, T2=1.0):
sig = fid * np.exp(-times / T2) # apodization -> Lorentzian linewidth ~ 1/(pi T2)
spec = np.fft.fftshift(np.fft.fft(sig))
freq = np.fft.fftshift(np.fft.fftfreq(len(times), dt))
return freq, np.abs(spec)

fid_ax = run_fid(ax_isa, n=2, est=estimator)
freq, spec = spectrum(fid_ax, T2=1.0)

fig, ax = plt.subplots(1, 2, figsize=(12, 4))
ax[0].plot(times, fid_ax.real, label=r"Re $s(t)=\langle X\rangle$")
ax[0].plot(times, fid_ax.imag, label=r"Im $s(t)=\langle Y\rangle$")
ax[0].set_xlabel("evolution time $t$")
ax[0].set_ylabel("transverse signal")
ax[0].set_title("AX free induction decay")
ax[0].legend()
ax[1].plot(freq, spec)
for nu in ax_offsets:
for s in (-1, 1):
ax[1].axvline(nu + s * ax_couplings[(0, 1)] / 2, color="gray", ls="--", lw=1)
ax[1].set_xlim(-12, 12)
ax[1].set_xlabel(r"frequency $\nu$ (Hz)")
ax[1].set_ylabel("intensity")
ax[1].set_title("AX spectrum: two doublets, split by $J$")
plt.tight_layout()
plt.show()
print(
"Expected AX lines (Hz):",
sorted({nu + s * ax_couplings[(0, 1)] / 2 for nu in ax_offsets for s in (-1, 1)}),
)

Output of the previous code cell

Expected AX lines (Hz): [-7.0, -5.0, 5.0, 7.0]

Ekstensi: Sistem AX2_2 dan aturan n+1n+1​

Sekarang tambahkan tetangga ekuivalen kedua. Dalam sistem AX2_2, satu spin A berkopling, dengan JJ yang sama, ke dua spin X ekuivalen. (Ingat, X hanyalah label untuk dua spin ekuivalen itu; tidak ada hubungannya dengan operator Pauli XX. Setiap kopling di bawah tetap ZZZZ.) Spin A lalu melihat empat konfigurasi tetangga (↑↑, ↑↓, ↓↑, ↓↓)(\uparrow\uparrow,\ \uparrow\downarrow,\ \downarrow\uparrow,\ \downarrow\downarrow); dua yang campuran berimpit, jadi A terpecah menjadi triplet 1:2:1 (n=2⇒n+1=3n=2 \Rightarrow n+1 = 3 garis). Setiap spin X hanya melihat A dan tetap berupa doublet. Spin ekuivalen tidak saling memecah, jadi kita tidak menyertakan kopling X–X.

Molekul nyata dengan pola ini adalah 1,1,2-trikloroetana (Cl2_2CH–CH2_2Cl). Saat menargetkan spin hidrogen (bentuk NMR yang paling umum), proton hidrogen tunggal di CH berperan sebagai A, dan dua proton CH2_2 yang ekuivalen adalah dua spin X. Sekarang tinggal hitung tetangga: proton CH punya dua tetangga (pasangan CH2_2), jadi sinyalnya terpecah menjadi triplet 1:2:1 (n=2n=2); setiap proton CH2_2 hanya punya satu tetangga (CH), jadi sinyal CH2_2 adalah doublet 1:1 (n=1n=1). Perhatikan bahwa gugus dengan proton lebih sedikit (CH) menampilkan garis lebih banyak; jumlah garis mencerminkan berapa banyak tetangga yang dimiliki suatu sinyal, bukan berapa banyak proton yang berbagi sinyal itu.

1,1,2-trichloroethane: the CH proton is A, the two equivalent CH_2 protons are X

Di bawah ini, kita mensimulasikan sistem AX2_2 dengan tiga qubit.

# AX2: spin A (qubit 0) coupled equally to two equivalent X spins (qubits 1, 2)
ax2_offsets = [6.0, -6.0, -6.0] # A at +6 Hz; the two X both at -6 Hz
ax2_couplings = {
(0, 1): 2.0,
(0, 2): 2.0,
} # equal A-X couplings; no coupling between the X spins

ax2_isa = pm_ax2.run(fid_circuit(ax2_offsets, ax2_couplings))
fid_ax2 = run_fid(ax2_isa, n=3, est=estimator)
freq2, spec2 = spectrum(fid_ax2, T2=1.0)

fig, ax = plt.subplots(1, 2, figsize=(12, 4))
ax[0].plot(times, fid_ax2.real, label=r"Re $s(t)=\langle X\rangle$")
ax[0].plot(times, fid_ax2.imag, label=r"Im $s(t)=\langle Y\rangle$")
ax[0].set_xlabel("evolution time $t$")
ax[0].set_ylabel("transverse signal")
ax[0].set_title("AX$_2$ free induction decay")
ax[0].legend()
ax[1].plot(freq2, spec2)
ax[1].set_xlim(-12, 12)
ax[1].set_xlabel(r"frequency $\nu$ (Hz)")
ax[1].set_ylabel("intensity")
ax[1].set_title(r"AX$_2$ spectrum: A is a 1:2:1 triplet, X is a doublet")
plt.tight_layout()
plt.show()
print(
"A triplet at (Hz):",
[ax2_offsets[0] + k * ax2_couplings[(0, 1)] for k in (-1, 0, 1)],
)
print(
"X doublet at (Hz):",
[ax2_offsets[1] + s * ax2_couplings[(0, 1)] / 2 for s in (-1, 1)],
)

Cek pemahamanmu​

Baca tiap pertanyaan, pikirkan jawabanmu, lalu klik segitiga untuk menampilkan solusinya.

Pada spektrum AX, kedua doublet dipisahkan (pusat ke pusat) sejauh 12 Hz, dan dua garis dalam tiap doublet berjarak 2 Hz. Angka mana yang merupakan selisih pergeseran kimia dan mana yang konstanta kopling JJ? Kalau kamu pindah ke spektrometer dengan medan magnet dua kali lipat, mana yang akan berubah?

Jawaban

Pemisahan pusat ke pusat 12 Hz adalah selisih pergeseran kimia (ν1−ν2\nu_1-\nu_2); jarak garis 2 Hz adalah konstanta kopling JJ. Pergeseran kimia berasal dari pelindungan B0B_0, jadi skalanya mengikuti medan. Pada medan dua kali lipat, pemisahan menjadi dua kali lipat, 24 Hz. Kopling JJ adalah properti molekul melalui ikatan dan tidak bergantung pada medan, jadi tetap 2 Hz. Para kimiawan membedakan pergeseran dari kopling dengan melihat bagaimana pemecahan berskala terhadap medan.

Dengan aturan n+1n+1, prediksikan multiplet A kalau spin X ekuivalen ketiga ditambahkan (sistem AX3_3), dan jelaskan mengapa dua spin X ekuivalen di AX2_2 tidak saling memecah.

Jawaban

Dengan n=3n=3 tetangga ekuivalen, A menjadi kuartet 1:3:3:1. Spin ekuivalen tidak saling memecah karena kopling mereka menggeser tingkat energi yang relevan dengan jumlah yang sama, sehingga selisih antar tingkat itu — frekuensi yang kita lihat — tidak berubah.

Kesimpulan​

Kita telah menggunakan komputer kuantum sungguhan untuk menjelajahi bagaimana spin inti berpresesi dan mengalami dekoherensi dalam eksperimen NMR, dan bagaimana menafsirkan spektrum yang dihasilkan. Ada baiknya kita mundur sejenak untuk meninjau mengapa ini bisa dilakukan, dan mengapa ini lebih dari sekadar keingintahuan.

Qubit dan inti spin-1/2 secara matematis adalah objek yang sama: sistem kuantum dua tingkat yang hidup di bola Bloch, diputar oleh pulsa resonan, berpresesi mengitari sebuah sumbu, dan kehilangan koherensi ke lingkungannya. Seluruh kosakata modul ini (frekuensi Larmor, kerangka berputar, T1T_1, T2T_2, dan T2∗T_2^*, fringe Ramsey, gema Hahn) sama-sama milik NMR dan komputasi kuantum. Apa yang disebut kimiawan pergeseran kimia, disebut insinyur kuantum rotasi ZZ satu-qubit; apa yang disebut kimiawan kopling JJ, disebut insinyur kuantum interaksi ZZZZ. Kedua bidang ini telah menggambarkan fisika yang sama dengan kata-kata berbeda.

Hubungan itu bersifat historis, bukan sekadar konseptual: NMR adalah salah satu platform pertama yang menjalankan algoritma kuantum sungguhan. Pada 2001, sebuah molekul dalam larutan telah mengeksekusi algoritma Shor untuk memfaktorkan 15, dengan setiap spin inti berfungsi sebagai qubit. NMR juga menyumbangkan banyak perangkat kontrol ke komputasi kuantum. Gema spin, rangkaian pulsa CPMG, pulsa komposit, dan dynamical decoupling semuanya ditemukan oleh para ahli spektroskopi puluhan tahun sebelum qubit membutuhkannya untuk melawan dekoherensi. Ada simetri yang memuaskan dalam apa yang baru saja kita lakukan: bidang yang melahirkan komputer kuantum awal kini menjadi sesuatu yang bisa disimulasikan oleh komputer kuantum.

Ulasan dan pertanyaan​

Konsep penting​

  • Qubit adalah sistem dua tingkat rekayasa yang ekuivalen dengan spin-1/2; ∣0⟩/∣1⟩|0\rangle/|1\rangle, frekuensi, gate, T1T_1, dan T2T_2-nya adalah analog langsung dari besaran NMR.

  • Pulsa resonan memutar vektor Bloch; sudut balik menentukan rotasi, dan kurva Rabi/nutasi P1=sin⁡2(θ/2)P_1=\sin^2(\theta/2) mengidentifikasi rotasi π\pi dan π/2\pi/2.

  • T1T_1 adalah relaksasi energi (spin-kisi); T2T_2 adalah relaksasi fase (spin-spin); T2∗T_2^{*} juga mencakup dephasing statis reversibel yang difokuskan ulang oleh gema Hahn.

  • Molekul adalah himpunan spin berkopling: pergeseran kimia menentukan posisi tiap puncak, dan kopling JJ memecah puncak menjadi multiplet mengikuti aturan n+1n+1, yang bersama-sama mengodekan lingkungan kimia dan konektivitas.

Benar / salah​

  1. B/S Frekuensi Larmor adalah celah energi antara dua keadaan spin dibagi ℏ\hbar.

  2. B/S Pulsa 90∘90^\circ (π/2\pi/2) membalik sepenuhnya spin kesetimbangan.

  3. B/S T2∗T_2^{*} umumnya lebih panjang daripada T2T_2.

  4. B/S Gema Hahn memfokuskan ulang dephasing spin-spin ireversibel.

  5. B/S Pada sistem AX2_2, inti A tampak sebagai triplet 1:2:1.

  6. B/S Konstanta kopling JJ menjadi dua kali lipat saat kamu menggandakan medan magnet spektrometer.

  7. B/S Dihapusnya Qiskit Pulse membuat pengukuran T1T_1 pada hardware saat ini menjadi mustahil.

Jawaban singkat​

  1. Jelaskan dalam dua kalimat mengapa satu pulsa π\pi adalah cara yang buruk untuk mendeteksi galat sudut balik 5%5\%, dan bagaimana merangkai pulsa mengatasinya.

  2. Sebuah sampel menunjukkan garis NMR yang lebar. Dalam hal T2T_2, apa artinya itu, dan besaran qubit apa yang menjadi analognya?

  3. Berikan analog qubit untuk masing-masing: (a) jeda daur ulang, (b) pulsa 90∘90^\circ, (c) pergeseran kimia.

  4. Dengan aturan n+1n+1, jelaskan mengapa gugus etil (CH3_3CH2_2–) menampilkan triplet 1:2:1 dan kuartet 1:3:3:1, dan identifikasi gugus mana yang menghasilkan multiplet mana.

Soal tantangan​

  1. Rangkaian Carr-Purcell. Modifikasi gema Hahn untuk menyisipkan nn pulsa π\pi berjarak sama (rangkaian gaya CPMG) dan ukur T2T_2 sebagai fungsi nn. Diskusikan mengapa lebih banyak pulsa pemfokus ulang bisa memperpanjang T2T_2 semu.

  2. Amplifikasi galat pulsa. Bangun circuit yang menerapkan RX(π)R_X(\pi) sebanyak  n\,n kali pada ∣0⟩|0\rangle dan plot P1P_1 terhadap nn untuk sudut yang sengaja diatur salah RX(1.05π)R_X(1.05\pi). Tunjukkan bagaimana galat menjadi terlihat saat nn bertambah. Petunjuk: letakkan barrier setelah setiap rotasi; kalau tidak, transpiler mungkin menggabungkan rotasi berulang menjadi satu gate ekuivalen.

  3. Gugus etil. Dengan fid_circuit, bangun spektrum proton fragmen etil (CH3_3–CH2_2): lima qubit, dengan tiga proton CH3_3 berbagi satu pergeseran kimia dan dua proton CH2_2 berbagi yang lain, dan setiap pasangan CH3_3–CH2_2 berkopling dengan JJ yang sama (tanpa kopling dalam gugus ekuivalen). Prediksikan polanya dengan aturan n+1n+1, lalu simulasikan; kamu seharusnya mendapatkan kuartet 1:3:3:1 (CH2_2) dan triplet 1:2:1 (CH3_3).

Appendix (opsional): Evolusi Trotterized — biarkan dekoherensi menentukan lebar garis​

Di bagian spektroskopi NMR sebuah molekul, evolusi e−iHte^{-iHt} diterapkan dalam satu lapisan yang kedalamannya tidak bertambah seiring tt, sehingga FID tidak meluruh dengan sendirinya dan kita menetapkan lebar garis dengan jendela apodisasi e−t/T2e^{-t/T_2}. Sel opsional ini mengambil pendekatan sebaliknya. Ia memecah evolusi menjadi pp langkah Trotter dan menaruh barrier setelah tiap langkah (sehingga transpiler tidak bisa menggabungkannya kembali menjadi satu lapisan). Titik waktu kk memakai kk langkah, jadi kedalaman circuit bertambah seiring waktu.

Karena setiap suku dalam HH saling komut, ini eksak: pada simulator tanpa noise, hasilnya sama-sama puncak tajam. Tapi pada backend model noise atau hardware sungguhan, galat gate yang terakumulasi membuat FID meluruh dengan sendirinya, dan peluruhan itulah yang menentukan lebar garis. Ini adalah ide T2↔T_2\leftrightarrowlebar garis dari bagian eksperimen T2T_2, T2∗T_2^*, yang kini muncul dari dekoherensi nyata alih-alih jendela pemrosesan. Kita menjalankan sistem AX (dua doublet) dan sistem AX2_2 (triplet 1:2:1 plus doublet) dengan cara ini.

Dua catatan praktis untuk dipertimbangkan. Pertama, biayanya adalah kedalaman: circuit AX terdalam memakai ∼ ⁣47\sim\!47 gate dua-qubit dan versi AX2_2 sekitar dua kali lipatnya, jadi keduanya jauh lebih bernoise dan lebih mahal daripada versi eksak. Kedua, setiap sistem spin tetap dikirim sebagai satu job, dan setiap titik waktu memakai ulang layout qubit fisik yang sama yang dipilih di bagian spektroskopi. Jika USE_BATCH=True, job-job ini juga memakai Batch opsional untuk seluruh notebook.

# Trotterized FID: depth grows with time, so real decoherence (not apodization) sets the linewidth.
estimator.options.default_shots = 2000 # deep circuits need more shots in this appendix
n_tr, dt_tr = (
48,
1 / 20,
) # Nyquist = 1/(2 dt) = 10 Hz (covers the AX2 triplet's 8 Hz line)
times_tr = np.arange(n_tr) * dt_tr
APODIZE_TROTTER = False # keep False so the linewidth comes from decoherence

def trotter_fid_circuit(offsets, couplings, p, step):
n = len(offsets)
qc = QuantumCircuit(n)
qc.ry(np.pi / 2, range(n)) # 90-degree pulse
for _ in range(p): # p Trotter steps, each of duration `step`
for i, nu in enumerate(offsets):
qc.rz(2 * np.pi * nu * step, i) # chemical-shift evolution for one step
for (i, j), J in couplings.items():
qc.rzz(np.pi * J * step, i, j) # J-coupling evolution for one step
qc.barrier() # <-- stops the transpiler merging the steps
return qc

def run_trotter(offsets, couplings, n, est, pass_manager):
# Time point k uses k Trotter steps (its own circuit). Submit every time point and both
# quadratures together as ONE Estimator job, using one fixed physical-qubit layout.
ox, oy = transverse_obs(n)
pubs = []
for k in range(n_tr):
isa = pass_manager.run(trotter_fid_circuit(offsets, couplings, k, dt_tr))
pubs.append((isa, ox.apply_layout(isa.layout))) # <X> at time k
pubs.append((isa, oy.apply_layout(isa.layout))) # <Y> at time k
res = est.run(pubs).result()
return np.array(
[res[2 * k].data.evs + 1j * res[2 * k + 1].data.evs for k in range(n_tr)]
)

def trotter_spectrum(fid):
sig = fid * np.exp(-times_tr / 1.0) if APODIZE_TROTTER else fid
spec = np.abs(np.fft.fftshift(np.fft.fft(sig)))
freq = np.fft.fftshift(np.fft.fftfreq(n_tr, dt_tr))
return freq, spec

fid_tr = run_trotter(
ax_offsets, ax_couplings, 2, estimator, pm_ax
) # AX -> two doublets
fid2_tr = run_trotter(
ax2_offsets, ax2_couplings, 3, estimator, pm_ax2
) # AX2 -> triplet + doublet

f_ax, s_ax = trotter_spectrum(fid_tr)
f_ax2, s_ax2 = trotter_spectrum(fid2_tr)

fig, ax = plt.subplots(1, 3, figsize=(15, 4))
ax[0].plot(times_tr, np.abs(fid_tr), "o-", label="AX")
ax[0].plot(times_tr, np.abs(fid2_tr), "s-", label="AX$_2$")
ax[0].set_xlabel("evolution time $t$")
ax[0].set_ylabel("|FID|")
ax[0].set_title("Trotter FID envelope (decays only on a noisy backend)")
ax[0].legend()
ax[1].plot(f_ax, s_ax)
ax[1].set_xlim(-12, 12)
ax[1].set_xlabel(r"frequency $\nu$ (Hz)")
ax[1].set_ylabel("intensity")
ax[1].set_title("AX: two doublets")
ax[2].plot(f_ax2, s_ax2)
ax[2].set_xlim(-12, 12)
ax[2].set_xlabel(r"frequency $\nu$ (Hz)")
ax[2].set_title("AX$_2$: 1:2:1 triplet + doublet")
plt.tight_layout()
plt.show()
print(
f"Deepest AX circuit: {n_tr-1} two-qubit gates; deepest AX2 circuit: {2*(n_tr-1)} two-qubit gates."
)
print(
"Noiseless backend: |FID| stays flat, peaks razor-sharp (set APODIZE_TROTTER=True to tame ripples)."
)
print(
"Noise-model backend / hardware: |FID| decays on its own and the peaks gain a real linewidth."
)
# Close the optional runtime Batch, if one was opened.
if batch is not None:
batch.close()