Example: Effect of wavefront aberrations in atom interferometry

As an example, we reproduce two plots from the paper https://link.springer.com/article/10.1007/s00340-015-6138-5.

The simulation will require the following objects and parameters

  • Wavefront: contains the wavefront aberrations of the interferometry lasers

  • AtomicEnsemble: an ensemble of atoms which different trajectories or phase space vectors

  • Detector: determines which atoms contribute to the signal

  • times of the three interferometer pulses

  • effective wavevector

[1]:
import json

import numpy as np
import matplotlib.pyplot as plt
from functools import partial
import aisim as ais

Loading and preparing wavefront data

Wavefront aberration in multiples of \(\lambda\) = 780 nm.

Load Zernike coefficients from file:

[2]:
coeff_window = {j: val for j, val in enumerate(np.loadtxt("data/wavefront.txt"))}

Creating Wavefront objects and removing piston, tip and tilt from the data:

[3]:
r_wf = 10.91e-3  # radius of the available wavefront data in m

wf = ais.Wavefront(r_wf, coeff_window, zern_order="WYANT", zern_norm=None)
for n in [0, 1, 2]:
    wf.coeff[n] = 0
[4]:
wf.plot(cmap="twilight_shifted", levels=50)
fig, ax = wf.plot_coeff(color="C3")
../_images/examples_wavefront-aberrations_8_0.png
../_images/examples_wavefront-aberrations_8_1.png

Creating an atomic ensemble

Due to the large number of parameters determining an atomic ensemble, dictionaries are used:

[5]:
# Create an ensemble of atoms
atoms = ais.create_random_ensemble(
    int(1e5),
    x_dist=partial(ais.dist.position_dist_gaussian, std=3.0e-3),
    y_dist=partial(ais.dist.position_dist_gaussian, std=3.0e-3),
    z_dist=partial(ais.dist.position_dist_gaussian, std=3.0e-3),
    vx_dist=partial(ais.dist.velocity_dist_from_temp, temperature=3.5e-6),
    vy_dist=partial(ais.dist.velocity_dist_from_temp, temperature=3.5e-6),
    vz_dist=partial(ais.dist.velocity_dist_for_gaussian_velsel, pulse_duration=60e-6),
    seed=1,
)

Plotting the initial positions of the ensemble.

[6]:
fig, ax = plt.subplots()
atoms.plot(ax=ax, cmap="Reds")
ax.set_aspect("equal", "box")
ax.set_xlabel("x / mm")
ax.set_ylabel("y / mm")
[6]:
Text(0, 0.5, 'y / mm')
../_images/examples_wavefront-aberrations_12_1.png

Setting up the detector

We want to calculate the dependency of the phase shift caused by wavefront aberrations on the detection area. For this reason, we set up a Detector with varying detection radius within a for-loop.

[7]:
t_det = 778e-3  # time of the detection in s

Simulation the bias in gravity from wavefront aberrations

For the simulation we need the objects created above and the timing of the interferometer sequence.

[8]:
T = 260e-3  # interferometer time in s
t1 = 130e-3  # time of first pulse in s
t2 = t1 + T
t3 = t2 + T
[9]:
awfs = []
r_dets = np.linspace(2e-3, 10e-3, 16)
for r_det in r_dets:
    # creating detector with new detection radius
    det = ais.PolarDetector(t_det, r_det=r_det)

    det_atoms = det.detected_atoms(atoms)

    # calculate the imprinted phase for each "test atom" at each pulse. This is the computationally heavy part
    phi1 = 2 * np.pi * wf.get_value(det_atoms.calc_position(t1))
    phi2 = 2 * np.pi * wf.get_value(det_atoms.calc_position(t2))
    phi3 = 2 * np.pi * wf.get_value(det_atoms.calc_position(t3))

    # calculate a complex amplitude factor for the Mach-Zehnder sequence and calculate
    # the mean. Note that some atoms will have probed the wavefront outside of the
    # Raman beam radius and will thus have a NaN value in the phase.
    awfs.append(np.nanmean(np.exp(1j * (phi1 - 2 * phi2 + phi3))))

# factor two since the window is passed twice
g = 2 * ais.phase_error_to_grav(np.angle(awfs), T=260e-3, keff=1.610574779769e7)

We load the measured gravity data from a file and compare it to the simulation results.

[10]:
data = json.load(open("data/experimental_data.json"))
r_det_data = np.array(data["r_det"])
grav = np.array(data["g"])
graverr = np.array(data["g_err"])
[11]:
fig, ax = plt.subplots()
ax.plot(1e3 * r_dets, 1e9 * g, label="simulation")
ax.errorbar(1e3 * r_det_data, 1e9 * grav, yerr=1e9 * graverr, fmt="o", label="data")
ax.set_xlabel("Detection radius / mm")
ax.set_ylabel("Gravity bias / $nm/s^2$")
ax.set_ylim([-35, 5])
ax.legend()
[11]:
<matplotlib.legend.Legend at 0x1238a7770>
../_images/examples_wavefront-aberrations_20_1.png

Note that the original software used in the paper used a fixed position and velocity grid rather than a Monte-Carlo approach, so some discrepancies are to be expected.