メインコンテンツへスキップ
このプロジェクトはベータ版です。バグを見つけましたか?GitHub Issueを開く。質問やアイデアがありますか?Discussionsで質問する — フィードバックをお待ちしています!
このページの翻訳品質はいかがですか?

Qiskit による核磁気共鳴

この Qiskit in Classrooms モジュールでは、学生は次のパッケージがインストールされた動作する Python 環境を用意する必要があります。

  • qiskit v2.1 以降
  • qiskit-ibm-runtime v0.40.1 以降
  • qiskit-aer v0.17 以降
  • scipy と matplotlib(フィッティングとプロットのため)
  • pylatexenc(回路図のレンダリングのため)

上記のパッケージをセットアップしてインストールするには、Install Qiskit ガイドを参照してください。実際の量子コンピューターでジョブを実行するには、Set up your IBM Cloud® account ガイドの手順に従って、学生が IBM Quantum® のアカウントを設定する必要があります。

このモジュールはテスト済みで、QPU 時間を約 5 分使用します。これは見積もりであり、実際の使用量は異なる場合があります。

はじめに​

化学者が NMR 実験を行うとき、核スピンの試料を強い磁場の中に置き、ラジオ波(RF)パルスを印加して、返ってくる信号を測定します。この手法は、磁場中のスピン 1/2 粒子の量子力学に由来するいくつかの考え方に基づいています。スピンには 2 つのエネルギー準位があり、その間隔は特徴的なラーモア周波数に対応するエネルギーで隔てられています。その周波数での共鳴パルスはスピンを横平面に倒し、そこでスピンは歳差運動して信号を誘起し、その信号は 2 つの特徴的な時間 T1T_1 と T2T_2 で減衰していきます。

超伝導キュービットは、これと同じ対象を人工的に作り出したものであり、制御可能な 2 準位の量子系です。その ∣0⟩|0\rangle 状態と ∣1⟩|1\rangle 状態はスピンアップとスピンダウンに相当し、回転させるために適用するゲートは RF パルスに相当し、独自の T1T_1 と T2T_2 でコヒーレンスを失います。これは単なる大まかな比較ではありません。1990 年代後半から 2000 年代初頭にかけての最初期の実験的な量子プロセッサの一部は、NMR を使用していました。

このモジュールでは、実際の量子コンピューターを使用して、NMR 分光学者(またはハードウェアを較正する量子エンジニア)が行うのと同じ特性評価実験を実行します。

  • Rabi / 章動 — スピンを駆動し、それが回転する様子を観察します。これが π\pi または π/2\pi/2 回転を較正する方法です。

  • T1T_1 — スピン-格子緩和。励起されたスピンが平衡状態に戻る減衰です。

  • T2T_2 — スピン-スピン緩和(位相緩和)。Ramsey 実験で測定し、Hahn エコーで再収束させます。

  • NMR 分光(化学シフトと JJ カップリング) — 複数のキュービット上で小さな分子をシミュレーションし、その多重線スペクトルを読み取って、n+1n+1 則によって結合の接続関係を復元します。

各概念について、まず最小限の理論を示し、その後、4 段階の Qiskit patterns ワークフローに従って Qiskit で実験を実装します。

理論​

磁場中のスピン 1/2 粒子​

スピン 1/2 の原子核は磁気モーメント μ=γℏI\boldsymbol{\mu} = \gamma \hbar \mathbf{I} を持ちます。ここで γ\gamma は磁気回転比です。zz 方向の静磁場 B0B_0 の中では、2 つのスピン状態はエネルギーが分裂します(Zeeman 効果)。

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

このエネルギー差がラーモア(共鳴)周波数 ω0=γB0\omega_0 = \gamma B_0 を定めます。磁気モーメントは、この同じ周波数で B0B_0 の周りを歳差運動します。

スピンを回転させるには、ω0\omega_0 またはその近傍で振動する RF 磁場を印加します。駆動周波数で回転する座標系(回転座標系)では、共鳴駆動は Bloch ベクトルを xx-yy 平面内の軸の周りに回転させます。どれだけ回転するか(フリップ角)は、パルスの強さと持続時間で決まります。特に重要な選択は 2 つあります。90∘90^\circ パルスは +1/2+1/2 のスピンを横平面に倒し、180∘180^\circ パルスはそれを完全に −1/2-1/2 に回転させます。

NMR とクォンタム・コンピューティングの用語の対応​

キュービットは 2 準位系であり、数学的には磁場中のスピン 1/2 と同一です — したがって、原子核ではなく量子プロセッサ上で実行していても、その物理は NMR の根底にある物理と同じです。異なるのは用語だけです。この表は、物理化学者とクォンタム・コンピューティングの専門家の双方と話をする助けになります。

NMR / スピン 1/2量子プロセッサ(キュービット)
スピンアップ / スピンダウン∣0⟩\ket{0} / ∣1⟩\ket{1}
Zeeman ギャップ ΔE=ℏω0\Delta E = \hbar\omega_0キュービットの遷移エネルギー ℏω01\hbar\omega_{01}
ラーモア周波数 ω0\omega_0キュービット周波数 ω01\omega_{01}
共鳴 RF パルス(フリップ角 θ\theta)1 キュービット回転ゲート RX(θ)R_X(\theta)
90∘90^\circ / 180∘180^\circ パルスRX(π/2)R_X(\pi/2) / RX(π)R_X(\pi)
スピン-格子緩和 T1T_1キュービットのエネルギー緩和時間 T1T_1
スピン-スピン緩和 T2T_2キュービットの位相緩和時間 T2T_2
章動Rabi 振動
自由誘導減衰(FID)、Hahn エコーRamsey、エコーシーケンス

以下はすべて、右の列を測定し、それを左の列として読み解く演習です。

Qiskit 環境のセットアップ​

全体を通して、Qiskit patterns ワークフローに従います。

  1. 問題を量子回路にマッピングします。

  2. 実行のために回路を最適化します(バックエンドにトランスパイルします)。

  3. IBM Quantum のプリミティブを使用して実行します。

  4. 後処理して分析します。

まず、Runtime サービスを読み込み、最も空いている実機のバックエンドを選択します。まだ認証情報を保存していない場合は、ログイン認証情報の保存方法に関するドキュメントページを参照してください。

# 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

ノートブック全体で実機とローカルシミュレーターを切り替えるには、以下のセルを使用します。このモジュールの要点はハードウェアでの実行ですが、接続や使用上の問題がある場合は、シミュレーターをバックアップとして使えます。上で選択した QPU で実行するには、USE_HARDWARE = True に設定してください。

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

実験:Rabi 振動(章動)​

理論​

スピンを共鳴で駆動すると、その Bloch ベクトルは極から極へ一定に回転します — 章動します。∣0⟩|0\rangle から始まるキュービットに、xx 軸の周りにフリップ角 θ\theta の回転を適用すると、∣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).

これが Rabi 曲線です。その最初の極大が π\pi パルス(完全反転、180∘180^\circ パルス)です。π/2\pi/2 パルスは 4 分の 1 回転(スピンを横平面に倒す 90∘90^\circ パルス)です。NMR では、この曲線がパルスを較正する方法です。

実験上の注意

文字どおりの Rabi 実験では、駆動の振幅または持続時間を時間方向に掃引します。そのためにはパルスレベルの制御が必要ですが、これは Qiskit v2.0 で削除されました。ここでは代わりに、RX(θ)R_X(\theta) ゲートの回転角 θ\theta を掃引します。曲線と π\pi/π/2\pi/2 の対応は同一です。唯一の違いは、パルス長を較正する代わりに、角度を直接選ぶことです。分数ゲートを備えたハードウェアでは、任意の θ\theta に対する RX(θ)R_X(\theta) がネイティブに実行されます。

Bloch 球での見方​

以下のアニメーションは章動を示しています。共鳴駆動が Bloch ベクトルを xx 軸の周りに一定に回転させ、∣0⟩|0\rangle から ∣1⟩|1\rangle の方へ下り、また戻ります。これはまさに Rabi 曲線が測定している動きです。

問題を回路にマッピングする​

パラメータ化された回路を 1 つ構築します。RX(θ)R_X(\theta) に測定が続く回路で、後で多数の角度で一度に評価します。

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

実行のための最適化​

選択したバックエンド向けの命令セットアーキテクチャ(ISA)回路にトランスパイルします。

rabi_isa = pm_q.run(rabi)

実行​

θ\theta を 00 から 4π4\pi まで(Rabi サイクル 2 周分)掃引します。パラメータ値の配列を 1 つの Primitive Unified Bloc(PUB)として渡すと、それらすべてを 1 つのジョブで実行します。

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

後処理と分析​

各角度で P1P_1 を抽出し、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

理解度チェック​

測定した曲線では、P1P_1 は π\pi パルス(θ=π\theta = \pi)で最初に最大値に達します。ここで、あるデバイスが誤較正されていて、x 軸上の公称角度 θ\theta が実際には 1.05 θ1.05\,\theta の回転を生じると仮定します。

(a) P1P_1 の最初の極大は、公称角度がいくつのところに現れますか。 (b) 単一の π\pi パルスを適用することは、この 5% の誤差を検出する感度の高い方法でしょうか。

解答

(a) 極大は実際の回転が π\pi に等しくなるところ、つまり公称角度 θ=π/1.05≈0.95π\theta = \pi/1.05 \approx 0.95\pi に現れます — ピークがわずかに左へシフトします。

(b) いいえ。公称 π\pi パルスは実際には 1.05π1.05\pi の回転を生じ、P1=sin⁡2(1.05π/2)≈0.994P_1 = \sin^2(1.05\pi/2) \approx 0.994 となり、11 にはなりません — 測定ノイズに対して見分けるには小さすぎる変化です。このため、正確に較正するには、NMR 分光学者は回転を何度も適用して(たとえば 360∘360^\circ 法や長いパルス列)誤差を増幅します。これにより、パルスごとの小さな誤差が蓄積して、測定可能な大きなずれになります。この考え方は、チャレンジ問題で再び取り上げます。

実験:T1T_1(スピン-格子緩和)​

理論​

励起されたスピンは、放置するとそのエネルギーを周囲(「格子」)に放出し、熱平衡へと緩和していきます。励起状態の占有率は、スピン-格子緩和時間 T1T_1 で指数関数的に減衰します。

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

化学者にとって、T1T_1 は定量NMRでスキャンの間に待つ必要があるリサイクル遅延を決めるものであり、分子のタンブリングや動力学についての情報も与えてくれます。量子ビットでは、これはエネルギー緩和時間であり、量子コンピューターにおけるデコヒーレンスの影響をモデル化するために使われる2つの主要な数値のうちの1つです。

実験の手順。 XX ゲート(π\pi パルス)で ∣1⟩|1\rangle を準備し、可変の遅延 τ\tau だけ待ってから測定します。∣1⟩|1\rangle にとどまっている割合が減衰曲線を描きます。

ブロッホ球で見る​

T1T_1 緩和により励起状態は平衡状態に戻るため、ブロッホベクトルは zz 軸に沿って ∣1⟩|1\rangle から ∣0⟩|0\rangle へと戻っていきます。

遅延スキャンの構築とトランスパイル​

遅延時間は具体的な値でなければならない(ハードウェア時間でスケジュールされる)ため、遅延ごとに1つの回路を構築します。

# 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

実行​

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

解析 — 指数関数でフィットする​

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

正確な T1T_1 の値は実験ごと、バックエンドごとに異なりますが、数百マイクロ秒程度の結果が得られるはずです。

理解度チェック​

定量NMRでは、スキャンの間に約 5 T15\,T_1 待つように指示されます。減衰の法則を用いて、5 T15\,T_1 後に平衡磁化のどれだけの割合が回復しているか求めてください。

解答

5 T15\,T_1 後、平衡からの残りのずれは e−5≈0.0067e^{-5}\approx 0.0067 であり、つまり約 99.3% 回復しています。

実験: T2T_2、T2∗T_2^*​

理論​

T1T_1 はエネルギーの損失を測ります。T2T_2 は横平面における位相コヒーレンスの損失を測ります。これはラムゼー実験で観測できます。π/2\pi/2 パルスがすべてのスピンを赤道面まで倒し、すべてが同じ向きを指します。スピンはラーモア歳差運動をしますが、それぞれがわずかに異なる周波数で歳差運動するため、赤道面上で扇状に広がり、正味の横方向の信号は特性減衰時間 T2∗T_2^* で減衰します。

量子分野の読者向けの補足: 扇状に広がる描像は、コヒーレンスを失う一つの方法、つまり平均化されて消える確定した位相の広がりを示しています。ただし、これが唯一の機構ではないことは知っておく価値があります。位相が環境とエンタングルしてしまい、系がそもそも確定した値を保持していない、という形でもコヒーレンスは失われます。信号の減衰を見ているだけでは2つの機構を区別できないため、扇状に広がる描像は直観的な理解として有用です。ただし、完全で文字通りの説明として受け取らないよう注意してください。

スピンの位相を乱す効果は2つあります。

  • 他の自由度との制御できない結合から生じる、不可逆(または事実上不可逆)なデコヒーレンス。真の T2T_2 で特徴づけられます。NMRでは、これは一般にスピン間の結合を変調する分子運動に由来します。量子コンピューティングでは、量子ビットと環境との間の望ましくない結合に相当します。
  • 静的またはゆっくり変化する可逆な周波数オフセット。磁場の不均一性、量子ビット間の周波数のばらつき、較正誤差などです。真の T2T_2 と組み合わさって、ラムゼー実験で観測されるより短い見かけの減衰時間 T2∗T_2^* を生じさせます。

では、ラムゼー実験で分かるのが T2∗T_2^* だけなら、真の T2T_2 はどのように求めればよいのでしょうか。ハーン・エコーと呼ばれる2つ目の実験は、第2の種類のノイズをすべて打ち消して真の T2T_2 を求めます。最初はラムゼー実験と同じく π/2\pi/2 パルスで始まり、スピンが先ほどと同様に扇状に広がります。時間 τ\tau の後に π\pi パルスを加えます。このパルスは静的な周波数オフセットによる位相の蓄積を反転させるため、同じ長さ τ\tau の2回目の遅延の後にスピンが再び収束します。

T2∗T_2^* と T2T_2 は、化学の場面でも量子コンピューティングの場面でも、どちらも知っておくべき重要な数値です。化学の場面では、T2∗T_2^* が標準的なNMR実験の線幅を決めます(これが何を意味するかは、後のセクションで見ます)。T2T_2 は分子のタンブリングなど分子について多くのことを教えてくれ、T2T_2 の変化は、分子がより大きなものに結合したことを示す場合があります。量子コンピューティングの場面では、デコヒーレンスが系を圧倒する前により多くの時間を実験に使えるよう、一般に T2T_2 と T2∗T_2^* の両方をできるだけ長くしたいと考えます。ハーン・エコーに基づく一群の技法は、可逆なノイズを打ち消すことでこの時間を延ばし、回路が理想的には T2∗T_2^* ではなく真の T2T_2 で制限されるようにします。これは「動的デカップリング」と呼ばれます。

実装​

量子ビットでラムゼー実験とハーン・エコー実験を実装する場合、読み出しはNMRの場合とは少し異なります。NMRでは、横平面でラーモア周波数で歳差運動するスピンが誘起する振動磁場を直接観測できます。しかし量子ビットでは、ZZ 軸への射影、つまり ∣0⟩\vert 0 \rangle か ∣1⟩\vert 1 \rangle かしか測定できません。そのため、どちらの実験でも、最後にもう1つの π/2\pi/2 パルスを加えてから測定します。この π/2\pi/2 パルスは、量子ビットの位相情報を占有率へと変換します。

以下で、T2∗T_2^* と T2T_2 を求めるために各実験を実行します。

ラムゼー (T2∗T_2^{*}): 遅延時間 τ\tau を挟む2つの π/2\pi/2 パルス​

最初の π/2\pi/2 パルスはブロッホベクトルを赤道面に倒し、次のパルスはブロッホベクトルの位相を占有率に変換します。ブロッホベクトルが歳差運動するにつれて位相は時間とともに変化するため、占有率は歳差時間 τ\tau の関数として正弦波状のパターンを示します。量子ビットが位相コヒーレンスを失うにつれて、この正弦曲線の振幅が減衰するのが見えるはずです。

量子分野の読者向けのもう少し細かい点: 量子コンピューティングの実験で見える振動は、実際にはラーモア周波数のものではありません。ラーモア周波数はGHz帯で、速すぎて分解できないからです。量子実験は回転座標系で行われます。これは量子ビットと(ほぼ)ラーモア周波数で一緒に回転する座標系で、速い歳差運動は見えなくなり、ブロッホベクトルはほぼ静止して見えます。干渉縞を見るために、回転座標系をラーモア周波数からわずかにずらして意図的に設定します。すると、ブロッホベクトルはその小さな離調で歳差運動しているように見え、離調周波数の振動信号が得られます。以下では、離調を1つの値に固定せず、(量子ビットのコヒーレンス時間に合わせて決めた)測定ウィンドウに約12本の干渉縞が収まるように選びます。これにより、エイリアシングなしで振動を容易にサンプリングできます。もう一つの複雑な点として、Qiskitでは、(回転座標系の周波数を決める)パルス周波数を変更するのに必要なパルスレベルの制御ができません。代わりに、2つ目のラムゼー・パルスの位相を設定することで、実効的な離調を手動で加えます。やや作為的ですが、結果は同じであり、観測される減衰は依然として本物の T2∗T_2^* です。

ブロッホ球で見る​

π/2\pi/2 パルスがスピンを赤道面に倒します。その後、各スピンはわずかに異なる速度で歳差運動して扇状に広がるため、位相が乱れるにつれて正味の(赤い)ベクトルは縮みます。この減衰が 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

ハーン・エコー (T2T_2): 中間に再収束用の π\pi パルスを加える​

ブロッホ球で見る​

ハーン・エコー: スピンは先ほどと同様に扇状に広がり、その後 π\pi パルスがスピンを反転させるため、その後も続く歳差運動によってスピンが再び収束し(赤いベクトルが再び伸びます)、静的な位相緩和で失われたコヒーレンスが回復します。

# 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

解析: 2つの減衰をフィットして比較する​

# 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

ここでは、ラムゼー振動とハーン・エコー信号の両方が、予想どおり減衰しているのが分かります。これらの減衰に対応する時間スケールが、それぞれ T2∗T_2^* と T2T_2 です。

理解度チェック​

通常は T2≳T2∗T_2 \gtrsim T_2^{*} となるはずです。その理由を1文で説明し、ハーン・エコーの π\pi パルスがどのような物理的効果を取り除くか、そして何を取り除けないかを述べてください。

解答

T2∗T_2^{*} は不可逆な位相緩和と可逆な静的広がりの両方を含むため、より速く減衰します。エコーの π\pi パルスは静的で可逆な位相緩和(磁場の不均一性や固定周波数のオフセット)を再収束させ、不可逆なスピン-スピン位相緩和だけを残します。これが真の T2T_2 です。エコーは、系列の前半と後半の間で変化するゆらぎを元に戻すことはできません。

分子のNMR分光: 化学シフトと JJ カップリング​

1つのスピンから分子へ​

ここまでの実験では、単一の孤立したスピンを特徴づけてきました。実際の化学NMRでは分子(結合した多数のスピンを同時に)を読み取り、得られたスペクトルから分子構造の特徴を推測します。これにより、2つの効果が分かります。

  • 化学シフト。 化学的に異なる各核は、局所的な電子が印加磁場を異なる量だけ遮蔽するため、わずかに異なる周波数 νi\nu_i で歳差運動します。したがって、ピークの位置から化学環境が分かります(たとえば、CH3_3 のプロトンと芳香族のプロトンの違いなど)。

  • スカラー JJ カップリング。 2つのスピンの間の結合を介した相互作用により、各ピークが多重線に分裂します。nn 個の等価な隣接核を持つ核は、二項分布の強度を持つ n+1n+1 本の線に分裂します。これが n+1n+1 則です。分裂のパターンは結合関係(どの核が結合しているか)を明らかにし、間隔が結合定数 JJ です。

実験の手順​

これを量子コンピューターでモデル化するには、量子ビット上にスピンハミルトニアンを実装する必要があります。弱結合・高磁場の極限(化学シフトの差 ≫\gg カップリング)では、カップリングの XXXX 部分と YYYY 部分は平均化されて消え、ZZZZ だけが残ります。したがってハミルトニアンは次のようになります(角周波数の単位、ℏ=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}}.

各核は1つの量子ビットです。化学シフトは単一量子ビットの ZZ 回転であり、カップリングは2量子ビットの ZZZZ 相互作用です。

実験は次のように進みます。π/2\pi/2 パルスがすべてのスピンを横平面に倒し、スピンは時間 tt の間、歳差運動とカップリングを行います。すべてのスピンについて合計した複素横磁化 s(t)=⟨X⟩+i⟨Y⟩s(t) = \langle X\rangle + i\langle Y\rangle を記録します。上のラムゼー実験と同様に、この横磁化は歳差時間 tt の関数として減衰する正弦波を描きます。この信号のフーリエ変換を取ると、化学シフト νi\nu_i にピークを持ち、スカラーカップリング JijJ_{ij} で分裂したスペクトルが得られます。

備考

分子の時間発展は、時間 tt を回転角に対応させることでシミュレーションします。tt が大きいほど ZZ 回転と ZZZZ 回転が大きくなるのであって、回路が長くなったり深くなったりするわけではありません。したがって、シミュレーションされる状態は tt とともに進みますが(これが自由誘導減衰(FID)を振動させる理由です)、物理的な量子ビットがそれ以上長く待機することはなく、tt に依存するデコヒーレンスもありません。そのため、シミュレーションされたFIDはそれ自体では減衰しません。無限に長い理想的なFIDであればスペクトル線は無限に鋭くなりますが、有限のサンプリングウィンドウでは有限の分解能になり、スペクトル漏れが生じることがあります。実際のNMRの線は、T2∗T_2^* で決まる有限の幅を持ちます。これを補うために、フーリエ変換の前にFIDへウィンドウ e−t/T2∗e^{-t/T_2^{*}} を掛けます。これは線幅拡大アポダイゼーションと呼ばれ、分光学者が線幅を設定するために処理で適用します(分解能と信号対雑音比のトレードオフになります)。(このモジュールの付録にあるオプションのセルでは、トロッター化した代替手法を示します。そこでは、回路の深さとともに増大する実際のデコヒーレンスによって減衰が生じます。)

AX系​

まず、いわゆるAX系から始めましょう。これは、結合した2つの化学的に異なるスピンからなる分子です。AとXというラベルは、2つの化学的に異なるスピン環境を表します。文字自体は任意ですが、アルファベット上で離れていることが、化学的な違いを表しています。

マッピングと最適化​

化学シフトのオフセット νi\nu_i とカップリング JijJ_{ij} が与えられたとき、パラメーター化された状態準備回路を返す関数を1つ書きます。π/2\pi/2 パルスがスピンを赤道面に倒し、その後 ZZ 回転が、化学シフト項と JJ カップリング項のもとでの状態の時間発展をシミュレーションします。各 νi\nu_i は、選んだ基準(回転座標系のキャリア)に対する、Hz単位の小さな歳差オフセットです。ここでは手動で設定しますが、実際の分子では、電子による遮蔽が局所磁場を変え、基準に対する各核の共鳴をずらします。これは、スピンごとに1つずつの単一量子ビット RZR_Z ゲートでモデル化され、各 νi\nu_i がそのスピンのピークがスペクトル上のどこに位置するかを決めます。2量子ビットの ZZZZ ゲートはカップリング JijJ_{ij} を加え、各ピークを多重線に分裂させ、線の間隔を決めます。

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

実行と後処理​

時間発展の時刻のグリッド上で回路を評価し、Estimator が各時刻での ⟨X⟩\langle X\rangle と ⟨Y⟩\langle Y\rangle を返します。これらを複素FID ⟨X⟩+i⟨Y⟩\langle X\rangle + i\langle Y\rangle にまとめ、アポダイゼーション・ウィンドウを適用し、結果をフーリエ変換してスペクトルを得ます。

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

拡張: AX2_2 系と n+1n+1 則​

次に、2つ目の等価な隣接核を加えます。AX2_2 系では、1つの A スピンが、同じ JJ で2つの等価な X スピンと結合します。(Xは2つの等価なスピンを表すただのラベルであり、パウリ XX 演算子とは関係がありません。以下のカップリングはすべて ZZZZ のままです。)このときスピンAには4通りの隣接核の配置 (↑↑, ↑↓, ↓↑, ↓↓)(\uparrow\uparrow,\ \uparrow\downarrow,\ \downarrow\uparrow,\ \downarrow\downarrow) が見え、混合した2つは一致するため、Aは 1:2:1 の三重線に分裂します(n=2⇒n+1=3n=2 \Rightarrow n+1 = 3 本の線)。各XスピンはAだけを見るため、二重線のままです。等価なスピン同士は互いを分裂させないため、X–X カップリングは含めません。

このパターンを持つ実在の分子が 1,1,2-トリクロロエタン(Cl2_2CH–CH2_2Cl)です。水素スピンを対象とする場合(最も一般的なNMRの形です)、CH の1個の水素プロトンが A の役割を果たし、2個の等価な CH2_2 プロトンが2つの X スピンです。ここで隣接核を数えてみましょう。CHプロトンには2つの隣接核(CH2_2の対)があるため、その信号は 1:2:1 の三重線(n=2n=2)に分裂します。各CH2_2プロトンには1つの隣接核(CH)しかないため、CH2_2の信号は 1:1 の二重線(n=1n=1)になります。プロトンの数が少ない基(CH)のほうが多くの線を示すことに注意してください。線の数は、その信号が持つ隣接核の数を反映するのであって、その信号を共有するプロトンの数ではありません。

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

以下では、3量子ビットでAX2_2系をシミュレーションします。

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

理解度チェック​

各問題を読み、答えを考えてから、三角形をクリックして解答を表示してください。

AXスペクトルでは、2つの二重線が(中心間で)12 Hz離れており、各二重線内の2本の線は2 Hz離れています。化学シフトの差はどちらの数値で、カップリング定数 JJ はどちらでしょうか。磁場が2倍の分光計に移った場合、どちらが変化するでしょうか。

解答

12 Hzの中心間の間隔が化学シフトの差(ν1−ν2\nu_1-\nu_2)で、2 Hzの線の間隔がカップリング定数 JJ です。化学シフトは B0B_0 の遮蔽に由来するため、磁場に比例してスケールします。磁場が2倍になると間隔は24 Hzに倍増します。カップリング JJ は結合を介した分子の性質であり、磁場に依存しないため、2 Hzのままです。化学者は、分裂が磁場に対してどうスケールするかを見ることで、シフトとカップリングを見分けます。

n+1n+1 則を用いて、3つ目の等価なXスピンが加わった場合(AX3_3 系)のAの多重線を予測し、AX2_2 にある2つの等価なXスピンが互いを分裂させない理由を説明してください。

解答

n=3n=3 個の等価な隣接核があるため、Aは1:3:3:1の四重線になります。等価なスピン同士が互いを分裂させないのは、それらのカップリングが関連するエネルギー準位をすべて同じ量だけずらすため、それらの準位の間隔(私たちが観測する周波数)が変化しないからです。

まとめ​

私たちは実際の量子コンピューターを使って、NMR実験で核スピンがどのように歳差運動しデコヒーレンスを起こすか、そして得られたスペクトルをどのように解釈するかを探ってきました。そもそもなぜこれが可能だったのか、そしてなぜそれが単なる珍しい話にとどまらないのかを、ここで振り返っておく価値があります。

量子ビットとスピン1/2の核は、数学的には同じ対象です。どちらもブロッホ球上にある2準位の量子系で、共鳴パルスによって回転し、ある軸の周りを歳差運動し、周囲の環境にコヒーレンスを失っていきます。このモジュールの語彙のすべて(ラーモア周波数、回転座標系、T1T_1、T2T_2、T2∗T_2^*、ラムゼー縞、ハーン・エコー)は、NMRと量子コンピューティングの両方に等しく属しています。化学者が化学シフトと呼ぶものを、量子エンジニアは単一量子ビットの ZZ 回転と呼び、化学者が*JJ カップリングと呼ぶものを、量子エンジニアはZZZZ 相互作用*と呼びます。2つの分野は、同じ物理を異なる言葉で記述してきたのです。

このつながりは概念的なものにとどまらず、歴史的なものでもあります。NMRは、実際の量子アルゴリズムを実行した最初のプラットフォームの1つでした。2001年までに、溶液中の分子がショアのアルゴリズムを実行して15を因数分解しており、各核スピンが量子ビットとして機能していました。NMRはまた、量子コンピューティングに制御ツールの多くをもたらしました。スピンエコー、CPMGパルス列、複合パルス、動的デカップリングは、量子ビットがデコヒーレンスと戦うためにそれらを必要とするより数十年も前に、分光学者によって考案されたものです。私たちが今行ったことには、満足のいく対称性があります。初期の量子コンピューターを生み出した分野が、今では量子コンピューターでシミュレーションできるものになったのです。

復習と問題​

重要な概念​

  • 量子ビットはスピン1/2と等価な、設計された2準位系です。その ∣0⟩/∣1⟩|0\rangle/|1\rangle、周波数、ゲート、T1T_1、T2T_2 は、NMRの量に直接対応するものです。

  • 共鳴パルスはブロッホベクトルを回転させます。フリップ角が回転を決め、ラビ/章動曲線 P1=sin⁡2(θ/2)P_1=\sin^2(\theta/2) によって π\pi 回転と π/2\pi/2 回転が特定されます。

  • T1T_1 はエネルギー(スピン-格子)緩和、T2T_2 は位相(スピン-スピン)緩和です。T2∗T_2^{*} にはさらに、ハーン・エコーが再収束させる可逆な静的位相緩和が含まれます。

  • 分子は結合したスピンの集まりです。化学シフトが各ピークの位置を決め、JJ カップリングが n+1n+1 則に従ってピークを多重線に分裂させ、これらが合わさって化学環境と結合関係を符号化します。

正誤問題​

  1. T/F ラーモア周波数は、2つのスピン状態間のエネルギー差を ℏ\hbar で割ったものである。

  2. T/F 90∘90^\circ(π/2\pi/2)パルスは、平衡状態のスピンを完全に反転させる。

  3. T/F T2∗T_2^{*} は一般に T2T_2 よりも長い。

  4. T/F ハーン・エコーは、不可逆なスピン-スピン位相緩和を再収束させる。

  5. T/F AX2_2 系では、A核は1:2:1の三重線として現れる。

  6. T/F 分光計の磁場を2倍にすると、カップリング定数 JJ も2倍になる。

  7. T/F Qiskit Pulseが削除されたことで、現行のハードウェアで T1T_1 を測定することは不可能になった。

短答問題​

  1. 単一の π\pi パルスが 5%5\% のフリップ角誤差を検出する方法として不向きな理由と、パルスを連鎖させることでこれがどう解決されるかを、2文で説明してください。

  2. ある試料が幅の広いNMR線を示しています。T2T_2 の観点から、これは何を意味し、それに対応する量子ビットの量は何でしょうか。

  3. 次のそれぞれに対応する量子ビットの類似物を答えてください。(a) リサイクル遅延、(b) 90∘90^\circ パルス、(c) 化学シフト。

  4. n+1n+1 則を用いて、エチル基(CH3_3CH2_2–)が1:2:1の三重線と1:3:3:1の四重線を示す理由を説明し、どの基がどの多重線を生じるかを特定してください。

チャレンジ問題​

  1. カー・パーセル列。 ハーン・エコーを修正して、等間隔の nn 個の π\pi パルス(CPMG型のパルス列)を挿入し、nn の関数として T2T_2 を測定してください。再収束パルスを増やすと見かけの T2T_2 が長くなりうる理由を論じてください。

  2. パルス誤差の増幅。 ∣0⟩|0\rangle に RX(π)R_X(\pi) を  n\,n 回適用する回路を構築し、意図的に誤設定した角度 RX(1.05π)R_X(1.05\pi) について、nn に対する P1P_1 をプロットしてください。nn が増えるにつれて誤差が見えるようになることを示してください。ヒント: 各回転の後にバリアを置いてください。そうしないと、トランスパイラーが繰り返された回転を等価な単一のゲートにまとめてしまうことがあります。

  3. エチル基。 fid_circuit を使って、エチル断片(CH3_3–CH2_2)のプロトンスペクトルを構築してください。5量子ビットで、3つのCH3_3プロトンが1つの化学シフトを共有し、2つのCH2_2プロトンが別の化学シフトを共有し、すべてのCH3_3–CH2_2の組が同じ JJ で結合します(等価な基の内部にはカップリングなし)。n+1n+1 則でパターンを予測してからシミュレーションしてください。1:3:3:1の四重線(CH2_2)と1:2:1の三重線(CH3_3)が得られるはずです。

付録(オプション): トロッター化された時間発展 — デコヒーレンスに線幅を決めさせる​

分子のNMR分光のセクションでは、時間発展 e−iHte^{-iHt} は tt とともに深さが増えない単一の層として適用されるため、FIDはそれ自体では減衰せず、線幅は e−t/T2e^{-t/T_2} のアポダイゼーション・ウィンドウで与えました。このオプションのセルでは、逆のアプローチを取ります。時間発展を pp 個のトロッター・ステップに分割し、各ステップの後にバリアを置きます(トランスパイラーがそれらを1つの層にまとめ直せないようにするためです)。時間点 kk は kk ステップを使うため、回路の深さは時間とともに増えます。

HH のすべての項は可換なので、これは厳密です。ノイズのないシミュレーターでは、同じ鋭いピークが得られます。しかし、ノイズモデルのバックエンドや実機では、蓄積するゲート誤差によってFIDがひとりでに減衰し、その減衰が線幅を決めます。これは、T2T_2、T2∗T_2^* 実験のセクションにある T2↔T_2\leftrightarrow線幅という考え方であり、今回は処理ウィンドウではなく実際のデコヒーレンスから生じています。この方法で、AX系(2つの二重線)と**AX2_2**系(1:2:1の三重線と二重線)の両方を実行します。

実用上の注意点が2つあります。第一に、コストは深さです。最も深いAX回路は約 ∼ ⁣47\sim\!47 個の2量子ビットゲートを使い、AX2_2 版はその約2倍になるため、どちらも厳密版よりはるかにノイズが多く、コストも高くなります。第二に、各スピン系は引き続き1つのジョブとして送信され、すべての時間点は分光のセクションで選んだものと同じ物理量子ビットのレイアウトを再利用します。USE_BATCH=True の場合、これらのジョブはノートブック全体で共有するオプションのバッチも使用します。

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