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:
qiskitv2.1 nebo novějšíqiskit-ibm-runtimev0.40.1 nebo novějšíqiskit-aerv0.17 nebo novějšíscipyamatplotlib(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, a .
Supravodivý qubit je uměle vytvořená verze stejného objektu: ovladatelný kvantový systém o dvou úrovních. Jeho stavy a jsou analogií spinu nahoru a dolů, hradlo, kterým ho otáčíme, je analogií RF pulsu a ztrácí koherenci s vlastními a . 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 nebo .
-
— spin-mřížková relaxace, návrat excitovaného spinu zpět do rovnováhy.
-
— spin-spinová relaxace (defázování), měřená Ramseyho experimentem a refokusovaná Hahnovým echem.
-
NMR spektroskopie (chemický posun a -vazba) — simulace malé molekuly na několika qubitech a čtení jejího multipletového spektra pro určení konektivity pomocí pravidla .
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 , kde je gyromagnetický poměr. Ve statickém poli ve směru se dva spinové stavy energeticky rozštěpí (Zeemanův jev):
Energetická mezera určuje Larmorovu (rezonanční) frekvenci . Magnetický moment precesuje kolem právě touto frekvencí.
K otočení spinu aplikujeme oscilující RF pole na frekvenci (nebo blízko ní). V soustavě rotující budicí frekvencí (rotující soustava) rezonanční buzení otáčí Blochův vektor kolem osy v rovině -. 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 překlopí spin do transverzální roviny a puls ho otočí zcela na .
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/2 | Kvantový procesor (qubit) |
|---|---|
| Spin nahoru / spin dolů | / |
| Zeemanova mezera | Přechodová energie qubitu |
| Larmorova frekvence | Frekvence qubitu |
| Rezonanční RF puls (úhel překlopení ) | Jednoqubitové rotační hradlo |
| Puls / | / |
| Spin-mřížková relaxace | Doba energetické relaxace qubitu |
| Spin-spinová relaxace | Doba defázování qubitu |
| Nutace | Rabiho oscilace |
| Signál volné precese (FID), Hahnovo echo | Ramseyho 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:
-
Mapuj problém na kvantový obvod.
-
Optimalizuj obvod pro provedení (transpilace pro backend).
-
Proveď pomocí primitiva IBM Quantum.
-
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 rotaci o úhel překlopení kolem osy , je pravděpodobnost nalezení ve stavu :
Toto je Rabiho křivka. Její první maximum je puls (úplná inverze, puls ); puls je čtvrtotáčka (puls , který překlopí spin do transverzální roviny). V NMR takto kalibruješ své pulsy.
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 hradla . Křivka i určení / 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ěží pro libovolné nativně.
Pohled na Blochovu sféru
Animace níže ukazuje nutaci: rezonanční buzení stabilně otáčí Blochův vektor kolem osy a vede ho z dolů směrem k a zase zpět. Přesně tento pohyb Rabiho křivka měří.
Převod problému na obvod
Sestavíme jeden parametrizovaný obvod, 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")
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 od do (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 pro každý úhel a porovnej s .
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()

Zkontroluj si, že rozumíš
Na změřené křivce dosáhne poprvé maxima u pulzu (). Nyní předpokládej, že je zařízení špatně zkalibrované, takže nominální úhel na ose x ve skutečnosti vytvoří rotaci o .
(a) Při jakém nominálním úhlu se nyní objeví první maximum ? (b) Byl by jediný pulz citlivým způsobem, jak tuto 5% chybu odhalit?
Odpověď
(a) Maximum se objeví tam, kde se skutečná rotace rovná , tedy při nominálním úhlu — malý posun vrcholu doleva.
(b) Ne. Nominální pulz vytvoří skutečnou rotaci , což dává místo — 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 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: (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 :
Pro chemika určuje č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 pomocí hradla ( pulz), počkej proměnnou dobu a poté měř. Podíl, který zůstal v , vykreslí průběh rozpadu.
Pohled na Blochovu sféru
relaxace vrací excitovaný stav do rovnováhy, takže Blochův vektor klouže z zpět nahoru do podél osy .
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")
Measured T1 = 225.3 microseconds
Přesná hodnota 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 . Jaký podíl rovnovážné magnetizace se podle zákona rozpadu obnoví po ?
Odpověď
Po je zbývající odchylka od rovnováhy , tedy je obnoveno zhruba 99,3 %.
Experiment: ,
Teorie
měří ztrátu energie. měří ztrátu fázové koherence v příčné rovině. Můžeme ji pozorovat pomocí Ramseyho experimentu: pulz 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 .
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 . 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 vytvářejí kratší zdánlivou dobu rozpadu pozorovanou v Ramseyho experimentu.
Jak tedy získat skutečné , když Ramseyho experiment prozradí jen ? Druhý experiment, nazývaný Hahnovo echo, odstraní veškerý šum druhého typu, aby našel skutečné . Začíná stejně jako Ramseyho experiment pulzem , poté se spiny rozvějiří jako dříve. Po nějaké době se aplikuje pulz . 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 znovu zaostří.
a jsou důležitá čísla, která je třeba znát v chemii i v kvantovém počítání. V chemii určuje šířku čáry ve standardním NMR experimentu (co to znamená, uvidíš v pozdější sekci). může o molekulách prozradit mnoho, například o molekulárním překlápění, a změna může signalizovat, že se molekula navázala na něco většího. V kvantovém počítání obecně chceme, aby i 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 , nikoli . 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 , tedy zda je v nebo . Proto u obou experimentů na konec přidáme další pulz následovaný měřením. Tento pulz převede informaci o fázi qubitu na populaci.
Níže spustíme každý experiment, abychom určili a .
Ramsey (): dva pulzy se zpožděním
První pulz 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 . 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é .
Pohled na Blochovu sféru
Pulz 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 .
# 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)
Hahnovo echo (): přidání refokusačního pulzu uprostřed
Pohled na Blochovu sféru
Hahnovo echo: spiny se rozvějiří jako dříve, pak je pulz 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
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")

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 , respektive .
Zkontroluj si, že rozumíš
Typicky bys měl zjistit, že . Vysvětli jednou větou proč a řekni, jaký fyzikální efekt pulz Hahnova echa odstraní a jaký odstranit nedokáže.
Odpověď
zahrnuje nevratné defázování i vratný statický rozptyl, a proto klesá rychleji; pulz 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é . Echo nedokáže vrátit zpět fluktuace, které se mezi oběma polovinami sekvence mění.
NMR spektroskopie molekuly: chemický posun a -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í , 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 CH oproti aromatickému).
-
Skalární -vazba. Interakce přes vazby mezi dvěma spiny rozštěpí každý pík na multiplet. Jádro s ekvivalentními sousedy se rozštěpí na čar s binomickými intenzitami, což je pravidlo . Vzor štěpení prozrazuje konektivitu (která jádra jsou spojena vazbou) a rozestup je vazebná konstanta .
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ů vazby) se části a vazby zprůměrují, takže zůstane jen . Hamiltonián je tedy (v jednotkách úhlové frekvence, ):
Každé jádro je jeden qubit: chemický posun je jednoqubitová rotace a vazba je dvouqubitová interakce .
Experiment probíhá takto. Pulz sklopí každý spin do příčné roviny a spiny precesují a vážou se po dobu . Zaznamenáváme komplexní příčnou magnetizaci 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 . Když na signál aplikujeme Fourierovu transformaci, dostaneme spektrum s píky na chemickém posunu rozštěpenými skalární vazbou .
Vývoj molekuly simulujeme tak, že čas převedeme na úhly rotací: větší znamená větší rotace a , nikoli delší nebo hlubší obvod. Takže i když se simulovaný stav s 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 . 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 . Tu doplníme vynásobením FID oknem 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ů a vazby vrátí parametrizovaný obvod pro přípravu stavu. Pulz sklopí spiny na rovník a poté rotace simulují vývoj stavu pod členy chemického posunu a -vazby. Každé 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 , jedním na spin, a každé určuje, kde leží pík daného spinu ve spektru. Dvouqubitové hradlo aplikuje vazbu , 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ě a . Z nich složíme komplexní FID , 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)}),
)

Expected AX lines (Hz): [-7.0, -5.0, 5.0, 7.0]
Rozšíření: systém AX a pravidlo
Nyní přidej druhého ekvivalentního souseda. V systému AX se jeden spin A váže se stejným 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 . Každá vazba níže je stále .) Spin A pak vidí čtyři konfigurace sousedů ; dvě smíšené splývají, takže A se rozštěpí na triplet 1:2:1 ( čá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 (ClCH–CHCl). 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 CH jsou dva spiny X. Teď stačí spočítat sousedy: proton CH má dva sousedy (dvojici CH), takže se jeho signál rozštěpí na triplet 1:2:1 (); každý proton CH má jen jednoho souseda (CH), takže signál CH je dublet 1: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í.
Níže simulujeme systém AX 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 ? 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ů (); rozestup čar 2 Hz je vazebná konstanta . Chemické posuny pocházejí ze stínění pole , takže se s polem škálují. Při dvojnásobném poli se vzdálenost zdvojnásobí na 24 Hz. Vazba 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 předpověz multiplet A, kdyby byl přidán třetí ekvivalentní spin X (systém AX), a vysvětli, proč dva ekvivalentní spiny X v AX nevytvářejí vzájemné štěpení.
Odpověď
Se 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, , a , 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 ; čemu chemik říká -vazba, tomu kvantový inženýr říká interakce . 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 , frekvence, hradla, a 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 identifikuje rotace o a .
-
je energetická (spin-mřížková) relaxace; je fázová (spin-spinová) relaxace; 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 -vazba štěpí píky na multiplety podle pravidla , což dohromady kóduje chemické prostředí a konektivitu.
Pravda / nepravda
-
P/N Larmorova frekvence je energetická mezera mezi dvěma spinovými stavy dělená .
-
P/N Pulz () plně invertuje rovnovážný spin.
-
P/N je obecně delší než .
-
P/N Hahnovo echo refokusuje nevratné spin-spinové defázování.
-
P/N V systému AX se jádro A jeví jako triplet 1:2:1.
-
P/N Vazebná konstanta se zdvojnásobí, když zdvojnásobíš magnetické pole spektrometru.
-
P/N Odstranění Qiskit Pulse znemožňuje měření na současném hardwaru.
Krátká odpověď
-
Vysvětli ve dvou větách, proč je jediný pulz špatným způsobem, jak odhalit chybu úhlu překlopení, a jak to řeší řetězení pulzů.
-
Vzorek vykazuje široké NMR čáry. Co ti to říká z hlediska a jaká veličina qubitu je jeho analogií?
-
Uveď analogii v qubitu pro každou z těchto veličin: (a) čekací doba mezi skeny, (b) pulz , (c) chemický posun.
-
Pomocí pravidla vysvětli, proč ethylová skupina (CHCH–) vykazuje triplet 1:2:1 a kvartet 1:3:3:1, a urči, která skupina vytváří který multiplet.
Úlohy navíc
-
Carrova-Purcellova sekvence. Uprav Hahnovo echo tak, aby vložilo rovnoměrně rozmístěných pulzů (sekvence ve stylu CPMG), a změř jako funkci . Diskutuj, proč může více refokusačních pulzů prodloužit zdánlivé .
-
Zesílení chyby pulzu. Sestav obvod, který aplikuje -krát na , a vykresli vs pro záměrně špatně nastavený úhel . Ukaž, jak se chyba stává viditelnou s rostoucím . Nápověda: vlož bariéru za každou rotaci; jinak může transpiler opakované rotace sloučit do jediného ekvivalentního hradla.
-
Ethylová skupina. Pomocí
fid_circuitsestav protonové spektrum ethylového fragmentu (CH–CH): pět qubitů, kde tři protony CH sdílejí jeden chemický posun a dva protony CH jiný, a každý pár CH–CH je vázán stejným (uvnitř ekvivalentní skupiny žádná vazba). Předpověz vzor pomocí pravidla a poté ho simuluj; měl bys získat kvartet 1:3:3:1 (CH) a triplet 1:2:1 (CH).
Příloha (volitelná): trotterizovaný vývoj — nech dekoherenci určit šířku čáry
V sekci NMR spektroskopie molekuly se vývoj aplikuje v jediné vrstvě, jejíž hloubka s neroste, takže FID sám od sebe neklesá a šířku čáry zavádíme apodizačním oknem . Tato volitelná buňka zvolí opačný přístup. Rozdělí vývoj na Trotterových kroků a za každý krok vloží bariéru (aby je transpiler nemohl sloučit zpět do jedné vrstvy). Časový bod používá kroků, takže hloubka obvodu s časem roste.
Protože všechny členy v 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 šířka čáry ze sekce experiment , , 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 AX (triplet 1:2:1 plus dublet).
Dvě praktické poznámky k zvážení. Za prvé, cenou je hloubka: nejhlubší obvod AX používá dvouqubitových hradel a verze AX 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()