Přeskočit na hlavní obsah
Tento projekt je v betě. Našel jsi chybu? Otevřít issue na GitHubu. Máš otázku nebo nápad? Zeptej se v Discussions — rádi si poslechneme tvou zpětnou vazbu!
Jaká je kvalita překladu na této stránce?

Nukleární magnetická rezonance s Qiskit

Pro tento modul Qiskit in Classrooms musí mít studenti funkční prostředí Pythonu s nainstalovanými následujícími balíčky:

  • qiskit v2.1 nebo novější
  • qiskit-ibm-runtime v0.40.1 nebo novější
  • qiskit-aer v0.17 nebo novější
  • scipy a matplotlib (pro fitování a vykreslování)
  • pylatexenc (pro vykreslování diagramů obvodů)

Informace o nastavení a instalaci výše uvedených balíčků najdeš v průvodci Install Qiskit. Chcete-li spouštět úlohy na skutečných kvantových počítačích, musí si studenti založit účet u IBM Quantum® podle kroků v průvodci Set up your IBM Cloud® account.

Tento modul byl otestován a spotřebuje přibližně 5 minut času QPU. Jde o odhad a skutečná spotřeba se může lišit.

Úvod​

Když chemik provádí NMR experiment, umístí vzorek jaderných spinů do silného magnetického pole, aplikuje radiofrekvenční (RF) puls a změří signál, který se vrátí. Tato technika stojí na několika myšlenkách z kvantové mechaniky částice se spinem 1/2 v magnetickém poli. Spin má dvě energetické hladiny oddělené energií, která odpovídá charakteristické Larmorově frekvenci. Rezonanční puls na této frekvenci překlopí spin do transverzální roviny, kde precesuje a indukuje signál, který pak vyhasíná se dvěma charakteristickými časy, T1T_1 a T2T_2.

Supravodivý qubit je uměle vytvořená verze stejného objektu: ovladatelný kvantový systém o dvou úrovních. Jeho stavy ∣0⟩|0\rangle a ∣1⟩|1\rangle jsou analogií spinu nahoru a dolů, hradlo, kterým ho otáčíme, je analogií RF pulsu a ztrácí koherenci s vlastními T1T_1 a T2T_2. Nejde jen o volné srovnání; některé z prvních experimentálních kvantových procesorů na konci 90. a na počátku 2000. let používaly NMR.

V tomto modulu použiješ skutečný kvantový počítač ke spuštění týchž charakterizačních experimentů, jaké provádí NMR spektroskopik (nebo kvantový inženýr kalibrující hardware):

  • Rabiho oscilace / nutace — buzení spinu a sledování jeho rotace. Takto kalibruješ rotaci o π\pi nebo π/2\pi/2.

  • T1T_1 — spin-mřížková relaxace, návrat excitovaného spinu zpět do rovnováhy.

  • T2T_2 — spin-spinová relaxace (defázování), měřená Ramseyho experimentem a refokusovaná Hahnovým echem.

  • NMR spektroskopie (chemický posun a JJ-vazba) — simulace malé molekuly na několika qubitech a čtení jejího multipletového spektra pro určení konektivity pomocí pravidla n+1n+1.

Pro každý koncept nejprve uvedeme minimální teorii a poté experiment implementujeme v Qiskit podle čtyřkrokového workflow Qiskit patterns.

Teorie​

Částice se spinem 1/2 v magnetickém poli​

Jádro se spinem 1/2 má magnetický moment μ=γℏI\boldsymbol{\mu} = \gamma \hbar \mathbf{I}, kde γ\gamma je gyromagnetický poměr. Ve statickém poli B0B_0 ve směru zz se dva spinové stavy energeticky rozštěpí (Zeemanův jev):

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 .

Energetická mezera určuje Larmorovu (rezonanční) frekvenci ω0=γB0\omega_0 = \gamma B_0. Magnetický moment precesuje kolem B0B_0 právě touto frekvencí.

K otočení spinu aplikujeme oscilující RF pole na frekvenci ω0\omega_0 (nebo blízko ní). V soustavě rotující budicí frekvencí (rotující soustava) rezonanční buzení otáčí Blochův vektor kolem osy v rovině xx-yy. To, jak daleko se otočí (úhel překlopení), je dáno intenzitou a délkou pulsu. Nejvíce záleží na dvou volbách: puls 90∘90^\circ překlopí spin +1/2+1/2 do transverzální roviny a puls 180∘180^\circ ho otočí zcela na −1/2-1/2.

Překlad mezi žargonem NMR a kvantových počítačů​

Qubit je dvouúrovňový systém, matematicky identický se spinem 1/2 v magnetickém poli — takže i když pracujeme na kvantovém procesoru, a ne na jádře, fyzika je táž, která stojí za NMR. Liší se pouze slovník. Tato tabulka ti pomůže domluvit se s fyzikálními chemiky i specialisty na kvantové počítání.

NMR / spin 1/2Kvantový procesor (qubit)
Spin nahoru / spin dolů∣0⟩\ket{0} / ∣1⟩\ket{1}
Zeemanova mezera ΔE=ℏω0\Delta E = \hbar\omega_0Přechodová energie qubitu ℏω01\hbar\omega_{01}
Larmorova frekvence ω0\omega_0Frekvence qubitu ω01\omega_{01}
Rezonanční RF puls (úhel překlopení θ\theta)Jednoqubitové rotační hradlo RX(θ)R_X(\theta)
Puls 90∘90^\circ / 180∘180^\circRX(π/2)R_X(\pi/2) / RX(π)R_X(\pi)
Spin-mřížková relaxace T1T_1Doba energetické relaxace qubitu T1T_1
Spin-spinová relaxace T2T_2Doba defázování qubitu T2T_2
NutaceRabiho oscilace
Signál volné precese (FID), Hahnovo echoRamseyho a echo sekvence

Vše níže je cvičení v měření pravého sloupce a jeho čtení jako levého sloupce.

Nastavení prostředí Qiskit​

Po celou dobu postupujeme podle workflow Qiskit patterns:

  1. Mapuj problém na kvantový obvod.

  2. Optimalizuj obvod pro provedení (transpilace pro backend).

  3. Proveď pomocí primitiva IBM Quantum.

  4. Následně zpracuj a analyzuj.

Nejprve načteme službu runtime a vybereme nejméně vytížený skutečný backend. Pokud jsi dosud neuložil své přihlašovací údaje, přečti si stránku dokumentace o tom, jak uložit přihlašovací údaje.

# 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

Pomocí buňky níže můžeš v celém notebooku přepínat mezi skutečným hardwarem a lokálním simulátorem. Smyslem modulu je běh na hardwaru, ale při problémech s konektivitou nebo využitím může simulátor posloužit jako záloha. Nastav USE_HARDWARE = True, aby se úloha spustila na QPU vybraném výše.

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: Rabiho oscilace (nutace)​

Teorie​

Budíš-li spin na rezonanci, jeho Blochův vektor se plynule otáčí od pólu k pólu — nutuje. Aplikujeme-li na qubit začínající ve stavu ∣0⟩|0\rangle rotaci o úhel překlopení θ\theta kolem osy xx, je pravděpodobnost nalezení ve stavu ∣1⟩|1\rangle:

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

Toto je Rabiho křivka. Její první maximum je puls π\pi (úplná inverze, puls 180∘180^\circ); puls π/2\pi/2 je čtvrtotáčka (puls 90∘90^\circ, který překlopí spin do transverzální roviny). V NMR takto kalibruješ své pulsy.

Experimentální upozornění

Skutečný Rabiho experiment mění amplitudu nebo délku budicího pulzu v čase. To vyžaduje řízení na úrovni pulzů, které bylo v Qiskit v2.0 odstraněno. Zde místo toho měníme úhel rotace θ\theta hradla RX(θ)R_X(\theta). Křivka i určení π\pi/π/2\pi/2 pulzů jsou totožné; jediný rozdíl je v tom, že úhel volíme přímo, místo abychom kalibrovali délku pulzu. Na hardwaru s frakčními hradly běží RX(θ)R_X(\theta) pro libovolné θ\theta nativně.

Pohled na Blochovu sféru​

Animace níže ukazuje nutaci: rezonanční buzení stabilně otáčí Blochův vektor kolem osy xx a vede ho z ∣0⟩|0\rangle dolů směrem k ∣1⟩|1\rangle a zase zpět. Přesně tento pohyb Rabiho křivka měří.

Převod problému na obvod​

Sestavíme jeden parametrizovaný obvod, RX(θ)R_X(\theta) následované měřením, a později ho vyhodnotíme pro mnoho úhlů najednou.

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

Optimalizace pro spuštění​

Transpiluj do obvodu v architektuře instrukční sady (ISA) pro zvolený backend.

rabi_isa = pm_q.run(rabi)

Spuštění​

Měníme θ\theta od 00 do 4π4\pi (dva celé Rabiho cykly). Předáním pole hodnot parametrů jako jediného Primitive Unified Bloc (PUB) se všechny spustí v jedné úloze.

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

Následné zpracování a analýza​

Získej P1P_1 pro každý úhel a porovnej s 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

Zkontroluj si, že rozumíš​

Na změřené křivce dosáhne P1P_1 poprvé maxima u π\pi pulzu (θ=π\theta = \pi). Nyní předpokládej, že je zařízení špatně zkalibrované, takže nominální úhel θ\theta na ose x ve skutečnosti vytvoří rotaci o 1.05 θ1.05\,\theta.

(a) Při jakém nominálním úhlu se nyní objeví první maximum P1P_1? (b) Byl by jediný π\pi pulz citlivým způsobem, jak tuto 5% chybu odhalit?

Odpověď

(a) Maximum se objeví tam, kde se skutečná rotace rovná π\pi, tedy při nominálním úhlu θ=π/1.05≈0.95π\theta = \pi/1.05 \approx 0.95\pi — malý posun vrcholu doleva.

(b) Ne. Nominální π\pi pulz vytvoří skutečnou rotaci 1.05π1.05\pi, což dává P1=sin⁡2(1.05π/2)≈0.994P_1 = \sin^2(1.05\pi/2) \approx 0.994 místo 11 — změna příliš malá na to, aby byla vidět na pozadí šumu měření. Proto se při přesné kalibraci odborníci na NMR spektroskopii snaží chybu zesílit tím, že rotaci aplikují mnohokrát (například metodou 360∘360^\circ nebo dlouhou sérií pulzů), takže se malá chyba na pulz nasčítá do velké, měřitelné odchylky. K této myšlence se vrátíme v úlohách navíc.

Experiment: T1T_1 (spin-mřížková relaxace)​

Teorie​

Ponechán sám sobě předá excitovaný spin energii svému okolí („mřížce“) a relaxuje zpět k tepelné rovnováze. Populace excitovaného stavu exponenciálně klesá s dobou spin-mřížkové relaxace T1T_1:

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

Pro chemika určuje T1T_1 čekací dobu mezi skeny, kterou musíš u kvantitativní NMR dodržet, a vypovídá o molekulárním překlápění a dynamice. U qubitu je to doba energetické relaxace a je to jedno ze dvou hlavních čísel používaných k modelování efektů dekoherence v kvantovém počítači.

Experiment. Připrav ∣1⟩|1\rangle pomocí hradla XX (π\pi pulz), počkej proměnnou dobu τ\tau a poté měř. Podíl, který zůstal v ∣1⟩|1\rangle, vykreslí průběh rozpadu.

Pohled na Blochovu sféru​

T1T_1 relaxace vrací excitovaný stav do rovnováhy, takže Blochův vektor klouže z ∣1⟩|1\rangle zpět nahoru do ∣0⟩|0\rangle podél osy zz.

Sestavení a transpilace skenu zpoždění​

Doby zpoždění musí být konkrétní hodnoty (plánují se v hardwarovém čase), proto pro každé zpoždění sestavíme jeden obvod.

# 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

Spuštění​

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

Analýza — proložení exponenciály​

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

Přesná hodnota T1T_1 se liší experiment od experimentu a backend od backendu, ale měl bys vidět výsledek v řádu několika stovek mikrosekund.

Zkontroluj si, že rozumíš​

U kvantitativní NMR se doporučuje mezi skeny čekat zhruba 5 T15\,T_1. Jaký podíl rovnovážné magnetizace se podle zákona rozpadu obnoví po 5 T15\,T_1?

Odpověď

Po 5 T15\,T_1 je zbývající odchylka od rovnováhy e−5≈0.0067e^{-5}\approx 0.0067, tedy je obnoveno zhruba 99,3 %.

Experiment: T2T_2, T2∗T_2^*​

Teorie​

T1T_1 měří ztrátu energie. T2T_2 měří ztrátu fázové koherence v příčné rovině. Můžeme ji pozorovat pomocí Ramseyho experimentu: pulz π/2\pi/2 otočí všechny spiny dolů na rovník, všechny míří stejným směrem. Vykonávají Larmorovu precesi, ale každý precesuje s mírně odlišnou frekvencí, takže se podél rovníku rozvějířují a čistý příčný signál klesá s charakteristickou dobou rozpadu T2∗T_2^*.

Poznámka pro čtenáře z oblasti kvantových technologií: Obraz rozvějiřování ukazuje jeden způsob, jak koherenci ztratit: rozptyl určitých fází, které se průměrují. Je dobré vědět, že to není jediný mechanismus. Koherence se může ztratit i proto, že se fáze provázala s prostředím, takže už to není určitá hodnota, kterou systém vůbec má. Oba mechanismy nelze od sebe rozlišit pouhým pozorováním rozpadu signálu, takže obraz rozvějiřování je dobrá intuice. Dej si ale pozor, abys ho nebral jako úplný nebo doslovný popis.

Spin defázují dva efekty:

  • Nevratná (nebo prakticky nevratná) dekoherence vznikající z nekontrolované vazby na další stupně volnosti, charakterizovaná skutečným T2T_2. V NMR obvykle pochází z molekulárního pohybu, který moduluje vazby mezi spiny. Ve kvantovém počítání odpovídá nežádoucím vazbám mezi qubity a prostředím.
  • Statické nebo pomalu proměnné, vratné frekvenční odchylky, například nehomogenita pole, rozdíly frekvencí mezi qubity a chyby kalibrace. V kombinaci se skutečným T2T_2 vytvářejí kratší zdánlivou dobu rozpadu T2∗T_2^* pozorovanou v Ramseyho experimentu.

Jak tedy získat skutečné T2T_2, když Ramseyho experiment prozradí jen T2∗T_2^*? Druhý experiment, nazývaný Hahnovo echo, odstraní veškerý šum druhého typu, aby našel skutečné T2T_2. Začíná stejně jako Ramseyho experiment pulzem π/2\pi/2, poté se spiny rozvějiří jako dříve. Po nějaké době τ\tau se aplikuje pulz π\pi. Pulz zvrátí akumulaci fáze způsobenou statickými frekvenčními odchylkami, takže se spiny po druhém zpoždění stejné délky τ\tau znovu zaostří.

T2∗T_2^* a T2T_2 jsou důležitá čísla, která je třeba znát v chemii i v kvantovém počítání. V chemii určuje T2∗T_2^* šířku čáry ve standardním NMR experimentu (co to znamená, uvidíš v pozdější sekci). T2T_2 může o molekulách prozradit mnoho, například o molekulárním překlápění, a změna T2T_2 může signalizovat, že se molekula navázala na něco většího. V kvantovém počítání obecně chceme, aby T2T_2 i T2∗T_2^* byly co nejdelší, abychom měli víc času na experimenty, než dekoherence systém přemůže. Celá rodina technik založených na Hahnově echu tuto dobu prodlužuje potlačením vratného šumu, takže je obvod v ideálním případě omezen skutečným T2T_2, nikoli T2∗T_2^*. Tomu se říká „dynamické oddělení“.

Implementace​

Když Ramseyho a Hahnův echo experiment implementujeme na qubitu, je odečet trochu jiný než u NMR. V NMR lze přímo pozorovat oscilující pole indukovaná spiny, které v příčné rovině precesují Larmorovou frekvencí. U qubitu ale můžeme měřit jen jeho průmět na osu ZZ, tedy zda je v ∣0⟩\vert 0 \rangle nebo ∣1⟩\vert 1 \rangle. Proto u obou experimentů na konec přidáme další pulz π/2\pi/2 následovaný měřením. Tento pulz π/2\pi/2 převede informaci o fázi qubitu na populaci.

Níže spustíme každý experiment, abychom určili T2∗T_2^* a T2T_2.

Ramsey (T2∗T_2^{*}): dva pulzy π/2\pi/2 se zpožděním τ\tau​

První pulz π/2\pi/2 sklopí Blochův vektor na rovník a další převede fázi Blochova vektoru na populaci. Protože se fáze s časem mění, jak Blochův vektor precesuje, vidíme v populaci sinusový průběh jako funkci doby precese τ\tau. Jak qubit ztrácí fázovou koherenci, měli bychom vidět klesající amplitudu této sinusovky.

Několik dalších jemností pro čtenáře z oblasti kvantových technologií: V experimentech s kvantovým počítáním oscilace, které vidíme, ve skutečnosti nejsou na Larmorově frekvenci, která je v řádu GHz a je příliš rychlá, než abychom ji rozlišili. Kvantové experimenty pracují v rotující soustavě: soustavě, která se otáčí spolu s qubitem na (téměř) Larmorově frekvenci, takže rychlá precese není vidět a Blochův vektor vypadá téměř nehybně. Abychom viděli proužky, záměrně nastavíme rotující soustavu mírně rozladěnou od Larmorovy frekvence. Blochův vektor pak vypadá, že precesuje tímto malým rozladěním, a získáme oscilující signál na frekvenci rozladění. Níže rozladění nefixujeme na jedné hodnotě; zvolíme ho tak, aby se do měřicího okna (jehož velikost přizpůsobíme době koherence qubitu) vešlo zhruba tucet proužků, díky čemuž lze oscilaci snadno vzorkovat bez aliasingu. Ještě jedna zvláštnost: v Qiskit nemáme řízení na úrovni pulzů potřebné ke změně frekvence pulzu (která určuje frekvenci rotující soustavy). Místo toho přidáme efektivní rozladění ručně a použijeme ho k nastavení fáze druhého Ramseyho pulzu. Je to trochu vykonstruované, ale výsledek je stejný a pozorovaný rozpad je stále skutečné T2∗T_2^*.

Pohled na Blochovu sféru​

Pulz π/2\pi/2 sklopí spiny na rovník; každý pak precesuje mírně odlišnou rychlostí a rozvějiří se, takže čistý (červený) vektor se zmenšuje, jak se defázují — tento rozpad je 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

Hahnovo echo (T2T_2): přidání refokusačního pulzu π\pi uprostřed​

Pohled na Blochovu sféru​

Hahnovo echo: spiny se rozvějiří jako dříve, pak je pulz π\pi překlopí, takže jejich další precese je refokusuje zpět dohromady (červený vektor opět roste) a obnoví koherenci ztracenou statickým defázováním.

# 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

Analýza: proložení a porovnání obou rozpadů​

# 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

Zde vidíme jak Ramseyho oscilaci, tak rozpad signálu Hahnova echa, jak se očekává. Příslušné časové škály těchto rozpadů jsou T2∗T_2^*, respektive T2T_2.

Zkontroluj si, že rozumíš​

Typicky bys měl zjistit, že T2≳T2∗T_2 \gtrsim T_2^{*}. Vysvětli jednou větou proč a řekni, jaký fyzikální efekt pulz π\pi Hahnova echa odstraní a jaký odstranit nedokáže.

Odpověď

T2∗T_2^{*} zahrnuje nevratné defázování i vratný statický rozptyl, a proto klesá rychleji; pulz π\pi echa refokusuje statické, vratné defázování (nehomogenitu pole / pevné frekvenční odchylky) a zůstane jen nevratné spin-spinové defázování, které je skutečné T2T_2. Echo nedokáže vrátit zpět fluktuace, které se mezi oběma polovinami sekvence mění.

NMR spektroskopie molekuly: chemický posun a JJ-vazba​

Od jednoho spinu k molekule​

Dosavadní experimenty charakterizovaly jediný izolovaný spin. Skutečná chemická NMR čte molekulu (mnoho vázaných spinů najednou) a z výsledného spektra usuzuje na rysy struktury molekuly. To nám může říct o dvou efektech:

  • Chemický posun. Každé chemicky odlišné jádro precesuje s mírně jinou frekvencí νi\nu_i, protože jeho okolní elektrony stíní aplikované pole různou měrou. Polohy píků proto určují chemické prostředí (například proton v CH3_3 oproti aromatickému).

  • Skalární JJ-vazba. Interakce přes vazby mezi dvěma spiny rozštěpí každý pík na multiplet. Jádro s nn ekvivalentními sousedy se rozštěpí na n+1n+1 čar s binomickými intenzitami, což je pravidlo n+1n+1. Vzor štěpení prozrazuje konektivitu (která jádra jsou spojena vazbou) a rozestup je vazebná konstanta JJ.

Experiment​

Abychom to namodelovali na našem kvantovém počítači, musíme na qubitech implementovat spinový hamiltonián. V limitě slabé vazby a silného pole (rozdíly chemických posunů ≫\gg vazby) se části XXXX a YYYY vazby zprůměrují, takže zůstane jen ZZZZ. Hamiltonián je tedy (v jednotkách úhlové frekvence, ℏ=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}}.

Každé jádro je jeden qubit: chemický posun je jednoqubitová rotace ZZ a vazba je dvouqubitová interakce ZZZZ.

Experiment probíhá takto. Pulz π/2\pi/2 sklopí každý spin do příčné roviny a spiny precesují a vážou se po dobu tt. Zaznamenáváme komplexní příčnou magnetizaci s(t)=⟨X⟩+i⟨Y⟩s(t) = \langle X\rangle + i\langle Y\rangle sečtenou přes všechny spiny. Stejně jako v Ramseyho experimentu výše tato příčná magnetizace vykresluje klesající sinusovku jako funkci doby precese tt. Když na signál aplikujeme Fourierovu transformaci, dostaneme spektrum s píky na chemickém posunu νi\nu_i rozštěpenými skalární vazbou JijJ_{ij}.

poznámka

Vývoj molekuly simulujeme tak, že čas tt převedeme na úhly rotací: větší tt znamená větší rotace ZZ a ZZZZ, nikoli delší nebo hlubší obvod. Takže i když se simulovaný stav s tt vyvíjí (právě to způsobuje oscilace signálu volné indukce (FID)), fyzické qubity nikdy nečekají déle a neexistuje dekoherence závislá na tt. Simulovaný FID proto sám od sebe nikdy neklesá. U nekonečně dlouhého ideálního FID by byly spektrální čáry nekonečně ostré; naše konečné vzorkovací okno jim místo toho dává konečné rozlišení a může způsobit spektrální únik. Skutečné NMR čáry mají konečnou šířku určenou T2∗T_2^*. Tu doplníme vynásobením FID oknem e−t/T2∗e^{-t/T_2^{*}} před Fourierovou transformací. Říká se tomu apodizace rozšířením čar, kterou spektroskopisté používají při zpracování k nastavení šířky čáry (výměnou rozlišení za poměr signálu k šumu). (Volitelná buňka v příloze tohoto modulu ukazuje trotterizovanou alternativu, kde rozpad místo toho vychází ze skutečné dekoherence, která roste s hloubkou obvodu.)

Systém AX​

Začněme takzvaným systémem AX, molekulou tvořenou dvěma vázanými, chemicky odlišnými spiny. Označení A a X značí dvě chemicky odlišná spinová prostředí. Písmena jsou libovolná, ale skutečnost, že jsou v abecedě daleko od sebe, ilustruje jejich chemickou odlišnost.

Převod a optimalizace​

Napíšeme jednu funkci, která pro dané offsety chemických posunů νi\nu_i a vazby JijJ_{ij} vrátí parametrizovaný obvod pro přípravu stavu. Pulz π/2\pi/2 sklopí spiny na rovník a poté rotace ZZ simulují vývoj stavu pod členy chemického posunu a JJ-vazby. Každé νi\nu_i je malý offset precese v Hz vůči zvolené reference, neboli nosné rotující soustavy. Zde ho nastavujeme ručně; ve skutečné molekule stínění elektrony mění lokální magnetické pole a posouvá rezonanci každého jádra vůči této referenci. To je modelováno jednoqubitovými hradly RZR_Z, jedním na spin, a každé νi\nu_i určuje, kde leží pík daného spinu ve spektru. Dvouqubitové hradlo ZZZZ aplikuje vazbu JijJ_{ij}, která štěpí každý pík na multiplet a určuje rozestup jeho čar.

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

Spuštění a následné zpracování​

Obvod vyhodnotíme na mřížce dob vývoje a Estimator vrátí v každém bodě ⟨X⟩\langle X\rangle a ⟨Y⟩\langle Y\rangle. Z nich složíme komplexní FID ⟨X⟩+i⟨Y⟩\langle X\rangle + i\langle Y\rangle, aplikujeme apodizační okno a výsledek Fourierovou transformací převedeme na 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]

Rozšíření: systém AX2_2 a pravidlo n+1n+1​

Nyní přidej druhého ekvivalentního souseda. V systému AX2_2 se jeden spin A váže se stejným JJ na dva ekvivalentní spiny X. (Pamatuj, že X je jen označení dvou ekvivalentních spinů; nemá nic společného s Pauliho operátory XX. Každá vazba níže je stále ZZZZ.) Spin A pak vidí čtyři konfigurace sousedů (↑↑, ↑↓, ↓↑, ↓↓)(\uparrow\uparrow,\ \uparrow\downarrow,\ \downarrow\uparrow,\ \downarrow\downarrow); dvě smíšené splývají, takže A se rozštěpí na triplet 1:2:1 (n=2⇒n+1=3n=2 \Rightarrow n+1 = 3 čáry). Každý spin X vidí jen A a zůstane dubletem. Ekvivalentní spiny se navzájem neštěpí, proto nezahrnujeme žádnou vazbu X–X.

Skutečnou molekulou s tímto vzorem je 1,1,2-trichlorethan (Cl2_2CH–CH2_2Cl). Při cílení na spiny vodíku (což je nejběžnější forma NMR) hraje roli A jediný vodíkový proton v CH a dva ekvivalentní protony CH2_2 jsou dva spiny X. Teď stačí spočítat sousedy: proton CH má dva sousedy (dvojici CH2_2), takže se jeho signál rozštěpí na triplet 1:2:1 (n=2n=2); každý proton CH2_2 má jen jednoho souseda (CH), takže signál CH2_2 je dublet 1:1 (n=1n=1). Všimni si, že skupina s méně protony (CH) ukazuje více čar; počet čar odráží, kolik sousedů signál má, nikoli kolik protonů ho sdílí.

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

Níže simulujeme systém AX2_2 se třemi qubity.

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

Zkontroluj si, že rozumíš​

Přečti si každou otázku, popřemýšlej o odpovědi a pak kliknutím na trojúhelník zobraz řešení.

Ve spektru AX jsou dva dublety od sebe vzdáleny (od středu ke středu) 12 Hz a dvě čáry v každém dubletu jsou od sebe vzdáleny 2 Hz. Které číslo je rozdíl chemických posunů a které vazebná konstanta JJ? Které z nich by se změnilo, kdybys přešel na spektrometr s dvojnásobným magnetickým polem?

Odpověď

Vzdálenost 12 Hz od středu ke středu je rozdíl chemických posunů (ν1−ν2\nu_1-\nu_2); rozestup čar 2 Hz je vazebná konstanta JJ. Chemické posuny pocházejí ze stínění pole B0B_0, takže se s polem škálují. Při dvojnásobném poli se vzdálenost zdvojnásobí na 24 Hz. Vazba JJ je vlastnost molekuly přenášená přes vazby a na poli nezávisí, takže zůstane 2 Hz. Chemici odlišují posun od vazby podle toho, jak se štěpení škáluje s polem.

Pomocí pravidla n+1n+1 předpověz multiplet A, kdyby byl přidán třetí ekvivalentní spin X (systém AX3_3), a vysvětli, proč dva ekvivalentní spiny X v AX2_2 nevytvářejí vzájemné štěpení.

Odpověď

Se n=3n=3 ekvivalentními sousedy se A stane kvartetem 1:3:3:1. Ekvivalentní spiny se navzájem neštěpí, protože jejich vazba posune příslušné energetické hladiny o stejnou hodnotu, takže rozestupy mezi těmito hladinami — frekvence, které vidíme — se nezmění.

Závěr​

Použili jsme skutečný kvantový počítač ke zkoumání toho, jak jaderné spiny precesují a dekoherují v NMR experimentu, a jak interpretovat výsledná spektra. Stojí za to udělat krok zpět a zopakovat si, proč to vůbec bylo možné a proč je to víc než kuriozita.

Qubit a jádro se spinem 1/2 jsou matematicky tentýž objekt: dvouhladinové kvantové systémy žijící na Blochově sféře, otáčené rezonančními pulzy, precesující kolem osy a ztrácející koherenci ve prospěch svého okolí. Celá slovní zásoba tohoto modulu (Larmorova frekvence, rotující soustava, T1T_1, T2T_2 a T2∗T_2^*, Ramseyho proužky, Hahnovo echo) patří NMR i kvantovému počítání stejnou měrou. Čemu chemik říká chemický posun, tomu kvantový inženýr říká jednoqubitová rotace ZZ; čemu chemik říká JJ-vazba, tomu kvantový inženýr říká interakce ZZZZ. Obě oblasti popisují stejnou fyziku jinými slovy.

Tato souvislost je historická, nejen koncepční: NMR byla jednou z prvních platforem, na nichž běžely skutečné kvantové algoritmy. Do roku 2001 provedla molekula v roztoku Shorův algoritmus k faktorizaci čísla 15, přičemž každý jaderný spin sloužil jako qubit. NMR také kvantovému počítání dala velkou část jeho nástrojů pro řízení. Spinové echo, pulzní sekvence CPMG, složené pulzy a dynamické oddělení vynalezli spektroskopisté o desetiletí dřív, než je qubity potřebovaly v boji s dekoherencí. V tom, co jsme právě udělali, je uspokojivá symetrie: obor, který dal vzniknout prvním kvantovým počítačům, je nyní něco, co dokáže kvantový počítač simulovat.

Přehled a otázky​

Klíčové pojmy​

  • Qubit je uměle vytvořený dvouhladinový systém ekvivalentní spinu 1/2; jeho ∣0⟩/∣1⟩|0\rangle/|1\rangle, frekvence, hradla, T1T_1 a T2T_2 jsou přímými analogiemi veličin v NMR.

  • Rezonanční pulz otáčí Blochův vektor; úhel překlopení určuje rotaci a Rabiho/nutační křivka P1=sin⁡2(θ/2)P_1=\sin^2(\theta/2) identifikuje rotace o π\pi a π/2\pi/2.

  • T1T_1 je energetická (spin-mřížková) relaxace; T2T_2 je fázová (spin-spinová) relaxace; T2∗T_2^{*} navíc zahrnuje vratné statické defázování, které Hahnovo echo refokusuje.

  • Molekula je soubor vázaných spinů: chemický posun určuje polohu každého píku a JJ-vazba štěpí píky na multiplety podle pravidla n+1n+1, což dohromady kóduje chemické prostředí a konektivitu.

Pravda / nepravda​

  1. P/N Larmorova frekvence je energetická mezera mezi dvěma spinovými stavy dělená ℏ\hbar.

  2. P/N Pulz 90∘90^\circ (π/2\pi/2) plně invertuje rovnovážný spin.

  3. P/N T2∗T_2^{*} je obecně delší než T2T_2.

  4. P/N Hahnovo echo refokusuje nevratné spin-spinové defázování.

  5. P/N V systému AX2_2 se jádro A jeví jako triplet 1:2:1.

  6. P/N Vazebná konstanta JJ se zdvojnásobí, když zdvojnásobíš magnetické pole spektrometru.

  7. P/N Odstranění Qiskit Pulse znemožňuje měření T1T_1 na současném hardwaru.

Krátká odpověď​

  1. Vysvětli ve dvou větách, proč je jediný pulz π\pi špatným způsobem, jak odhalit 5%5\% chybu úhlu překlopení, a jak to řeší řetězení pulzů.

  2. Vzorek vykazuje široké NMR čáry. Co ti to říká z hlediska T2T_2 a jaká veličina qubitu je jeho analogií?

  3. Uveď analogii v qubitu pro každou z těchto veličin: (a) čekací doba mezi skeny, (b) pulz 90∘90^\circ, (c) chemický posun.

  4. Pomocí pravidla n+1n+1 vysvětli, proč ethylová skupina (CH3_3CH2_2–) vykazuje triplet 1:2:1 a kvartet 1:3:3:1, a urči, která skupina vytváří který multiplet.

Úlohy navíc​

  1. Carrova-Purcellova sekvence. Uprav Hahnovo echo tak, aby vložilo nn rovnoměrně rozmístěných pulzů π\pi (sekvence ve stylu CPMG), a změř T2T_2 jako funkci nn. Diskutuj, proč může více refokusačních pulzů prodloužit zdánlivé T2T_2.

  2. Zesílení chyby pulzu. Sestav obvod, který aplikuje RX(π)R_X(\pi)  n\,n-krát na ∣0⟩|0\rangle, a vykresli P1P_1 vs nn pro záměrně špatně nastavený úhel RX(1.05π)R_X(1.05\pi). Ukaž, jak se chyba stává viditelnou s rostoucím nn. Nápověda: vlož bariéru za každou rotaci; jinak může transpiler opakované rotace sloučit do jediného ekvivalentního hradla.

  3. Ethylová skupina. Pomocí fid_circuit sestav protonové spektrum ethylového fragmentu (CH3_3–CH2_2): pět qubitů, kde tři protony CH3_3 sdílejí jeden chemický posun a dva protony CH2_2 jiný, a každý pár CH3_3–CH2_2 je vázán stejným JJ (uvnitř ekvivalentní skupiny žádná vazba). Předpověz vzor pomocí pravidla n+1n+1 a poté ho simuluj; měl bys získat kvartet 1:3:3:1 (CH2_2) a triplet 1:2:1 (CH3_3).

Příloha (volitelná): trotterizovaný vývoj — nech dekoherenci určit šířku čáry​

V sekci NMR spektroskopie molekuly se vývoj e−iHte^{-iHt} aplikuje v jediné vrstvě, jejíž hloubka s tt neroste, takže FID sám od sebe neklesá a šířku čáry zavádíme apodizačním oknem e−t/T2e^{-t/T_2}. Tato volitelná buňka zvolí opačný přístup. Rozdělí vývoj na pp Trotterových kroků a za každý krok vloží bariéru (aby je transpiler nemohl sloučit zpět do jedné vrstvy). Časový bod kk používá kk kroků, takže hloubka obvodu s časem roste.

Protože všechny členy v HH komutují, je to přesné: na bezšumovém simulátoru to dá stejné ostré píky. Ale na backendu s modelem šumu nebo na skutečném hardwaru se kumulující chyba hradel postará o to, že FID klesá sám od sebe, a právě tento pokles určuje šířku čáry. Je to myšlenka T2↔T_2\leftrightarrowšířka čáry ze sekce experiment T2T_2, T2∗T_2^*, nyní vznikající ze skutečné dekoherence místo zpracovávacího okna. Tímto způsobem spouštíme systém AX (dva dublety) i systém AX2_2 (triplet 1:2:1 plus dublet).

Dvě praktické poznámky k zvážení. Za prvé, cenou je hloubka: nejhlubší obvod AX používá ∼ ⁣47\sim\!47 dvouqubitových hradel a verze AX2_2 zhruba dvakrát tolik, takže oba jsou mnohem zašuměnější a dražší než přesná verze. Za druhé, každý spinový systém se stále odesílá jako jedna úloha a každý časový bod znovu používá stejné rozložení fyzických qubitů vybrané v sekci o spektroskopii. Pokud je USE_BATCH=True, tyto úlohy také používají volitelný Batch platný pro celý 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()