codextrapped-ion-thermal-sideband-pulse.py
trapped-ion-thermal-sideband-pulse.py
"""Chosen Lamb-Dicke model and independent checks; Python 3.14/numpy 2.3/mpl 3.10.
Run from _media. Numerical inputs are teaching choices, not experimental data.
"""
import json
import math
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
MEAN_N = .25
ETA = .08
OMEGA = 2 * math.pi * 100_000 # Carrier Rabi angular frequency, rad/s.
NU = 2 * math.pi * 2_000_000 # Motional angular frequency, rad/s.
HBAR = 1.054571817e-34
KB = 1.380649e-23
def populations(mean_n, size=80):
n = np.arange(size)
r = mean_n / (mean_n + 1)
return (1-r) * r**n
def excitation(theta, p, blue=False):
n = np.arange(len(p)) + int(blue)
return np.sin(np.asarray(theta)[..., None] * np.sqrt(n) / 2)**2 @ p
def check_model():
p = populations(MEAN_N)
n = np.arange(len(p))
theta = np.linspace(0, 4*math.pi, 501)
red, blue = excitation(theta, p), excitation(theta, p, blue=True)
assert abs(p.sum()-1) < 2e-15
assert abs(n @ p - MEAN_N) < 2e-15
assert np.all((red >= 0) & (blue >= 0) & (red <= 1) & (blue <= 1))
ratio_error = np.max(abs(red - .2*blue))
assert ratio_error < 1e-14
# Independent 2x2 spectral exponentials check U†U and transition probabilities.
unitary_error = probability_error = 0.
for occupation in (0, 1, 2, 7):
h = np.array([[0., math.sqrt(occupation)], [math.sqrt(occupation), 0.]])/2
eigenvalues, vectors = np.linalg.eigh(h)
u = (vectors * np.exp(-1j*math.pi*eigenvalues)) @ vectors.T
unitary_error = max(unitary_error, float(np.max(abs(u.conj().T @ u-np.eye(2)))))
probability_error = max(probability_error, abs(abs(u[1, 0])**2-
math.sin(math.pi*math.sqrt(occupation)/2)**2))
assert unitary_error < 2e-15 and probability_error < 2e-15
temperature = HBAR*NU / (KB*math.log1p(1/MEAN_N))
recovered_mean = 1/math.expm1(HBAR*NU/(KB*temperature))
assert abs(recovered_mean-MEAN_N) < 1e-15
# Direct Poisson sums for the separate chosen fluorescence example, threshold 3.
bright_false_dark = math.exp(-12)*sum(12**k/math.factorial(k) for k in range(3))
dark_false_bright = sum(math.exp(-.2)*.2**k/math.factorial(k) for k in range(3, 40))
assert abs(dark_false_bright-(1-math.exp(-.2)*sum(.2**k/math.factorial(k) for k in range(3)))) < 1e-15
return {"population_sum": float(p.sum()), "mean_n": float(n@p),
"tail_bound": .2**len(p), "thermal_ratio_max_error": float(ratio_error),
"unitary_max_error": unitary_error, "spectral_probability_max_error": probability_error,
"red_pi_excitation": float(excitation(math.pi, p)),
"blue_pi_excitation": float(excitation(math.pi, p, blue=True)),
"pi_pulse_seconds": math.pi/(ETA*OMEGA), "temperature_kelvin": temperature,
"eta_rms": ETA*math.sqrt(2*MEAN_N+1),
"bright_false_dark": bright_false_dark, "dark_false_bright": dark_false_bright,
"equal_prior_readout_error": (bright_false_dark+dark_false_bright)/2}
def main():
checks = check_model()
p = populations(MEAN_N)
theta = np.linspace(0, 4*math.pi, 601)
plt.rcParams.update({"font.size": 12, "font.family": "DejaVu Sans"})
fig, axes = plt.subplots(1, 2, figsize=(11, 4.8), gridspec_kw={"width_ratios": [1, 1.65]},
layout="constrained", facecolor="white")
ax = axes[0]
ax.bar(np.arange(6), p[:6], color="#2563eb", width=.65)
ax.set(xlabel=r"Motional quantum number $n$", ylabel=r"Probability $p_n$",
title=r"Chosen thermal population: $\bar n=0.25$", ylim=(0, .9), xticks=np.arange(6))
ax.text(1.5, .7, r"$p_0=0.8$"+"\n"+r"$T=59.6\;\mu$K"+"\n"+r"$\nu/(2\pi)=2$ MHz", fontsize=12)
ax = axes[1]
ax.plot(theta/math.pi, excitation(theta, p, blue=True), color="#2563eb", lw=2.5,
label=r"Blue: $\sqrt{n+1}$ coupling")
ax.plot(theta/math.pi, excitation(theta, p), color="#dc2626", lw=2.5,
label=r"Red: $\sqrt{n}$ coupling")
ax.axvline(1, color="#64748b", ls="--", lw=1.4)
ax.scatter([1, 1], [checks["red_pi_excitation"], checks["blue_pi_excitation"]],
color=["#dc2626", "#2563eb"], zorder=5)
ax.text(1.08, .56, r"$n=1$ red $\pi$ pulse"+"\n"+r"$t=62.5\;\mu$s", fontsize=11)
ax.set(xlabel=r"Pulse area $\theta/\pi=\eta\Omega t/\pi$", ylabel="Internal excitation probability",
title="Same pulse duration for both sidebands", xlim=(0, 4), ylim=(0, 1.02))
ax.legend(loc="upper right", fontsize=10)
ax.grid(alpha=.18)
fig.suptitle(r"Ideal sideband model: $\eta=0.08$, $\Omega/(2\pi)=100$ kHz; no heating or decoherence",
fontsize=13)
fig.savefig(Path(__file__).stem + ".png", dpi=120, facecolor="white")
plt.close(fig)
print(json.dumps(checks, indent=2))
if __name__ == "__main__":
main()