Jądrowy rezonans magnetyczny z Qiskit
W tym module Qiskit in Classrooms uczniowie muszą mieć działające środowisko Python z zainstalowanymi następującymi pakietami:
qiskitv2.1 lub nowszyqiskit-ibm-runtimev0.40.1 lub nowszyqiskit-aerv0.17 lub nowszyscipyimatplotlib(do dopasowywania i rysowania wykresów)pylatexenc(do renderowania diagramów obwodów)
Aby skonfigurować środowisko i zainstalować powyższe pakiety, zobacz przewodnik Install Qiskit. Aby uruchamiać zadania na prawdziwych komputerach kwantowych, uczniowie muszą założyć konto IBM Quantum®, wykonując kroki z przewodnika Set up your IBM Cloud® account.
Ten moduł został przetestowany i zużywa około 5 minut czasu QPU. To jest szacunek, a Twoje rzeczywiste zużycie może się różnić.
Wprowadzenie
Gdy chemik przeprowadza eksperyment NMR, umieszcza próbkę spinów jądrowych w silnym polu magnetycznym, przykłada impuls o częstotliwości radiowej (RF) i mierzy powracający sygnał. Technika opiera się na kilku ideach z mechaniki kwantowej cząstki o spinie 1/2 w polu magnetycznym. Spin ma dwa poziomy energetyczne rozdzielone energią odpowiadającą charakterystycznej częstotliwości Larmora. Rezonansowy impuls o tej częstotliwości przechyla spin do płaszczyzny poprzecznej, gdzie ten precesuje i indukuje sygnał, który następnie zanika z dwoma charakterystycznymi czasami, i .
Nadprzewodzący kubit to wytworzona inżyniersko wersja tego samego obiektu: kontrolowalnego dwupoziomowego układu kwantowego. Jego stany i są odpowiednikami spinu w górę i w dół, bramka, którą stosujemy do jego obrotu, jest odpowiednikiem impulsu RF, a on sam traci koherencję z własnymi i . To nie jest tylko luźne porównanie; niektóre z najwcześniejszych eksperymentalnych procesorów kwantowych z końca lat 90. i początku XXI wieku wykorzystywały NMR.
W tym module użyjesz prawdziwego komputera kwantowego do przeprowadzenia tych samych eksperymentów charakteryzacyjnych, które wykonuje spektroskopista NMR (lub inżynier kwantowy kalibrujący sprzęt):
-
Rabi / nutacja — pobudzanie spinu i obserwowanie jego obrotu. W ten sposób kalibruje się obrót o lub .
-
— relaksacja spin-sieć, czyli powrót wzbudzonego spinu do równowagi.
-
— relaksacja spin-spin (defazowanie), mierzona eksperymentem Ramseya i refokusowana echem Hahna.
-
Spektroskopia NMR (przesunięcie chemiczne i sprzężenie ) — symulacja małej cząsteczki na kilku kubitach i odczyt jej widma multipletowego w celu odtworzenia łączności za pomocą reguły .
Dla każdej koncepcji najpierw podajemy minimum teorii, a następnie realizujemy eksperyment w Qiskit, zgodnie z czteroetapowym przepływem pracy Qiskit patterns.
Teoria
Cząstki o spinie 1/2 w polu magnetycznym
Jądro o spinie 1/2 ma moment magnetyczny , gdzie jest współczynnikiem żyromagnetycznym. W statycznym polu skierowanym wzdłuż osi dwa stany spinowe rozszczepiają się energetycznie (efekt Zeemana):
Przerwa energetyczna definiuje częstotliwość Larmora (rezonansową) . Moment magnetyczny precesuje wokół z tą samą częstotliwością.
Aby obrócić spin, przykładamy oscylujące pole RF o częstotliwości równej lub bliskiej. W układzie wirującym z częstotliwością pobudzania (układ wirujący) rezonansowe pobudzenie obraca wektor Blocha wokół osi leżącej w płaszczyźnie -. To, jak daleko się obróci (kąt odchylenia), zależy od siły i czasu trwania impulsu. Najważniejsze są dwa przypadki: impuls przechyla spin do płaszczyzny poprzecznej, a impuls obraca go całkowicie do .
Tłumaczenie między żargonem NMR a żargonem obliczeń kwantowych
Kubit jest układem dwupoziomowym, matematycznie identycznym ze spinem 1/2 w polu magnetycznym — więc choć działamy na procesorze kwantowym, a nie na jądrze, fizyka jest ta sama, która leży u podstaw NMR. Różni się tylko słownictwo. Ta tabela pomaga rozmawiać zarówno z chemikami fizykami, jak i ze specjalistami od obliczeń kwantowych.
| NMR / spin 1/2 | Procesor kwantowy (kubit) |
|---|---|
| Spin w górę / spin w dół | / |
| Przerwa Zeemana | Energia przejścia kubitu |
| Częstotliwość Larmora | Częstotliwość kubitu |
| Rezonansowy impuls RF (kąt odchylenia ) | Jednokubitowa bramka obrotu |
| Impuls / | / |
| Relaksacja spin-sieć | Czas relaksacji energii kubitu |
| Relaksacja spin-spin | Czas defazowania kubitu |
| Nutacja | Oscylacje Rabiego |
| Swobodny zanik indukcji (FID), echo Hahna | Sekwencje Ramseya, sekwencje echa |
Wszystko poniżej to ćwiczenie w mierzeniu prawej kolumny i odczytywaniu jej jako lewej kolumny.
Konfiguracja środowiska Qiskit
W całym module stosujemy przepływ pracy Qiskit patterns:
-
Zmapuj problem na obwód kwantowy.
-
Zoptymalizuj obwód do wykonania (transpilacja do backendu).
-
Wykonaj za pomocą prymitywu IBM Quantum.
-
Przetwórz końcowo i przeanalizuj wyniki.
Najpierw ładujemy usługę uruchomieniową i wybieramy najmniej obciążony prawdziwy backend. Jeśli nie zapisano jeszcze danych uwierzytelniających, zobacz stronę dokumentacji o tym, jak zapisać dane logowania.
# 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
Użyj poniższej komórki, aby przełączać się między prawdziwym sprzętem a lokalnym symulatorem w całym notatniku. Uruchamianie na sprzęcie jest sednem tego modułu, ale w razie problemów z łącznością lub użyciem symulator może posłużyć jako zapasowe rozwiązanie. Ustaw USE_HARDWARE = True, aby uruchomić na QPU wybranym powyżej.
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}.")
Eksperyment: oscylacje Rabiego (nutacja)
Teoria
Pobudzaj spin w rezonansie, a jego wektor Blocha będzie się równomiernie obracał od bieguna do bieguna — nutuje. Jeśli zastosujemy obrót o kąt odchylenia wokół osi do kubitu zaczynającego w stanie , prawdopodobieństwo znalezienia go w stanie wynosi:
To jest krzywa Rabiego. Jej pierwsze maksimum to impuls (pełna inwersja, impuls ); impuls to ćwierć obrotu (impuls , który przechyla spin do płaszczyzny poprzecznej). W NMR ta krzywa służy do kalibracji impulsów.
Dosłowny eksperyment Rabiego przemiata amplitudę lub czas trwania pobudzenia. Wymaga to kontroli na poziomie impulsów, którą usunięto w Qiskit v2.0. Tutaj zamiast tego przemiatamy kąt obrotu bramki . Krzywa i identyfikacja / są identyczne; jedyna różnica polega na tym, że wybieramy kąt bezpośrednio, zamiast kalibrować długość impulsu. Na sprzęcie z bramkami ułamkowymi bramka dla dowolnego działa natywnie.
Widok na sferze Blocha
Poniższa animacja pokazuje nutację: rezonansowe pobudzenie równomiernie obraca wektor Blocha wokół osi , przenosząc go z w stronę i z powrotem. To właśnie ten ruch mierzy krzywa Rabiego.
Mapowanie problemu na obwód
Budujemy jeden sparametryzowany obwód, z następującym po nim pomiarem, i później obliczamy go dla wielu kątów naraz.
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")
Optymalizacja do wykonania
Dokonaj transpilacji do obwodu architektury zestawu instrukcji (ISA) dla wybranego Backend.
rabi_isa = pm_q.run(rabi)
Wykonanie
Przeprowadzamy przemiatanie od do (dwa pełne cykle Rabiego). Przekazanie tablicy wartości parametrów jako jednego Primitive Unified Bloc (PUB) uruchamia wszystkie z nich w jednym zadaniu.
angles = np.linspace(0, 4 * np.pi, 41)
job = sampler.run([(rabi_isa, angles.reshape(-1, 1))], shots=1000)
rabi_result = job.result()
Przetwarzanie końcowe i analiza
Wyodrębnij dla każdego kąta i porównaj z .
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()

Sprawdź, czy rozumiesz
Na zmierzonej krzywej osiąga po raz pierwszy maksimum przy impulsie (). Załóżmy teraz, że urządzenie jest źle skalibrowane, tak że nominalny kąt na osi x w rzeczywistości daje obrót o .
(a) Przy jakim nominalnym kącie pojawi się teraz pierwsze maksimum ? (b) Czy zastosowanie pojedynczego impulsu byłoby czułym sposobem wykrycia tego 5% błędu?
Odpowiedź
(a) Maksimum pojawia się tam, gdzie rzeczywisty obrót wynosi , czyli przy nominalnym kącie — niewielkie przesunięcie piku w lewo.
(b) Nie. Nominalny impuls daje rzeczywisty obrót o , co prowadzi do zamiast — zmiany zbyt małej, by dostrzec ją na tle szumu pomiarowego. Dlatego aby kalibrować precyzyjnie, spektroskopiści NMR wzmacniają błąd, stosując obrót wiele razy (na przykład metoda lub długi ciąg impulsów), tak że mały błąd pojedynczego impulsu kumuluje się w duże, mierzalne odchylenie. Wrócimy do tego pomysłu w zadaniach wyzwań.
Eksperyment: (relaksacja spin-sieć)
Teoria
Pozostawiony sam sobie wzbudzony spin oddaje energię otoczeniu („sieci") i relaksuje z powrotem w kierunku równowagi termicznej. Obsadzenie stanu wzbudzonego zanika wykładniczo z czasem relaksacji spin-sieć :
Dla chemika wyznacza opóźnienie rekonwersji, jakie trzeba odczekać między skanami w ilościowym NMR, i niesie informację o ruchu obrotowym cząsteczek oraz ich dynamice. W przypadku kubitu jest to czas relaksacji energii i jedna z dwóch głównych wartości używanych do modelowania efektów dekoherencji w komputerze kwantowym.
Eksperyment. Przygotuj za pomocą bramki (impuls ), odczekaj zmienne opóźnienie , a następnie zmierz. Ułamek wciąż pozostający w wyznacza przebieg zaniku.
Widok na sferze Blocha
Relaksacja przywraca stan wzbudzony do równowagi, więc wektor Blocha zsuwa się z z powrotem do wzdłuż osi .
Budowa i transpilacja skanu opóźnień
Czasy opóźnień muszą być konkretnymi wartościami (są planowane w czasie sprzętowym), więc budujemy jeden obwód na każde opóźnienie.
# 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
Wykonanie
t1_job = sampler.run([(c,) for c in t1_isa], shots=1000)
t1_res = t1_job.result()
Analiza — dopasowanie wykładnicze
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
Dokładny czas różni się między eksperymentami i między Backend, ale powinieneś uzyskać wynik rzędu kilkuset mikrosekund.
Sprawdź, czy rozumiesz
W ilościowym NMR zaleca się odczekać około między skanami. Korzystając z prawa zaniku, jaka część magnetyzacji równowagowej odbudowuje się po ?
Odpowiedź
Po pozostałe odchylenie od równowagi wynosi , czyli odbudowało się około 99,3%.
Eksperyment: ,
Teoria
mierzy utratę energii. mierzy utratę spójności fazowej w płaszczyźnie poprzecznej. Możemy to zaobserwować w eksperymencie Ramseya: impuls obraca wszystkie spiny na równik, wszystkie wskazują ten sam kierunek. Wykonują precesję Larmora, ale każdy z nich precesuje z nieco inną częstotliwością, więc rozchodzą się wachlarzowo wzdłuż równika, a sumaryczny sygnał poprzeczny zanika z charakterystycznym czasem zaniku .
Uwaga dla czytelników z kwantów: Obraz rozchodzenia się wachlarzowego pokazuje jeden ze sposobów utraty spójności: rozrzut określonych faz, które się uśredniają. Warto wiedzieć, że nie jest to jedyny mechanizm. Spójność może też zostać utracona, ponieważ faza splątuje się z otoczeniem, więc nie jest już w ogóle określoną wartością, którą system posiada. Nie da się odróżnić tych dwóch mechanizmów, obserwując samo zanikanie sygnału, więc obraz wachlarza to dobra intuicja. Uważaj, by nie traktować go jako pełnej ani dosłownej historii.
Dwa efekty powodują defazację spinu:
- Nieodwracalna (lub efektywnie nieodwracalna) dekoherencja wynikająca z niekontrolowanego sprzężenia z innymi stopniami swobody, charakteryzowana przez prawdziwe . W NMR zwykle pochodzi z ruchu cząsteczek modulującego sprzężenia między spinami. W obliczeniach kwantowych odpowiada niepożądanym sprzężeniom między kubitami a otoczeniem.
- Statyczne lub wolnozmienne, odwracalne przesunięcia częstotliwości, takie jak niejednorodność pola, zróżnicowanie częstotliwości między kubitami i błędy kalibracji. W połączeniu z prawdziwym dają krótszy pozorny czas zaniku obserwowany w eksperymencie Ramseya.
Jak więc odzyskać prawdziwe , skoro eksperyment Ramseya mówi nam tylko o ? Drugi eksperyment, zwany echem Hahna, znosi cały szum drugiego rodzaju, aby wyznaczyć prawdziwe . Zaczyna się tak samo jak eksperyment Ramseya, od impulsu , po czym spiny rozchodzą się wachlarzowo jak poprzednio. Po pewnym czasie przykładany jest impuls . Impuls ten odwraca akumulację fazy spowodowaną statycznymi przesunięciami częstotliwości, więc spiny ogniskują się ponownie po drugim opóźnieniu o tym samym czasie trwania .
i to ważne wartości zarówno w chemii, jak i w obliczeniach kwantowych. W chemii wyznacza szerokość linii w standardowym eksperymencie NMR (co to znaczy, zobaczysz w późniejszej sekcji). może wiele powiedzieć o cząsteczkach, na przykład o ich ruchu obrotowym, a zmiana może sygnalizować, że cząsteczka związała się z czymś większym. W obliczeniach kwantowych zwykle chcemy, by zarówno , jak i były jak najdłuższe, abyśmy mieli więcej czasu na eksperymenty, zanim dekoherencja przytłoczy system. Cała rodzina technik opartych na echu Hahna wydłuża ten czas, znosząc szum odwracalny, tak że obwód jest w idealnym przypadku ograniczony prawdziwym , a nie . Nazywa się to „sprzężeniem dynamicznym" (ang. dynamical decoupling).
Implementacja
Gdy realizujemy eksperymenty Ramseya i echa Hahna na kubicie, odczyt różni się nieco od przypadku NMR. W NMR można bezpośrednio obserwować oscylujące pola indukowane przez spiny precesujące z częstotliwością Larmora w płaszczyźnie poprzecznej. Natomiast w kubicie możemy zmierzyć tylko jego rzut na oś , czyli czy jest , czy . Dlatego w obu eksperymentach kończymy dodatkowym impulsem , po którym następuje pomiar. Ten impuls przekłada informację o fazie kubitu na obsadzenie.
Poniżej uruchamiamy każdy eksperyment, aby wyznaczyć i .
Ramsey (): dwa impulsy z czasem opóźnienia
Pierwszy impuls odchyla wektor Blocha na równik, a następny przekłada fazę wektora Blocha na obsadzenie. Ponieważ faza zmienia się w czasie, gdy wektor Blocha precesuje, widzimy sinusoidalny wzór w obsadzeniu jako funkcję czasu precesji . Gdy kubit traci spójność fazową, powinniśmy zobaczyć zanik amplitudy tej sinusoidy.
Kilka dodatkowych subtelności dla czytelników z kwantów: W eksperymentach z obliczeń kwantowych obserwowane oscylacje w rzeczywistości nie mają częstotliwości Larmora, która leży w zakresie GHz i jest zbyt szybka, by ją rozdzielić. Eksperymenty kwantowe działają w układzie wirującym: układzie, który obraca się razem z kubitem z (prawie) częstotliwością Larmora, więc szybka precesja jest niewidoczna, a wektor Blocha wygląda niemal nieruchomo. Aby zobaczyć prążki, celowo ustawiamy nasz układ wirujący z niewielkim odstrojeniem od częstotliwości Larmora. Wektor Blocha zdaje się wtedy precesować z tym małym odstrojeniem i otrzymujemy sygnał oscylujący z częstotliwością odstrojenia. Poniżej nie ustalamy odstrojenia na jednej wartości; wybieramy je tak, aby około tuzin prążków mieściło się w oknie pomiarowym (którego rozmiar dobieramy do czasu spójności kubitu), co ułatwia próbkowanie oscylacji bez aliasingu. Jeszcze jedna komplikacja: w Qiskit nie mamy kontroli na poziomie impulsów potrzebnej do zmiany częstotliwości impulsu (która wyznacza częstotliwość układu wirującego). Zamiast tego dodajemy efektywne odstrojenie ręcznie, wykorzystując je do ustawienia fazy drugiego impulsu Ramseya. Jest to nieco sztuczne, ale wynik jest ten sam, a obserwowany zanik wciąż jest prawdziwym .
Widok na sferze Blocha
Impuls odchyla spiny na równik; każdy z nich precesuje następnie z nieco inną szybkością i rozchodzą się wachlarzowo, więc sumaryczny (czerwony) wektor kurczy się wraz z ich defazacją — ten zanik to .
# 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)
Echo Hahna (): dodaj ogniskujący impuls pośrodku
Widok na sferze Blocha
Echo Hahna: spiny rozchodzą się wachlarzowo jak poprzednio, następnie impuls je odwraca, więc ich dalsza precesja ogniskuje je z powrotem razem (czerwony wektor odrasta), odzyskując spójność utraconą przez defazację statyczną.
# 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
Analiza: dopasowanie i porównanie dwóch zaników
# 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
Widzimy tu zarówno oscylację Ramseya, jak i zanik sygnału echa Hahna, zgodnie z oczekiwaniami. Związane z nimi skale czasowe tych zaników to odpowiednio i .
Sprawdź, czy rozumiesz
Zwykle powinieneś otrzymać . Wyjaśnij dlaczego w jednym zdaniu i powiedz, jaki efekt fizyczny usuwa impuls echa Hahna oraz czego nie potrafi usunąć.
Odpowiedź
obejmuje zarówno nieodwracalną defazację, jak i odwracalny rozrzut statyczny, więc zanika szybciej; impuls echa ogniskuje statyczną, odwracalną defazację (niejednorodność pola / stałe przesunięcia częstotliwości), pozostawiając tylko nieodwracalną defazację spin-spin, czyli prawdziwe . Echo nie potrafi cofnąć fluktuacji, które zmieniają się między dwiema połowami sekwencji.
Spektroskopia NMR cząsteczki: przesunięcie chemiczne i sprzężenie
Od jednego spinu do cząsteczki
Dotychczasowe eksperymenty charakteryzowały pojedynczy, izolowany spin. Prawdziwe chemiczne NMR odczytuje cząsteczkę (wiele sprzężonych spinów naraz) i na podstawie otrzymanego widma wnioskuje o cechach jej struktury. Może to nam powiedzieć o dwóch efektach:
-
Przesunięcie chemiczne. Każde chemicznie odmienne jądro precesuje z nieco inną częstotliwością , ponieważ otaczające je elektrony ekranują przyłożone pole w różnym stopniu. Położenia pików identyfikują zatem środowiska chemiczne (na przykład proton CH w porównaniu z aromatycznym).
-
Skalarne sprzężenie . Oddziaływanie przez wiązania między dwoma spinami rozszczepia każdy pik na multiplet. Jądro z równoważnymi sąsiadami rozszczepia się na linii o intensywnościach dwumianowych, czyli regułę . Wzór rozszczepienia ujawnia łączność (które jądra są związane), a odstęp to stała sprzężenia .
Eksperyment
Aby zamodelować to na naszym komputerze kwantowym, musimy zaimplementować hamiltonian spinowy na kubitach. W granicy słabego sprzężenia i silnego pola (różnice przesunięć chemicznych sprzężenia) części i sprzężenia uśredniają się do zera, pozostawiając tylko . Hamiltonian ma więc postać (w jednostkach częstotliwości kątowej, ):
Każde jądro to jeden kubit: przesunięcie chemiczne to jednokubitowy obrót , a sprzężenie to dwukubitowe oddziaływanie .
Eksperyment przebiega następująco. Impuls odchyla każdy spin do płaszczyzny poprzecznej, a spiny precesują i sprzęgają się przez czas . Rejestrujemy zespoloną magnetyzację poprzeczną zsumowaną po wszystkich spinach. Podobnie jak w powyższym eksperymencie Ramseya, ta magnetyzacja poprzeczna wyznacza zanikającą sinusoidę w funkcji czasu precesji . Gdy wykonamy transformatę Fouriera sygnału, otrzymamy widmo z pikami przy przesunięciu chemicznym rozszczepionymi przez sprzężenie skalarne .
Symulujemy ewolucję cząsteczki, odwzorowując czas na kąty obrotu: większe oznacza większe obroty i , a nie dłuższy czy głębszy obwód. Zatem choć symulowany stan rzeczywiście ewoluuje wraz z (to sprawia, że swobodny zanik indukcji (FID) oscyluje), fizyczne kubity nigdy nie czekają dłużej i nie ma dekoherencji zależnej od . Symulowany FID nigdy więc sam nie zanika. Dla nieskończenie długiego idealnego FID linie widmowe byłyby nieskończenie ostre; nasze skończone okno próbkowania nadaje im natomiast skończoną rozdzielczość i może wprowadzać przeciek widmowy. Prawdziwe linie NMR mają skończoną szerokość wyznaczoną przez . Dodajemy ją z powrotem, mnożąc FID przez okno przed transformatą Fouriera. Nazywa się to apodyzacją poszerzającą linie, którą spektroskopiści stosują w przetwarzaniu, aby ustawić szerokość linii (wymieniając rozdzielczość na stosunek sygnału do szumu). (Opcjonalna komórka w dodatku do tego modułu pokazuje alternatywę z dekompozycją Trottera, w której zanik wynika z rzeczywistej dekoherencji rosnącej wraz z głębokością obwodu.)
Układ AX
Zacznijmy od tak zwanego układu AX, cząsteczki składającej się z dwóch sprzężonych, chemicznie odmiennych spinów. Etykiety A i X oznaczają dwa chemicznie odmienne środowiska spinowe. Litery są dowolne, ale fakt, że leżą daleko od siebie w alfabecie, ilustruje ich chemiczną odmienność.
Mapowanie i optymalizacja
Piszemy jedną funkcję, która dla zadanych przesunięć chemicznych i sprzężeń zwraca sparametryzowany obwód przygotowania stanu. Impuls odchyla spiny na równik, następnie obroty symulują ewolucję stanu pod wpływem członów przesunięcia chemicznego i sprzężenia . Każde to niewielkie przesunięcie precesji w Hz względem wybranego odniesienia, czyli nośnej układu wirującego. Tutaj ustawiamy je ręcznie; w prawdziwej cząsteczce ekranowanie elektronowe zmienia lokalne pole magnetyczne i przesuwa rezonans każdego jądra względem tego odniesienia. Modelują to jednokubitowe bramki , po jednej na spin, a każde ustala, gdzie w widmie leży pik tego spinu. Dwukubitowa bramka realizuje sprzężenie , które rozszczepia każdy pik na multiplet i wyznacza odstęp między jego liniami.
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")
Wykonanie i przetwarzanie końcowe
Obliczamy obwód na siatce czasów ewolucji, a Estimator zwraca i dla każdego z nich. Łączymy je w zespolony FID , stosujemy okno apodyzacji i wykonujemy transformatę Fouriera wyniku, aby otrzymać widmo.
# --- 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]
Rozszerzenie: układ AX i reguła
Dodajmy teraz drugiego równoważnego sąsiada. W układzie AX jeden spin A sprzęga się, z tym samym , z dwoma równoważnymi spinami X. (Pamiętaj, X to tylko etykieta dwóch równoważnych spinów; nie ma nic wspólnego z operatorami Pauliego . Każde poniższe sprzężenie to nadal .) Spin A widzi wtedy cztery konfiguracje sąsiadów ; dwie mieszane pokrywają się, więc A rozszczepia się na tryplet 1:2:1 ( linie). Każdy spin X widzi tylko A i pozostaje dubletem. Równoważne spiny nie rozszczepiają się nawzajem, więc nie uwzględniamy sprzężenia X–X.
Prawdziwą cząsteczką o tym wzorze jest 1,1,2-trichloroetan (ClCH–CHCl). Gdy celujemy w spiny wodoru (co jest najczęstszą formą NMR), pojedynczy proton wodoru w CH pełni rolę A, a dwa równoważne protony CH to dwa spiny X. Teraz wystarczy policzyć sąsiadów: proton CH ma dwóch sąsiadów (parę CH), więc jego sygnał rozszczepia się na tryplet 1:2:1 (); każdy proton CH ma tylko jednego sąsiada (CH), więc sygnał CH to dublet 1:1 (). Zauważ, że grupa z mniejszą liczbą protonów (CH) wykazuje więcej linii; liczba linii odzwierciedla, ilu sąsiadów ma sygnał, a nie ile protonów go dzieli.
Poniżej symulujemy układ AX z trzema kubitami.
# 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)],
)
Sprawdź, czy rozumiesz
Przeczytaj każde pytanie, zastanów się nad odpowiedzią, a następnie kliknij trójkąt, aby odsłonić rozwiązanie.
W widmie AX dwa dublety są oddzielone (od środka do środka) o 12 Hz, a dwie linie w każdym dublecie dzieli 2 Hz. Która liczba to różnica przesunięć chemicznych, a która to stała sprzężenia ? Gdybyś przeszedł na spektrometr o dwukrotnie silniejszym polu magnetycznym, która z nich by się zmieniła?
Odpowiedź
Odstęp 12 Hz od środka do środka to różnica przesunięć chemicznych (); odstęp linii 2 Hz to stała sprzężenia . Przesunięcia chemiczne wynikają z ekranowania , więc skalują się z polem. Przy dwukrotnie silniejszym polu odstęp podwaja się do 24 Hz. Sprzężenie jest właściwością cząsteczki przenoszoną przez wiązania i nie zależy od pola, więc pozostaje 2 Hz. Chemicy odróżniają przesunięcie od sprzężenia, obserwując, jak rozszczepienie skaluje się z polem.
Korzystając z reguły , przewidź multiplet A, gdyby dodano trzeci równoważny spin X (układ AX), i wyjaśnij, dlaczego dwa równoważne spiny X w AX nie rozszczepiają się nawzajem.
Odpowiedź
Przy równoważnych sąsiadach A staje się kwartetem 1:3:3:1. Równoważne spiny nie rozszczepiają się nawzajem, ponieważ ich sprzężenie przesuwa odpowiednie poziomy energii o tę samą wartość, więc odstępy między tymi poziomami — czyli częstotliwości, które widzimy — się nie zmieniają.
Podsumowanie
Użyliśmy prawdziwego komputera kwantowego, aby zbadać, jak spiny jądrowe precesują i ulegają dekoherencji w eksperymencie NMR oraz jak interpretować otrzymane widma. Warto cofnąć się o krok i przypomnieć, dlaczego to w ogóle było możliwe i dlaczego to coś więcej niż ciekawostka.
Kubit i jądro o spinie 1/2 są matematycznie tym samym obiektem: dwupoziomowymi układami kwantowymi żyjącymi na sferze Blocha, obracanymi przez impulsy rezonansowe, precesującymi wokół osi i tracącymi spójność na rzecz otoczenia. Całe słownictwo tego modułu (częstotliwość Larmora, układ wirujący, , i , prążki Ramseya, echo Hahna) w równym stopniu należy do NMR i do obliczeń kwantowych. To, co chemik nazywa przesunięciem chemicznym, inżynier kwantowy nazywa jednokubitowym obrotem ; to, co chemik nazywa sprzężeniem , inżynier kwantowy nazywa oddziaływaniem . Obie dziedziny opisywały tę samą fizykę innymi słowami.
Ten związek jest historyczny, a nie tylko pojęciowy: NMR było jedną z pierwszych platform, na których uruchomiono prawdziwe algorytmy kwantowe. Do 2001 roku cząsteczka w roztworze wykonała algorytm Shora, rozkładając na czynniki liczbę 15, przy czym każdy spin jądrowy służył jako kubit. NMR przekazało też obliczeniom kwantowym dużą część ich zestawu narzędzi sterowania. Echo spinowe, ciągi impulsów CPMG, impulsy złożone i sprzężenie dynamiczne zostały wynalezione przez spektroskopistów na dziesięciolecia, zanim kubity potrzebowały ich do walki z dekoherencją. W tym, co właśnie zrobiliśmy, jest satysfakcjonująca symetria: dziedzina, która dała początek wczesnym komputerom kwantowym, jest teraz czymś, co komputer kwantowy potrafi symulować.
Powtórzenie i pytania
Kluczowe pojęcia
-
Kubit to zaprojektowany układ dwupoziomowy równoważny spinowi 1/2; jego , częstotliwość, bramki, i są bezpośrednimi odpowiednikami wielkości NMR.
-
Impuls rezonansowy obraca wektor Blocha; kąt odchylenia wyznacza obrót, a krzywa Rabiego/nutacji identyfikuje obroty i .
-
to relaksacja energii (spin-sieć); to relaksacja fazy (spin-spin); dodatkowo obejmuje odwracalną defazację statyczną, którą echo Hahna ogniskuje.
-
Cząsteczka to zbiór sprzężonych spinów: przesunięcie chemiczne wyznacza położenie każdego piku, a sprzężenie rozszczepia piki na multiplety zgodnie z regułą , co razem koduje środowisko chemiczne i łączność.
Prawda / fałsz
-
P/F Częstotliwość Larmora to przerwa energetyczna między dwoma stanami spinowymi podzielona przez .
-
P/F Impuls () w pełni odwraca spin w równowadze.
-
P/F jest zazwyczaj dłuższe niż .
-
P/F Echo Hahna ogniskuje nieodwracalną defazację spin-spin.
-
P/F W układzie AX jądro A pojawia się jako tryplet 1:2:1.
-
P/F Stała sprzężenia podwaja się, gdy podwoisz pole magnetyczne spektrometru.
-
P/F Usunięcie Qiskit Pulse uniemożliwia pomiar na obecnym sprzęcie.
Krótka odpowiedź
-
Wyjaśnij w dwóch zdaniach, dlaczego pojedynczy impuls jest kiepskim sposobem wykrycia błędu kąta odchylenia rzędu i jak łączenie impulsów to naprawia.
-
Próbka wykazuje szerokie linie NMR. Co to mówi w kategoriach i jaka wielkość kubitu jest jej odpowiednikiem?
-
Podaj odpowiednik kubitowy każdego z nich: (a) opóźnienie rekonwersji, (b) impuls , (c) przesunięcie chemiczne.
-
Korzystając z reguły , wyjaśnij, dlaczego grupa etylowa (CHCH–) daje tryplet 1:2:1 i kwartet 1:3:3:1, oraz wskaż, która grupa daje który multiplet.
Zadania wyzwań
-
Ciąg Carra-Purcella. Zmodyfikuj echo Hahna, wstawiając równo rozmieszczonych impulsów (ciąg w stylu CPMG) i zmierz w funkcji . Omów, dlaczego więcej impulsów ogniskujących może wydłużyć pozorne .
-
Wzmacnianie błędu impulsu. Zbuduj obwód, który stosuje razy do , i wykreśl względem dla celowo błędnie ustawionego kąta . Pokaż, jak błąd staje się widoczny wraz ze wzrostem . Wskazówka: umieść barierę po każdym obrocie; w przeciwnym razie transpiler może połączyć powtarzane obroty w jedną równoważną bramkę.
-
Grupa etylowa. Używając
fid_circuit, zbuduj widmo protonowe fragmentu etylowego (CH–CH): pięć kubitów, przy czym trzy protony CH mają wspólne przesunięcie chemiczne, a dwa protony CH inne, a każda para CH–CH jest sprzężona tym samym (bez sprzężenia w obrębie grupy równoważnej). Przewidź wzór za pomocą reguły , a następnie zasymuluj go; powinieneś otrzymać kwartet 1:3:3:1 (CH) i tryplet 1:2:1 (CH).
Dodatek (opcjonalny): ewolucja z dekompozycją Trottera — niech dekoherencja wyznacza szerokość linii
W sekcji Spektroskopia NMR cząsteczki ewolucja jest stosowana w jednej warstwie, której głębokość nie rośnie z , więc FID nie zanika sam i szerokość linii narzucamy oknem apodyzacji . Ta opcjonalna komórka przyjmuje podejście przeciwne. Dzieli ewolucję na kroków Trottera i umieszcza barierę po każdym kroku (aby transpiler nie mógł scalić ich z powrotem w jedną warstwę). Punkt czasowy używa kroków, więc głębokość obwodu rośnie z czasem.
Ponieważ wszystkie człony w komutują, jest to dokładne: na bezszumowym symulatorze daje te same ostre piki. Ale na Backend z modelem szumu lub na prawdziwym sprzęcie kumulujący się błąd bramek sam powoduje zanik FID i to ten zanik wyznacza szerokość linii. To idea szerokość linii z sekcji eksperyment , , teraz wynikająca z rzeczywistej dekoherencji zamiast z okna przetwarzania. W ten sposób uruchamiamy zarówno układ AX (dwa dublety), jak i układ AX (tryplet 1:2:1 plus dublet).
Dwie praktyczne uwagi. Po pierwsze, kosztem jest głębokość: najgłębszy obwód AX używa bramek dwukubitowych, a wersja AX około dwa razy tyle, więc oba są znacznie bardziej zaszumione i droższe niż wersja dokładna. Po drugie, każdy układ spinowy jest nadal przesyłany jako jedno zadanie, a każdy punkt czasowy ponownie używa tego samego układu fizycznych kubitów wybranego w sekcji spektroskopii. Jeśli USE_BATCH=True, zadania te korzystają też z opcjonalnego Batch obejmującego cały notatnik.
# 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()