|
| 1 | +""" |
| 2 | +Experiment 251: Atom Interferometer Phase Shift |
| 3 | +
|
| 4 | +This example demonstrates atom interferometry using light-pulse techniques. |
| 5 | +We explore the phase shifts induced by various physical effects and show |
| 6 | +how atom interferometers measure acceleration, rotation, and gravity. Topics: |
| 7 | +- Mach-Zehnder atom interferometer geometry |
| 8 | +- Phase accumulation from inertial effects |
| 9 | +- Gravity measurement (gravimeter) |
| 10 | +- Sagnac effect for rotation sensing |
| 11 | +
|
| 12 | +Key physics: |
| 13 | +- Beam splitter: pi/2 pulse transfers superposition of momentum states |
| 14 | +- Mirror: pi pulse swaps momentum states |
| 15 | +- Free evolution: momentum states separate and accumulate phase |
| 16 | +- Phase shift from acceleration: phi = k_eff * a * T^2 |
| 17 | +""" |
| 18 | + |
| 19 | +import sys |
| 20 | +import os |
| 21 | +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) |
| 22 | + |
| 23 | +import numpy as np |
| 24 | +import matplotlib.pyplot as plt |
| 25 | +from src.sciforge.physics.amo import HBAR, KB, C, M_PROTON |
| 26 | + |
| 27 | +def simulate_atom_interferometer(): |
| 28 | + """Simulate Mach-Zehnder atom interferometer.""" |
| 29 | + |
| 30 | + # Rubidium-87 parameters |
| 31 | + wavelength = 780e-9 # m |
| 32 | + k = 2 * np.pi / wavelength # Wave vector |
| 33 | + k_eff = 2 * k # Effective wave vector for two-photon transition |
| 34 | + m_Rb = 87 * M_PROTON # Rb-87 mass |
| 35 | + v_recoil = HBAR * k / m_Rb # Recoil velocity |
| 36 | + |
| 37 | + results = {} |
| 38 | + results['k_eff'] = k_eff |
| 39 | + results['m'] = m_Rb |
| 40 | + results['v_recoil'] = v_recoil |
| 41 | + results['wavelength'] = wavelength |
| 42 | + |
| 43 | + print(f"Effective wave vector k_eff: {k_eff:.2e} m^-1") |
| 44 | + print(f"Recoil velocity: {v_recoil*1e3:.3f} mm/s") |
| 45 | + |
| 46 | + # 1. Interferometer geometry |
| 47 | + print("\nSimulating Mach-Zehnder geometry...") |
| 48 | + |
| 49 | + # Time between pulses |
| 50 | + T = 100e-3 # 100 ms interrogation time (typical for gravimeters) |
| 51 | + g = 9.81 # m/s^2 |
| 52 | + |
| 53 | + # Phase shift from gravity |
| 54 | + phi_g = k_eff * g * T**2 |
| 55 | + print(f"Gravitational phase shift: {phi_g:.2f} rad = {phi_g/(2*np.pi):.2f} fringes") |
| 56 | + |
| 57 | + results['T'] = T |
| 58 | + results['g'] = g |
| 59 | + results['phi_g'] = phi_g |
| 60 | + |
| 61 | + # Trajectory calculation |
| 62 | + t_total = 2 * T |
| 63 | + t = np.linspace(0, t_total, 1000) |
| 64 | + |
| 65 | + # Upper arm: receives +hbar*k_eff at t=0 (after pi/2 pulse) |
| 66 | + # Lower arm: stays in original momentum state |
| 67 | + |
| 68 | + # Initial position z0=0, initial velocity v0=1 m/s (upward launch) |
| 69 | + v0 = 2.0 # m/s upward |
| 70 | + z0 = 0 |
| 71 | + |
| 72 | + # Lower arm trajectory (free fall, no recoil) |
| 73 | + z_lower = z0 + v0 * t - 0.5 * g * t**2 |
| 74 | + |
| 75 | + # Upper arm trajectory (receives recoil kick at t=0) |
| 76 | + z_upper = z0 + (v0 + v_recoil) * t - 0.5 * g * t**2 |
| 77 | + |
| 78 | + # At t=T (pi pulse), arms swap momentum |
| 79 | + # After pi pulse, upper arm now has lower momentum |
| 80 | + t_pi = T |
| 81 | + idx_pi = np.argmin(np.abs(t - t_pi)) |
| 82 | + |
| 83 | + z_upper_after = np.zeros_like(t) |
| 84 | + z_lower_after = np.zeros_like(t) |
| 85 | + z_upper_after[:idx_pi+1] = z_upper[:idx_pi+1] |
| 86 | + z_lower_after[:idx_pi+1] = z_lower[:idx_pi+1] |
| 87 | + |
| 88 | + # After pi pulse: swap trajectories (with continuity) |
| 89 | + z_at_pi_upper = z_upper[idx_pi] |
| 90 | + z_at_pi_lower = z_lower[idx_pi] |
| 91 | + v_upper_before = v0 + v_recoil - g * t_pi |
| 92 | + v_lower_before = v0 - g * t_pi |
| 93 | + |
| 94 | + # After swap: upper arm now moves with lower velocity, lower moves with upper |
| 95 | + for i in range(idx_pi + 1, len(t)): |
| 96 | + dt = t[i] - t_pi |
| 97 | + z_upper_after[i] = z_at_pi_upper + v_lower_before * dt - 0.5 * g * dt**2 |
| 98 | + z_lower_after[i] = z_at_pi_lower + v_upper_before * dt - 0.5 * g * dt**2 |
| 99 | + |
| 100 | + results['trajectories'] = { |
| 101 | + 't': t, |
| 102 | + 'z_upper': np.where(t <= t_pi, z_upper, z_upper_after), |
| 103 | + 'z_lower': np.where(t <= t_pi, z_lower, z_lower_after), |
| 104 | + 't_pi': t_pi |
| 105 | + } |
| 106 | + |
| 107 | + # 2. Phase shift vs interrogation time |
| 108 | + print("Computing phase shift vs interrogation time...") |
| 109 | + T_range = np.linspace(10e-3, 200e-3, 100) |
| 110 | + phi_vs_T = k_eff * g * T_range**2 |
| 111 | + |
| 112 | + results['phase_vs_T'] = { |
| 113 | + 'T': T_range, |
| 114 | + 'phi': phi_vs_T |
| 115 | + } |
| 116 | + |
| 117 | + # 3. Sensitivity calculation |
| 118 | + # Shot noise limited sensitivity: delta_g / g = 1 / (k_eff * g * T^2 * sqrt(N)) |
| 119 | + N_atoms = 1e6 # Number of atoms per shot |
| 120 | + cycle_time = 1.0 # 1 second per measurement |
| 121 | + |
| 122 | + delta_phi = 1 / np.sqrt(N_atoms) # Phase sensitivity |
| 123 | + delta_g = delta_phi / (k_eff * T_range**2) # Gravity sensitivity |
| 124 | + |
| 125 | + results['sensitivity'] = { |
| 126 | + 'T': T_range, |
| 127 | + 'delta_g': delta_g, |
| 128 | + 'N_atoms': N_atoms |
| 129 | + } |
| 130 | + |
| 131 | + # 4. Interferometer fringes |
| 132 | + print("Computing interference fringes...") |
| 133 | + |
| 134 | + # Scan applied phase (e.g., by varying laser phase) |
| 135 | + phi_scan = np.linspace(0, 4 * np.pi, 200) |
| 136 | + |
| 137 | + # At output, probability of being in upper state: |
| 138 | + # P = (1 + cos(phi_total)) / 2 |
| 139 | + # where phi_total = phi_g + phi_applied |
| 140 | + |
| 141 | + P_output = (1 + np.cos(phi_g + phi_scan)) / 2 |
| 142 | + |
| 143 | + results['fringes'] = { |
| 144 | + 'phi_scan': phi_scan, |
| 145 | + 'P': P_output |
| 146 | + } |
| 147 | + |
| 148 | + # 5. Sagnac phase for rotation sensing |
| 149 | + print("Computing Sagnac effect...") |
| 150 | + |
| 151 | + # Sagnac phase: phi_Sagnac = 4 * m * A * Omega / hbar |
| 152 | + # where A is enclosed area, Omega is rotation rate |
| 153 | + |
| 154 | + A_enclosed = 0.01 # m^2 (10 cm x 10 cm effective area) |
| 155 | + Omega_range = np.linspace(0, 1e-5, 100) # Earth rotation ~ 7e-5 rad/s |
| 156 | + |
| 157 | + phi_Sagnac = 4 * m_Rb * A_enclosed * Omega_range / HBAR |
| 158 | + |
| 159 | + results['sagnac'] = { |
| 160 | + 'Omega': Omega_range, |
| 161 | + 'phi': phi_Sagnac, |
| 162 | + 'A': A_enclosed |
| 163 | + } |
| 164 | + |
| 165 | + # 6. Gravity gradient measurement (gradiometer) |
| 166 | + print("Computing gravity gradient sensitivity...") |
| 167 | + |
| 168 | + # Two interferometers separated by baseline L |
| 169 | + L_baseline = 1.0 # m |
| 170 | + T_grad = 100e-3 # interrogation time |
| 171 | + |
| 172 | + # Gravity gradient measurement |
| 173 | + # Delta_phi = k_eff * grad_g * L * T^2 |
| 174 | + grad_g_range = np.linspace(0, 3e-6, 100) # s^-2 (typical ~ 3e-6) |
| 175 | + |
| 176 | + delta_phi_grad = k_eff * grad_g_range * L_baseline * T_grad**2 |
| 177 | + |
| 178 | + results['gradiometer'] = { |
| 179 | + 'grad_g': grad_g_range, |
| 180 | + 'delta_phi': delta_phi_grad, |
| 181 | + 'L': L_baseline, |
| 182 | + 'T': T_grad |
| 183 | + } |
| 184 | + |
| 185 | + return results |
| 186 | + |
| 187 | + |
| 188 | +def plot_results(results): |
| 189 | + """Create comprehensive visualization of atom interferometry.""" |
| 190 | + |
| 191 | + fig = plt.figure(figsize=(14, 12)) |
| 192 | + |
| 193 | + # Plot 1: Mach-Zehnder trajectories |
| 194 | + ax1 = fig.add_subplot(2, 2, 1) |
| 195 | + traj = results['trajectories'] |
| 196 | + |
| 197 | + ax1.plot(traj['t'] * 1e3, traj['z_upper'] * 1e3, 'b-', linewidth=2, |
| 198 | + label='Upper arm') |
| 199 | + ax1.plot(traj['t'] * 1e3, traj['z_lower'] * 1e3, 'r-', linewidth=2, |
| 200 | + label='Lower arm') |
| 201 | + |
| 202 | + # Mark pulse positions |
| 203 | + T = results['T'] |
| 204 | + ax1.axvline(x=0, color='green', linestyle='--', alpha=0.7, label=r'$\pi/2$ pulse') |
| 205 | + ax1.axvline(x=T * 1e3, color='purple', linestyle='--', alpha=0.7, label=r'$\pi$ pulse') |
| 206 | + ax1.axvline(x=2 * T * 1e3, color='green', linestyle='--', alpha=0.7) |
| 207 | + |
| 208 | + ax1.set_xlabel('Time (ms)', fontsize=11) |
| 209 | + ax1.set_ylabel('Position (mm)', fontsize=11) |
| 210 | + ax1.set_title('Mach-Zehnder Atom Interferometer Trajectories', fontsize=12) |
| 211 | + ax1.legend(loc='upper right', fontsize=9) |
| 212 | + ax1.grid(True, alpha=0.3) |
| 213 | + |
| 214 | + # Add annotation about momentum states |
| 215 | + ax1.annotate(r'$|p\rangle + |p+\hbar k\rangle$', xy=(T*1e3/2, 100), |
| 216 | + fontsize=9, ha='center', |
| 217 | + bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.5)) |
| 218 | + |
| 219 | + # Plot 2: Interference fringes |
| 220 | + ax2 = fig.add_subplot(2, 2, 2) |
| 221 | + fr = results['fringes'] |
| 222 | + |
| 223 | + ax2.plot(fr['phi_scan'] / np.pi, fr['P'], 'b-', linewidth=2) |
| 224 | + ax2.axhline(y=0.5, color='gray', linestyle=':', alpha=0.5) |
| 225 | + |
| 226 | + # Mark gravitational phase |
| 227 | + phi_g = results['phi_g'] |
| 228 | + ax2.axvline(x=phi_g / np.pi, color='red', linestyle='--', alpha=0.7, |
| 229 | + label=f'Gravity: {phi_g/(2*np.pi):.1f} fringes') |
| 230 | + |
| 231 | + ax2.set_xlabel(r'Applied Phase ($\pi$ rad)', fontsize=11) |
| 232 | + ax2.set_ylabel('Output Probability', fontsize=11) |
| 233 | + ax2.set_title(f'Interference Fringes (T = {T*1e3:.0f} ms)', fontsize=12) |
| 234 | + ax2.legend(loc='upper right', fontsize=9) |
| 235 | + ax2.set_xlim(0, 4) |
| 236 | + ax2.grid(True, alpha=0.3) |
| 237 | + |
| 238 | + # Add inset showing fringe contrast |
| 239 | + ax2.text(0.05, 0.05, 'Fringe contrast ~ 100%\n(ideal case)', |
| 240 | + transform=ax2.transAxes, fontsize=9, |
| 241 | + bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.5)) |
| 242 | + |
| 243 | + # Plot 3: Phase shift and sensitivity vs T |
| 244 | + ax3 = fig.add_subplot(2, 2, 3) |
| 245 | + pv = results['phase_vs_T'] |
| 246 | + sv = results['sensitivity'] |
| 247 | + |
| 248 | + ax3_twin = ax3.twinx() |
| 249 | + |
| 250 | + l1, = ax3.plot(pv['T'] * 1e3, pv['phi'] / (2 * np.pi), 'b-', linewidth=2, |
| 251 | + label='Phase shift') |
| 252 | + l2, = ax3_twin.semilogy(sv['T'] * 1e3, sv['delta_g'] / results['g'] * 1e9, 'r-', |
| 253 | + linewidth=2, label=r'$\delta g / g$') |
| 254 | + |
| 255 | + ax3.set_xlabel('Interrogation Time T (ms)', fontsize=11) |
| 256 | + ax3.set_ylabel('Phase Shift (fringes)', fontsize=11, color='b') |
| 257 | + ax3_twin.set_ylabel(r'Relative Sensitivity $\delta g/g$ (ppb)', fontsize=11, color='r') |
| 258 | + ax3.set_title('Gravimeter Phase Shift and Sensitivity', fontsize=12) |
| 259 | + |
| 260 | + # Combined legend |
| 261 | + ax3.legend([l1, l2], ['Phase shift', 'Sensitivity (ppb)'], |
| 262 | + loc='upper left', fontsize=9) |
| 263 | + ax3.grid(True, alpha=0.3) |
| 264 | + ax3.tick_params(axis='y', labelcolor='b') |
| 265 | + ax3_twin.tick_params(axis='y', labelcolor='r') |
| 266 | + |
| 267 | + # Plot 4: Various applications |
| 268 | + ax4 = fig.add_subplot(2, 2, 4) |
| 269 | + |
| 270 | + # Sagnac effect for rotation sensing |
| 271 | + sg = results['sagnac'] |
| 272 | + ax4.plot(sg['Omega'] * 1e6, sg['phi'], 'g-', linewidth=2, |
| 273 | + label=f'Gyroscope (A = {sg["A"]*1e4:.0f} cm$^2$)') |
| 274 | + |
| 275 | + # Mark Earth rotation |
| 276 | + Omega_Earth = 7.27e-5 # rad/s |
| 277 | + phi_Earth = 4 * results['m'] * sg['A'] * Omega_Earth / HBAR |
| 278 | + ax4.axvline(x=Omega_Earth * 1e6, color='orange', linestyle='--', alpha=0.7) |
| 279 | + ax4.text(Omega_Earth * 1e6 * 1.1, max(sg['phi']) * 0.8, |
| 280 | + 'Earth\nrotation', fontsize=8, color='orange') |
| 281 | + |
| 282 | + ax4.set_xlabel(r'Rotation Rate ($\mu$rad/s)', fontsize=11) |
| 283 | + ax4.set_ylabel('Sagnac Phase (rad)', fontsize=11) |
| 284 | + ax4.set_title('Sagnac Effect for Rotation Sensing', fontsize=12) |
| 285 | + ax4.legend(loc='upper left', fontsize=9) |
| 286 | + ax4.grid(True, alpha=0.3) |
| 287 | + |
| 288 | + # Add text about applications |
| 289 | + textstr = '\n'.join([ |
| 290 | + 'Applications:', |
| 291 | + '- Gravimeters: $\\delta g/g \\sim 10^{-9}$', |
| 292 | + '- Gyroscopes: $\\delta\\Omega \\sim nrad/s$', |
| 293 | + '- Gradiometers: gravity mapping', |
| 294 | + '- Tests of general relativity' |
| 295 | + ]) |
| 296 | + ax4.text(0.05, 0.05, textstr, transform=ax4.transAxes, fontsize=9, |
| 297 | + verticalalignment='bottom', |
| 298 | + bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.5)) |
| 299 | + |
| 300 | + plt.tight_layout() |
| 301 | + return fig |
| 302 | + |
| 303 | + |
| 304 | +def main(): |
| 305 | + print("=" * 60) |
| 306 | + print("Experiment 251: Atom Interferometer Phase Shift") |
| 307 | + print("=" * 60) |
| 308 | + print() |
| 309 | + |
| 310 | + # Run simulation |
| 311 | + print("Running simulations...") |
| 312 | + results = simulate_atom_interferometer() |
| 313 | + |
| 314 | + # Create visualization |
| 315 | + print("\nCreating visualization...") |
| 316 | + fig = plot_results(results) |
| 317 | + |
| 318 | + # Save figure |
| 319 | + output_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'output') |
| 320 | + os.makedirs(output_dir, exist_ok=True) |
| 321 | + output_path = os.path.join(output_dir, 'atom_interferometer.png') |
| 322 | + fig.savefig(output_path, dpi=150, bbox_inches='tight') |
| 323 | + print(f"\nPlot saved to: {output_path}") |
| 324 | + |
| 325 | + # Print summary |
| 326 | + print("\n" + "=" * 60) |
| 327 | + print("Summary (Rb-87 Mach-Zehnder Interferometer):") |
| 328 | + print("=" * 60) |
| 329 | + print(f"Wavelength: {results['wavelength']*1e9:.0f} nm") |
| 330 | + print(f"Effective wave vector: {results['k_eff']:.2e} m^-1") |
| 331 | + print(f"Recoil velocity: {results['v_recoil']*1e3:.3f} mm/s") |
| 332 | + print() |
| 333 | + print(f"Interrogation time: {results['T']*1e3:.0f} ms") |
| 334 | + print(f"Gravitational phase shift: {results['phi_g']:.2f} rad = {results['phi_g']/(2*np.pi):.2f} fringes") |
| 335 | + print() |
| 336 | + print("Key phase shift formulas:") |
| 337 | + print(" Gravity: phi = k_eff * g * T^2") |
| 338 | + print(" Rotation (Sagnac): phi = 4 * m * A * Omega / hbar") |
| 339 | + print(" Gravity gradient: phi = k_eff * grad_g * L * T^2") |
| 340 | + print() |
| 341 | + print("Achievable sensitivities:") |
| 342 | + print(" - Gravity: delta_g/g ~ 10^-9 (ppb level)") |
| 343 | + print(" - Rotation: delta_Omega ~ nrad/s") |
| 344 | + print(" - Equivalent to optical interferometers at lambda ~ nm scale!") |
| 345 | + |
| 346 | + plt.close() |
| 347 | + |
| 348 | + |
| 349 | +if __name__ == "__main__": |
| 350 | + main() |
0 commit comments