Sari la conținutul principal
Acest proiect este în versiune beta. Ai găsit o eroare? Deschide un issue pe GitHub. Ai o întrebare sau o idee? Întreabă în Discussions — ne-ar plăcea să aflăm părerea ta!
Cum este calitatea traducerii pe această pagină?

Rezonanță magnetică nucleară cu Qiskit

Pentru acest modul Qiskit in Classrooms, studenții trebuie să aibă un mediu Python funcțional, cu următoarele pachete instalate:

  • qiskit v2.1 sau mai nou
  • qiskit-ibm-runtime v0.40.1 sau mai nou
  • qiskit-aer v0.17 sau mai nou
  • scipy și matplotlib (pentru ajustare și trasare)
  • pylatexenc (pentru randarea diagramelor de circuit)

Pentru a configura și a instala pachetele de mai sus, consultă ghidul Install Qiskit. Pentru a rula joburi pe computere cuantice reale, studenții trebuie să creeze un cont IBM Quantum® urmând pașii din ghidul Set up your IBM Cloud® account.

Acest modul a fost testat și folosește aproximativ 5 minute de timp QPU. Aceasta este o estimare, iar utilizarea ta reală poate varia.

Introducere​

Când un chimist rulează un experiment RMN, plasează o probă de spini nucleari într-un câmp magnetic puternic, aplică un impuls de radiofrecvență (RF) și măsoară semnalul care revine. Tehnica se bazează pe câteva idei din mecanica cuantică a unei particule cu spin 1/2 într-un câmp magnetic. Spinul are două niveluri de energie separate de o energie care corespunde unei frecvențe Larmor caracteristice. Un impuls rezonant la acea frecvență înclină spinul în planul transversal, unde acesta execută o mișcare de precesie și induce un semnal care apoi se estompează cu doi timpi caracteristici, T1T_1 și T2T_2.

Un qubit supraconductor este o versiune concepută artificial a aceluiași obiect: un sistem cuantic controlabil cu două niveluri. Stările sale ∣0⟩|0\rangle și ∣1⟩|1\rangle sunt analogul spinului în sus și al spinului în jos, poarta pe care o aplicăm pentru a-l roti este analogul impulsului RF, iar el pierde coerența cu propriii T1T_1 și T2T_2. Aceasta nu este doar o comparație vagă; unele dintre primele procesoare cuantice experimentale de la sfârșitul anilor 1990 și începutul anilor 2000 au folosit RMN.

În acest modul vei folosi un computer cuantic real pentru a rula aceleași experimente de caracterizare pe care le efectuează un spectroscopist RMN (sau un inginer cuantic care calibrează hardware-ul):

  • Rabi / nutație — acționarea spinului și observarea rotației sale. Așa calibrezi o rotație de π\pi sau π/2\pi/2.

  • T1T_1 — relaxarea spin-rețea, revenirea unui spin excitat la echilibru.

  • T2T_2 — relaxarea spin-spin (defazare), măsurată cu un experiment Ramsey și refocalizată cu un ecou Hahn.

  • Spectroscopie RMN (deplasare chimică și cuplaj JJ) — simularea unei molecule mici pe mai mulți qubiți și citirea spectrului ei de multipleți pentru a recupera conectivitatea prin regula n+1n+1.

Pentru fiecare concept oferim mai întâi teoria minimă, apoi implementăm experimentul în Qiskit urmând fluxul de lucru în patru pași Qiskit patterns.

Teorie​

Particule cu spin 1/2 într-un câmp magnetic​

Un nucleu cu spin 1/2 are un moment magnetic μ=γℏI\boldsymbol{\mu} = \gamma \hbar \mathbf{I}, unde γ\gamma este raportul giromagnetic. Într-un câmp static B0B_0 de-a lungul axei zz, cele două stări de spin se despart energetic (efectul 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 .

Diferența de energie definește frecvența Larmor (de rezonanță) ω0=γB0\omega_0 = \gamma B_0. Momentul magnetic execută o precesie în jurul lui B0B_0 la această aceeași frecvență.

Pentru a roti spinul aplicăm un câmp RF oscilant la (sau aproape de) ω0\omega_0. În sistemul de referință care se rotește cu frecvența de acționare (sistemul rotitor), o acționare rezonantă rotește vectorul Bloch în jurul unei axe din planul xx-yy. Cât de mult se rotește (unghiul de basculare) este stabilit de intensitatea și durata impulsului. Două alegeri contează cel mai mult: un impuls de 90∘90^\circ înclină un spin +1/2+1/2 în planul transversal, iar un impuls de 180∘180^\circ îl rotește complet la −1/2-1/2.

Traducerea între jargonul RMN și cel al calculului cuantic​

Un qubit este un sistem cu două niveluri, identic matematic cu un spin 1/2 într-un câmp magnetic — așadar, chiar dacă rulăm pe un procesor cuantic și nu pe un nucleu, fizica este aceeași fizică ce stă la baza RMN. Doar vocabularul diferă. Acest tabel te ajută să vorbești atât cu chimiștii fizicieni, cât și cu specialiștii în calcul cuantic.

RMN / spin 1/2Procesor cuantic (qubit)
Spin în sus / spin în jos∣0⟩\ket{0} / ∣1⟩\ket{1}
Diferența Zeeman ΔE=ℏω0\Delta E = \hbar\omega_0Energia de tranziție a qubitului ℏω01\hbar\omega_{01}
Frecvența Larmor ω0\omega_0Frecvența qubitului ω01\omega_{01}
Impuls RF rezonant (unghi de basculare θ\theta)Poartă de rotație pe un singur qubit RX(θ)R_X(\theta)
Impuls de 90∘90^\circ / 180∘180^\circRX(π/2)R_X(\pi/2) / RX(π)R_X(\pi)
Relaxare spin-rețea T1T_1Timpul de relaxare energetică a qubitului T1T_1
Relaxare spin-spin T2T_2Timpul de defazare a qubitului T2T_2
NutațieOscilații Rabi
Declin liber al inducției (FID), ecou HahnSecvențe Ramsey, secvențe de ecou

Tot ce urmează este un exercițiu de măsurare a coloanei din dreapta și de citire a ei ca pe coloana din stânga.

Configurează mediul Qiskit​

Urmăm fluxul de lucru Qiskit patterns pe tot parcursul:

  1. Mapează problema pe un circuit cuantic.

  2. Optimizează circuitul pentru execuție (transpilează pentru backend).

  3. Execută folosind o primitivă IBM Quantum.

  4. Post-procesează și analizează.

Mai întâi încărcăm serviciul runtime și alegem backend-ul real cel mai puțin ocupat. Dacă nu ți-ai salvat încă acreditările, consultă pagina de documentație despre cum să îți salvezi acreditările de autentificare.

# 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

Folosește celula de mai jos pentru a comuta între hardware real și un simulator local pentru întregul notebook. Rularea pe hardware este scopul modulului, dar dacă apar probleme de conectivitate sau de utilizare, simulatorul poate fi o variantă de rezervă. Setează USE_HARDWARE = True pentru a rula pe QPU-ul selectat mai sus.

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

Experiment: oscilații Rabi (nutație)​

Teorie​

Acționează un spin la rezonanță și vectorul său Bloch se rotește constant de la un pol la celălalt — execută o nutație. Dacă aplicăm o rotație cu unghiul de basculare θ\theta în jurul axei xx unui qubit care pornește din ∣0⟩|0\rangle, probabilitatea de a-l găsi în ∣1⟩|1\rangle este:

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

Aceasta este curba Rabi. Primul ei maxim este impulsul π\pi (inversiune completă, impulsul de 180∘180^\circ); un impuls π/2\pi/2 este un sfert de tură (impulsul de 90∘90^\circ care înclină un spin în planul transversal). În RMN, această curbă este modul în care calibrezi impulsurile.

Avertisment experimental

Un experiment Rabi literal variază în timp amplitudinea sau durata acționării. Aceasta necesită control la nivel de impuls, care a fost eliminat în Qiskit v2.0. Aici variem în schimb unghiul de rotație θ\theta al unei porți RX(θ)R_X(\theta). Curba și identificarea π\pi/π/2\pi/2 sunt identice; singura diferență este că alegem direct un unghi, în loc să calibrăm o durată de impuls. Pe hardware cu porți fracționare, RX(θ)R_X(\theta) pentru θ\theta arbitrar rulează nativ.

O vedere pe sfera Bloch​

Animația de mai jos arată nutația: o acționare rezonantă rotește constant vectorul Bloch în jurul axei xx, ducându-l de la ∣0⟩|0\rangle spre ∣1⟩|1\rangle și înapoi. Aceasta este chiar mișcarea pe care o măsoară curba Rabi.

Mapează problema pe un circuit​

Construim un circuit parametrizat, RX(θ)R_X(\theta) urmat de o măsurătoare, și îl evaluăm ulterior la mai multe unghiuri simultan.

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

Optimizare pentru execuție​

Transpilează într-un circuit Instruction Set Architecture (ISA) pentru backend-ul ales.

rabi_isa = pm_q.run(rabi)

Execută​

Variem θ\theta de la 00 la 4π4\pi (două cicluri Rabi complete). Transmiterea unui array de valori ale parametrilor ca un singur Primitive Unified Bloc (PUB) le rulează pe toate într-un singur 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-procesare și analiză​

Extrage P1P_1 la fiecare unghi și compară cu 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

Verifică-ți înțelegerea​

Pe curba pe care ai măsurat-o, P1P_1 atinge prima dată maximul la pulsul π\pi (θ=π\theta = \pi). Presupune acum că un dispozitiv este calibrat greșit, astfel încât un unghi nominal θ\theta de pe axa x produce de fapt o rotație de 1.05 θ1.05\,\theta.

(a) La ce unghi nominal ar apărea acum primul maxim al lui P1P_1? (b) Ar fi un singur puls π\pi o metodă sensibilă de a detecta această eroare de 5%?

Răspuns

(a) Maximul apare acolo unde rotația reală este egală cu π\pi, adică la unghiul nominal θ=π/1.05≈0.95π\theta = \pi/1.05 \approx 0.95\pi — o mică deplasare a vârfului spre stânga.

(b) Nu. Un puls π\pi nominal produce o rotație reală de 1.05π1.05\pi, ceea ce dă P1=sin⁡2(1.05π/2)≈0.994P_1 = \sin^2(1.05\pi/2) \approx 0.994 în loc de 11 — o schimbare prea mică pentru a fi văzută în raport cu zgomotul de măsurare. De aceea, pentru a calibra precis, specialiștii în spectroscopie RMN amplifică eroarea aplicând rotația de mai multe ori (de exemplu metoda 360∘360^\circ sau un tren lung de pulsuri), astfel încât o mică eroare per puls se acumulează într-o abatere mare și măsurabilă. Revenim la această idee în problemele provocatoare.

Experiment: T1T_1 (relaxare spin-rețea)​

Teorie​

Lăsat singur, un spin excitat cedează energie mediului înconjurător („rețeaua”) și revine spre echilibrul termic. Populația stării excitate scade exponențial cu timpul de relaxare spin-rețea T1T_1:

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

Pentru un chimist, T1T_1 stabilește întârzierea de reciclare pe care trebuie s-o aștepți între scanări pentru RMN cantitativ și oferă informații despre rotirea moleculară și dinamică. Pe un qubit este timpul de relaxare a energiei și este unul dintre cele două numere principale folosite pentru a modela efectele de decoerență într-un computer cuantic.

Experimentul. Pregătește ∣1⟩|1\rangle cu o poartă XX (un puls π\pi), așteaptă o întârziere variabilă τ\tau, apoi măsoară. Fracțiunea rămasă în ∣1⟩|1\rangle trasează descreșterea.

O vizualizare pe sfera Bloch​

Relaxarea T1T_1 readuce starea excitată la echilibru, astfel încât vectorul Bloch alunecă de la ∣1⟩|1\rangle înapoi la ∣0⟩|0\rangle de-a lungul axei zz.

Construiește și transpilează o scanare a întârzierii​

Duratele întârzierilor trebuie să fie valori concrete (sunt programate în timpul hardware), așa că construim câte un circuit pentru fiecare întârziere.

# 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

Execută​

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

Analiză — potrivirea exponențialei​

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

Timpul exact T1T_1 variază de la un experiment la altul și de la un backend la altul, dar ar trebui să obții un rezultat de câteva sute de microsecunde.

Verifică-ți înțelegerea​

În RMN cantitativ ți se spune să aștepți aproximativ 5 T15\,T_1 între scanări. Folosind legea descreșterii, ce fracțiune din magnetizarea de echilibru s-a recuperat după 5 T15\,T_1?

Răspuns

După 5 T15\,T_1, abaterea rămasă față de echilibru este e−5≈0.0067e^{-5}\approx 0.0067, adică s-a recuperat aproximativ 99.3%.

Experiment: T2T_2, T2∗T_2^*​

Teorie​

T1T_1 măsoară pierderea de energie. T2T_2 măsoară pierderea coerenței de fază în planul transversal. O putem observa cu un experiment Ramsey: un puls π/2\pi/2 rotește toate spinurile până la ecuator, toate indicând în aceeași direcție. Ele execută precesie Larmor, dar fiecare precesează cu o frecvență ușor diferită, așa că se desfac în evantai de-a lungul ecuatorului, iar semnalul transversal net scade, cu un timp caracteristic de descreștere T2∗T_2^*.

O paranteză pentru cititorii cuantici: Imaginea evantaiului arată o modalitate de a pierde coerența: o dispersie de faze definite care se anulează în medie. Merită să știi că nu este singurul mecanism. Coerența se poate pierde și pentru că faza se încâlcește (entanglement) cu mediul, astfel încât nu mai este deloc o valoare definită pe care o deține sistemul. Nu poți distinge cele două mecanisme doar urmărind descreșterea semnalului, deci imaginea evantaiului este o bună intuiție. Ai grijă să nu o citești ca pe povestea completă sau literală.

Două efecte defazează un spin:

  • Decoerența ireversibilă (sau efectiv ireversibilă), provenită din cuplajul necontrolat cu alte grade de libertate, caracterizată de adevăratul T2T_2. În RMN, aceasta provine în general din mișcarea moleculară care modulează cuplajele dintre spinuri. În calculul cuantic, corespunde cuplajelor nedorite dintre qubiți și mediu.
  • Decalaje de frecvență statice sau lent variabile, reversibile, precum neomogenitatea câmpului, variația de frecvență de la un qubit la altul și erorile de calibrare. Combinate cu adevăratul T2T_2, acestea produc timpul de descreștere aparent, mai scurt, T2∗T_2^*, observat într-un experiment Ramsey.

Deci cum aflăm adevăratul T2T_2 dacă un experiment Ramsey ne spune doar despre T2∗T_2^*? Al doilea experiment, numit ecou Hahn, anulează tot zgomotul de al doilea tip pentru a găsi adevăratul T2T_2. Începe la fel ca un experiment Ramsey, cu un puls π/2\pi/2, apoi spinurile se desfac în evantai ca înainte. După un timp τ\tau, se aplică un puls π\pi. Pulsul inversează acumularea de fază cauzată de decalajele statice de frecvență, astfel încât spinurile se refocalizează după o a doua întârziere de aceeași durată, τ\tau.

T2∗T_2^* și T2T_2 sunt ambele numere importante de cunoscut, atât în chimie, cât și în calculul cuantic. În chimie, T2∗T_2^* stabilește lățimea liniei pentru un experiment RMN standard (vei vedea ce înseamnă aceasta într-o secțiune ulterioară). T2T_2 ne poate spune multe despre molecule, precum rotirea moleculară, iar o schimbare a lui T2T_2 poate semnala că o moleculă s-a legat de ceva mai mare. În calculul cuantic, în general vrem ca atât T2T_2, cât și T2∗T_2^* să fie cât mai lungi, pentru a avea mai mult timp să efectuăm experimente înainte ca decoerența să copleșească sistemul. O întreagă familie de tehnici bazate pe ecoul Hahn prelungește acest timp anulând zgomotul reversibil, astfel încât circuitul să fie limitat în mod ideal de adevăratul T2T_2, nu de T2∗T_2^*. Aceasta se numește „decuplare dinamică”.

Implementare​

Când implementăm experimentele Ramsey și ecou Hahn pe un qubit, citirea este ușor diferită față de cazul RMN. În RMN poți observa direct câmpurile oscilante induse de spinurile care execută precesie la frecvența Larmor în planul transversal. Dar cu un qubit putem măsura doar proiecția lui pe axa ZZ, adică dacă este ∣0⟩\vert 0 \rangle sau ∣1⟩\vert 1 \rangle. Prin urmare, pentru ambele experimente încheiem cu un puls π/2\pi/2 suplimentar, urmat de o măsurătoare. Acest puls π/2\pi/2 traduce informația de fază a qubitului într-o populație.

Mai jos rulăm fiecare experiment pentru a determina T2∗T_2^* și T2T_2.

Ramsey (T2∗T_2^{*}): Două pulsuri π/2\pi/2 cu timpul de întârziere τ\tau​

Primul puls π/2\pi/2 înclină vectorul Bloch până la ecuator, iar următorul traduce faza vectorului Bloch într-o populație. Deoarece faza se schimbă în timp pe măsură ce vectorul Bloch execută precesie, vedem un model sinusoidal în populație în funcție de timpul de precesie τ\tau. Pe măsură ce qubitul pierde coerența de fază, ar trebui să vedem amplitudinea acestei sinusoide scăzând.

Câteva subtilități în plus pentru cititorii cuantici: În experimentele de calcul cuantic, oscilațiile pe care le vedem nu sunt de fapt la frecvența Larmor, care este în domeniul GHz și mult prea rapidă pentru a fi rezolvată. Experimentele cuantice operează în sistemul de referință rotitor: un sistem care se rotește odată cu qubitul la (aproape) frecvența Larmor, astfel încât precesia rapidă este invizibilă, iar vectorul Bloch pare aproape nemișcat. Pentru a vedea franje, setăm în mod deliberat sistemul de referință rotitor ușor decalat față de frecvența Larmor. Vectorul Bloch pare atunci să execute precesie la acel mic decalaj, și obținem un semnal oscilant la frecvența decalajului. Mai jos nu fixăm decalajul la o singură valoare; îl alegem astfel încât aproximativ o duzină de franje să cuprindă fereastra de măsurare (pe care o dimensionăm după timpul de coerență al qubitului), ceea ce păstrează oscilația ușor de eșantionat fără aliasing. Încă o complicație: în Qiskit nu avem controlul la nivel de puls necesar pentru a schimba frecvența pulsului (care este cea care stabilește frecvența sistemului de referință rotitor). În schimb, adăugăm manual un decalaj efectiv, folosindu-l pentru a seta faza celui de-al doilea puls Ramsey. Este puțin artificial, dar rezultatul este același, iar descreșterea pe care o observăm este în continuare adevăratul T2∗T_2^*.

O vizualizare pe sfera Bloch​

Un puls π/2\pi/2 înclină spinurile până la ecuator; fiecare execută apoi precesie cu o viteză ușor diferită și se desfac în evantai, astfel încât vectorul net (roșu) se micșorează pe măsură ce se defazează — această descreștere este 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

Ecou Hahn (T2T_2): Adaugă un puls π\pi de refocalizare la mijloc​

O vizualizare pe sfera Bloch​

Ecoul Hahn: spinurile se desfac în evantai ca înainte, apoi un puls π\pi le răstoarnă, astfel încât precesia lor continuă le refocalizează din nou împreună (vectorul roșu crește la loc), recuperând coerența pierdută din cauza defazării statice.

# 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

Analiză: Potrivește și compară cele două descreșteri​

# 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

Aici vedem atât oscilația Ramsey, cât și descreșterea semnalului ecou Hahn, așa cum ne așteptam. Scalele de timp asociate fiecăreia dintre aceste descreșteri sunt T2∗T_2^* și, respectiv, T2T_2.

Verifică-ți înțelegerea​

De obicei ar trebui să găsești T2≳T2∗T_2 \gtrsim T_2^{*}. Explică de ce într-o singură propoziție și spune ce efect fizic elimină pulsul π\pi al ecoului Hahn, precum și ce nu poate elimina.

Răspuns

T2∗T_2^{*} include atât defazarea ireversibilă, cât și dispersia statică reversibilă, așa că scade mai repede; pulsul π\pi al ecoului refocalizează defazarea statică, reversibilă (neomogenitatea câmpului / decalaje de frecvență fixe), lăsând doar defazarea spin-spin ireversibilă, care este adevăratul T2T_2. Ecoul nu poate anula fluctuațiile care se schimbă între cele două jumătăți ale secvenței.

Spectroscopia RMN a unei molecule: deplasarea chimică și cuplajul JJ​

De la un spin la o moleculă​

Experimentele de până acum au caracterizat un singur spin izolat. RMN-ul chimic real citește o moleculă (multe spinuri cuplate simultan) și folosește spectrul rezultat pentru a deduce trăsături ale structurii moleculei. Acesta ne poate spune despre două efecte:

  • Deplasarea chimică. Fiecare nucleu distinct chimic execută precesie cu o frecvență ușor diferită νi\nu_i, deoarece electronii săi locali ecranează câmpul aplicat în măsuri diferite. Pozițiile vârfurilor identifică, prin urmare, mediile chimice (de exemplu un proton CH3_3 față de unul aromatic).

  • Cuplajul scalar JJ. O interacțiune prin legături între două spinuri împarte fiecare vârf într-un multiplet. Un nucleu cu nn vecini echivalenți este împărțit în n+1n+1 linii cu intensități binomiale, regula n+1n+1. Modelul de despicare dezvăluie conectivitatea (care nuclee sunt legate), iar distanța dintre linii este constanta de cuplaj JJ.

Experimentul​

Pentru a modela aceasta în computerul nostru cuantic, trebuie să implementăm un hamiltonian de spin pe qubiți. În limita cuplajului slab și a câmpului intens (diferențe de deplasare chimică ≫\gg cuplaje), părțile XXXX și YYYY ale cuplajului se anulează în medie, lăsând doar ZZZZ. Astfel, hamiltonianul este (în unități de frecvență unghiulară, ℏ=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}}.

Fiecare nucleu este un qubit: deplasarea chimică este o rotație ZZ pe un singur qubit, iar cuplajul este o interacțiune ZZZZ pe doi qubiți.

Experimentul decurge astfel. Un puls π/2\pi/2 înclină fiecare spin în planul transversal, iar spinurile execută precesie și se cuplează timp de tt. Înregistrăm magnetizarea transversală complexă s(t)=⟨X⟩+i⟨Y⟩s(t) = \langle X\rangle + i\langle Y\rangle însumată peste toate spinurile. Ca în experimentul Ramsey de mai sus, această magnetizare transversală trasează o sinusoidă descrescătoare în funcție de timpul de precesie tt. Când luăm transformata Fourier a semnalului, obținem un spectru cu vârfuri la deplasarea chimică νi\nu_i, despicate de cuplajul scalar JijJ_{ij}.

notă

Simulăm evoluția moleculei transformând timpul tt în unghiuri de rotație: un tt mai mare înseamnă rotații ZZ și ZZZZ mai mari, nu un circuit mai lung sau mai adânc. Așadar, deși starea simulată avansează cu tt (asta face ca descreșterea liberă prin inducție (FID) să oscileze), qubiții fizici nu stau niciodată inactivi mai mult timp și nu există decoerență dependentă de tt. FID-ul simulat nu scade deci niciodată de la sine. Pentru un FID ideal infinit de lung, liniile spectrale ar fi infinit de înguste; fereastra noastră finită de eșantionare le dă în schimb o rezoluție finită și poate introduce scurgere spectrală. Liniile RMN reale au o lățime finită stabilită de T2∗T_2^*. O adăugăm la loc înmulțind FID-ul cu o fereastră e−t/T2∗e^{-t/T_2^{*}} înainte de transformata Fourier. Aceasta se numește apodizare cu lărgire de linie, pe care spectroscopiștii o aplică în procesare pentru a stabili lățimea liniei (sacrificând rezoluția pentru raportul semnal-zgomot). (O celulă opțională din Anexa acestui modul arată o alternativă Trotterizată, în care descreșterea provine în schimb din decoerența reală, care crește cu adâncimea circuitului.)

Sistemul AX​

Să începem cu așa-numitul sistem AX, o moleculă formată din două spinuri cuplate, distincte chimic. Etichetele A și X denotă cele două medii de spin distincte chimic. Literele sunt arbitrare, dar faptul că sunt departe în alfabet ilustrează deosebirea lor chimică.

Mapare și optimizare​

Scriem o funcție care, date fiind decalajele de deplasare chimică νi\nu_i și cuplajele JijJ_{ij}, returnează un circuit parametrizat de pregătire a stării. Pulsul π/2\pi/2 înclină spinurile până la ecuator, apoi rotațiile ZZ simulează evoluția stării sub termenii de deplasare chimică și de cuplaj JJ. Fiecare νi\nu_i este un mic decalaj de precesie în Hz față de o referință aleasă, sau purtătoarea sistemului de referință rotitor. Aici îl setăm manual; într-o moleculă reală, ecranarea electronică schimbă câmpul magnetic local și deplasează rezonanța fiecărui nucleu față de acea referință. Aceasta este modelată de porțile RZR_Z pe un singur qubit, câte una pentru fiecare spin, iar fiecare νi\nu_i fixează unde se află vârful acelui spin în spectru. Poarta ZZZZ pe doi qubiți aplică cuplajul JijJ_{ij}, care împarte fiecare vârf într-un multiplet și stabilește distanța dintre liniile sale.

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

Execută și post-procesează​

Evaluăm circuitul pe o grilă de timpi de evoluție, iar Estimator returnează ⟨X⟩\langle X\rangle și ⟨Y⟩\langle Y\rangle pentru fiecare. Le combinăm în FID-ul complex ⟨X⟩+i⟨Y⟩\langle X\rangle + i\langle Y\rangle, aplicăm fereastra de apodizare și aplicăm transformata Fourier rezultatului pentru a obține spectrul.

# --- 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]

Extensie: un sistem AX2_2 și regula n+1n+1​

Adăugăm acum un al doilea vecin echivalent. Într-un sistem AX2_2, un spin A se cuplează, cu același JJ, cu doi spini X echivalenți. (Reține că X este doar o etichetă pentru cele două spinuri echivalente; nu are legătură cu operatorii Pauli XX. Fiecare cuplaj de mai jos este tot ZZZZ.) Spinul A vede atunci patru configurații ale vecinilor (↑↑, ↑↓, ↓↑, ↓↓)(\uparrow\uparrow,\ \uparrow\downarrow,\ \downarrow\uparrow,\ \downarrow\downarrow); cele două mixte coincid, deci A este despicat într-un triplet 1:2:1 (n=2⇒n+1=3n=2 \Rightarrow n+1 = 3 linii). Fiecare spin X vede doar A și rămâne dublet. Spinurile echivalente nu se despică unele pe altele, deci nu includem niciun cuplaj X–X.

O moleculă reală cu acest model este 1,1,2-tricloroetanul (Cl2_2CH–CH2_2Cl). Când țintim spinurile hidrogenului (cea mai comună formă de RMN), singurul proton de hidrogen din CH joacă rolul lui A, iar cei doi protoni echivalenți CH2_2 sunt cei doi spini X. Acum numără pur și simplu vecinii: protonul CH are doi vecini (perechea CH2_2), deci semnalul său se despică într-un triplet 1:2:1 (n=2n=2); fiecare proton CH2_2 are doar un vecin (CH-ul), deci semnalul CH2_2 este un dublet 1:1 (n=1n=1). Observă că grupul cu mai puțini protoni (CH) arată mai multe linii; numărul de linii reflectă câți vecini are un semnal, nu câți protoni îl împart.

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

Mai jos simulăm un sistem AX2_2 cu trei qubiți.

# 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)],
)

Verifică-ți înțelegerea​

Citește fiecare întrebare, gândește-te la răspuns, apoi dă clic pe triunghi pentru a afișa soluția.

În spectrul AX, cele două dublete sunt separate (centru la centru) cu 12 Hz, iar cele două linii din fiecare dublet sunt la 2 Hz distanță. Care număr este diferența de deplasare chimică și care este constanta de cuplaj JJ? Dacă ai trece la un spectrometru cu un câmp magnetic de două ori mai mare, care dintre ele s-ar schimba?

Răspuns

Separarea de 12 Hz de la centru la centru este diferența de deplasare chimică (ν1−ν2\nu_1-\nu_2); distanța de 2 Hz dintre linii este constanta de cuplaj JJ. Deplasările chimice provin din ecranarea lui B0B_0, deci scalează cu câmpul. La un câmp dublu, separarea se dublează la 24 Hz. Cuplajul JJ este o proprietate moleculară prin legături și nu depinde de câmp, deci rămâne 2 Hz. Chimiștii deosebesc deplasarea de cuplaj urmărind cum scalează o despicare cu câmpul.

Folosind regula n+1n+1, prezice multipletul A dacă s-ar adăuga un al treilea spin X echivalent (un sistem AX3_3) și explică de ce cei doi spini X echivalenți din AX2_2 nu se despică unul pe altul.

Răspuns

Cu n=3n=3 vecini echivalenți, A devine un cvartet 1:3:3:1. Spinurile echivalente nu se despică unele pe altele deoarece cuplajul lor deplasează nivelurile de energie relevante toate cu aceeași cantitate, astfel încât diferențele dintre aceste niveluri — frecvențele pe care le vedem — nu se schimbă.

Concluzie​

Am folosit un computer cuantic real pentru a explora cum execută precesie și se decoerează spinurile nucleare într-un experiment RMN și cum să interpretăm spectrele rezultate. Merită să facem un pas înapoi pentru a revedea de ce a fost posibil acest lucru și de ce este mai mult decât o curiozitate.

Un qubit și un nucleu cu spin 1/2 sunt, matematic, același obiect: sisteme cuantice cu două niveluri aflate pe sfera Bloch, rotite de pulsuri rezonante, executând precesie în jurul unei axe și pierzând coerența în favoarea mediului. Întregul vocabular al acestui modul (frecvența Larmor, sistemul de referință rotitor, T1T_1, T2T_2 și T2∗T_2^*, franjele Ramsey, ecoul Hahn) aparține în egală măsură RMN-ului și calculului cuantic. Ceea ce un chimist numește deplasare chimică, un inginer cuantic numește rotație ZZ pe un singur qubit; ceea ce un chimist numește cuplaj JJ, un inginer cuantic numește interacțiune ZZZZ. Cele două domenii au descris aceeași fizică în cuvinte diferite.

Această legătură este istorică, nu doar conceptuală: RMN-ul a fost una dintre primele platforme care au rulat algoritmi cuantici reali. Până în 2001, o moleculă în soluție executase algoritmul lui Shor pentru a factoriza 15, fiecare spin nuclear servind drept qubit. RMN-ul a oferit calculului cuantic și o mare parte din setul său de instrumente de control. Ecoul de spin, trenurile de pulsuri CPMG, pulsurile compuse și decuplarea dinamică au fost toate inventate de spectroscopiști cu zeci de ani înainte ca qubiții să aibă nevoie de ele pentru a combate decoerența. Există o simetrie satisfăcătoare în ceea ce tocmai am făcut: domeniul care a dat naștere primelor computere cuantice este acum ceva ce un computer cuantic poate simula.

Recapitulare și întrebări​

Concepte esențiale​

  • Un qubit este un sistem cu două niveluri creat artificial, echivalent cu un spin 1/2; ∣0⟩/∣1⟩|0\rangle/|1\rangle, frecvența, porțile, T1T_1 și T2T_2 sunt analoagele directe ale mărimilor din RMN.

  • Un puls rezonant rotește vectorul Bloch; unghiul de basculare stabilește rotația, iar curba Rabi/de nutație P1=sin⁡2(θ/2)P_1=\sin^2(\theta/2) identifică rotațiile π\pi și π/2\pi/2.

  • T1T_1 este relaxarea energiei (spin-rețea); T2T_2 este relaxarea fazei (spin-spin); T2∗T_2^{*} include în plus defazarea statică reversibilă pe care un ecou Hahn o refocalizează.

  • O moleculă este un set de spinuri cuplate: deplasarea chimică stabilește poziția fiecărui vârf, iar cuplajul JJ împarte vârfurile în multipleți după regula n+1n+1, care împreună codifică mediul chimic și conectivitatea.

Adevărat / fals​

  1. A/F Frecvența Larmor este diferența de energie dintre cele două stări de spin împărțită la ℏ\hbar.

  2. A/F Un puls de 90∘90^\circ (π/2\pi/2) inversează complet un spin aflat la echilibru.

  3. A/F T2∗T_2^{*} este în general mai lung decât T2T_2.

  4. A/F Ecoul Hahn refocalizează defazarea spin-spin ireversibilă.

  5. A/F Într-un sistem AX2_2, nucleul A apare ca un triplet 1:2:1.

  6. A/F Constanta de cuplaj JJ se dublează când dublezi câmpul magnetic al spectrometrului.

  7. A/F Eliminarea Qiskit Pulse face imposibilă măsurarea lui T1T_1 pe hardware-ul actual.

Răspuns scurt​

  1. Explică în două propoziții de ce un singur puls π\pi este o metodă slabă de a detecta o eroare de 5%5\% a unghiului de basculare și cum remediază aceasta înlănțuirea pulsurilor.

  2. O probă arată linii RMN late. În termeni de T2T_2, ce îți spune aceasta și care este mărimea analogă la un qubit?

  3. Dă analogul la qubit pentru fiecare: (a) întârzierea de reciclare, (b) pulsul de 90∘90^\circ, (c) deplasarea chimică.

  4. Folosind regula n+1n+1, explică de ce o grupare etil (CH3_3CH2_2–) arată un triplet 1:2:1 și un cvartet 1:3:3:1 și identifică ce grup produce ce multiplet.

Probleme provocatoare​

  1. Tren Carr-Purcell. Modifică ecoul Hahn pentru a insera nn pulsuri π\pi echidistante (un tren în stil CPMG) și măsoară T2T_2 în funcție de nn. Discută de ce mai multe pulsuri de refocalizare pot prelungi T2T_2 aparent.

  2. Amplificarea erorii de puls. Construiește un circuit care aplică RX(π)R_X(\pi) de  n\,n ori asupra lui ∣0⟩|0\rangle și trasează P1P_1 în funcție de nn pentru un unghi setat greșit în mod deliberat, RX(1.05π)R_X(1.05\pi). Arată cum eroarea devine vizibilă pe măsură ce nn crește. Indiciu: pune o barieră după fiecare rotație; altfel transpiler-ul poate combina rotațiile repetate într-o singură poartă echivalentă.

  3. Gruparea etil. Folosind fid_circuit, construiește spectrul de protoni al unui fragment etil (CH3_3–CH2_2): cinci qubiți, cu cei trei protoni CH3_3 având o singură deplasare chimică și cei doi protoni CH2_2 alta, și fiecare pereche CH3_3–CH2_2 cuplată cu același JJ (fără cuplaj în interiorul unui grup echivalent). Prezice modelul cu regula n+1n+1, apoi simulează-l; ar trebui să regăsești un cvartet 1:3:3:1 (CH2_2) și un triplet 1:2:1 (CH3_3).

Anexă (opțional): evoluție Trotterizată — lasă decoerența să stabilească lățimea liniei​

În secțiunea Spectroscopia RMN a unei molecule, evoluția e−iHte^{-iHt} este aplicată într-un singur strat a cărui adâncime nu crește cu tt, deci FID-ul nu scade de la sine și impunem lățimea liniei cu fereastra de apodizare e−t/T2e^{-t/T_2}. Această celulă opțională adoptă abordarea opusă. Ea împarte evoluția în pp pași Trotter și pune o barieră după fiecare pas (astfel încât transpiler-ul să nu-i poată îmbina înapoi într-un singur strat). Punctul de timp kk folosește kk pași, deci adâncimea circuitului crește cu timpul.

Deoarece toți termenii din HH comută, aceasta este exactă: pe un simulator fără zgomot dă aceleași vârfuri înguste. Dar pe un backend cu model de zgomot sau pe hardware real, eroarea acumulată a porților face FID-ul să scadă de la sine, iar această descreștere este cea care stabilește lățimea liniei. Aceasta este ideea T2↔T_2\leftrightarrowlățimea liniei din secțiunea experimentul T2T_2, T2∗T_2^*, apărând acum din decoerență reală în loc de o fereastră de procesare. Rulăm în acest mod atât sistemul AX (două dublete), cât și sistemul AX2_2 (un triplet 1:2:1 plus un dublet).

Două note practice de luat în considerare. În primul rând, costul este adâncimea: cel mai adânc circuit AX folosește ∼ ⁣47\sim\!47 de porți pe doi qubiți, iar versiunea AX2_2 aproximativ dublu, deci ambele sunt mult mai zgomotoase și mai costisitoare decât versiunea exactă. În al doilea rând, fiecare sistem de spin este tot trimis ca un singur job, iar fiecare punct de timp reutilizează aceeași dispunere a qubiților fizici selectată în secțiunea de spectroscopie. Dacă USE_BATCH=True, aceste joburi folosesc și Batch-ul opțional valabil pentru tot notebook-ul.

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