Adiabatic Rapid Passage

This example covers an example with both laser frequency and amplitude modulation: rapid adiabatic passage. It reproduces Fig. 2 from T. Lu, X. Miao, and H. Metcalf, “Bloch theorem on the Bloch sphere” Physical Review A 71, 061405(R) (2005), http://dx.doi.org/10.1103/PhysRevA.71.061405

[1]:
import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import solve_ivp
import pylcp
from pylcp.common import progressBar

Code the basic Hamiltonian

The Hamiltonian is a simple two-state Hamiltonian. Here, we code up a method to return the time-dependent Hamiltonian matrix to evolve it with the Schrodinger equation. We take the modulation to be the same as in Lu, et. al., above: \(\Omega(t) = \Omega_0\sin(\omega_m t)\) and \(\Delta=\Delta_0\cos(\omega_m t)\).

[2]:
def H(t, Delta0, Omega0, omegam):
    Delta = Delta0*np.cos(omegam*t)
    Omega = Omega0*np.sin(omegam*t)

    return 1/2*np.array([[Delta, Omega], [Omega, -Delta]])

Define the problem in pylcp

Unlike the above Hamiltonian, which is clearly time dependent, the only time dependence available to us in pylcp is through the fields. First, we need to remember for the amplitude modulation that \(I/I_{\rm sat} = 2\Omega^2/\Gamma^ = 2[\Omega_0\sin(\omega_m t)]^2/\Gamma^2\). Second, we need to frequency modulate the laser beams. Remember that if the lasers have a temporal phase \(\phi\), the frequency is \(\omega = \frac{d\phi}{dt}\). Thus, if the detuning \(\Delta(t)\) is specified, then \(\phi = \int \Delta (t)\ dt\). pylcp contains a built-in integrator in order to convert the detuning to a phase, but it might not always be reliable. So you can also reproduce the detuning by modulating the phase \(\phi\). To reproduce the \(\Delta(t) = \Delta_0\cos(\omega_m t)\), we need \(\phi = \Delta_0/\omega_m\sin(\omega_m t)\).

[3]:
def return_lasers(Delta0, Omega0, omegam):
    laserBeams = pylcp.laserBeams([
        {'kvec':np.array([1., 0., 0.]),
         'pol':np.array([0., 0., 1.]),
         'pol_coord':'cartesian',
         'delta': lambda t: Delta0*np.cos(omegam*t),
         'phase': 0,#lambda t: Delta0/omegam*np.sin(omegam*t),
         's': lambda R, t: 2*(Omega0*np.sin(omegam*t))**2
        }])

    return laserBeams

magField = lambda R: np.zeros(R.shape)

# Now define the extremely simple Hamiltonian:
Hg = np.array([[0.]])
mugq = np.array([[[0.]], [[0.]], [[0.]]])
He = np.array([[0.]])
mueq = np.array([[[0.]], [[0.]], [[0.]]])
dijq = np.array([[[0.]], [[1.]], [[0.]]])

gamma = 1.

hamiltonian = pylcp.hamiltonian(Hg, He, mugq, mueq, dijq, gamma=gamma)
hamiltonian.print_structure()
[[((<g|H_0|g> 1x1), (<g|mu_q|g> 1x1)) (<g|d_q|e> 1x1)]
 [(<e|d_q|g> 1x1) ((<e|H_0|e> 1x1), (<e|mu_q|e> 1x1))]]

Evolve a single state

We solve with both the Schrodinger Equation and the OBEs. We can play with the modulation parameters (\(\Delta_0\), \(\Omega_0\), \(\omega_m\)), and see how the result chanages.

[4]:
Delta0 = 5.
Omega0 = 10.
omegam = 1.

t = np.linspace(0., np.pi/omegam, 201)
sol_SE = solve_ivp(lambda t, y: -1j*H(t, Delta0, Omega0, omegam)@y, [0, np.pi/omegam],
                   np.array([1., 0.], dtype='complex128'), t_eval=t)

laserBeams = return_lasers(Delta0, Omega0, omegam)
obe = pylcp.obe(laserBeams, magField, hamiltonian)
obe.ev_mat['decay'] = np.zeros(obe.ev_mat['decay'].shape) # Turn off damping to compare to Schrodinger Equation.
obe.set_initial_rho_from_populations(np.array([1., 0.]))
sol_OBE = obe.evolve_density([0, np.pi/omegam], t_eval=t)

Plot it up. Dashed is OBEs from pylcp and solid is the Schrodiner equation. Orange is the \(|1\rangle\) state; blue is the \(|0\rangle\) state.

[5]:
fig, ax = plt.subplots(1, 1)
ax.plot(sol_SE.t, np.abs(sol_SE.y[0])**2, linewidth=0.75)
ax.plot(sol_SE.t, np.abs(sol_SE.y[1])**2, linewidth=0.75)
ax.plot(sol_OBE.t, np.real(sol_OBE.rho[0, 0]), '--', color='C0', linewidth=1.25)
ax.plot(sol_OBE.t, np.real(sol_OBE.rho[1, 1]), '--', color='C1', linewidth=1.25)
ax.set_xlabel('$\omega_m t$')
ax.set_ylabel('$\\rho_{ii}$');
../../_images/examples_basics_04_adiabatic_passage_9_0.png

Reproduce Fig. 2

This involves scanning over \(\Delta_0\) and \(\Omega_0\). This takes a long time, only because there is a fair amount of overhead in regenerating the optical Bloch equations on every iteration.

[6]:
Delta0s = np.arange(0.25, 25.1, 0.25)
Omega0s = np.arange(0.25, 25.1, 0.25)
t = np.linspace(0., np.pi, 201)

DELTA0S, OMEGA0S = np.meshgrid(Delta0s, Omega0s)

it = np.nditer([DELTA0S, OMEGA0S, None])

progress = progressBar()

for (Delta0, Omega0, rhogg) in it:
    del laserBeams

    # Set up new laser beams:
    laserBeams = return_lasers(Delta0, Omega0, omegam)

    # Set up OBE:
    obe = pylcp.obe(laserBeams, magField, hamiltonian)
    obe.ev_mat['decay'] = np.zeros(obe.ev_mat['decay'].shape) # Turn off damping to compare to Schrodinger Equation.
    obe.set_initial_rho_from_populations(np.array([1., 0.]))

    # Solve:
    sol_OBE = obe.evolve_density([0, np.pi], t_eval=t)

    # Save result:
    rhogg[...] = np.real(sol_OBE.rho[0, 0, -1])

    # Update progress bar:
    progress.update((it.iterindex+1)/it.itersize)

RHOGG = it.operands[2]
Completed in 8:02.

Plot it up:

[7]:
dDelta0 = np.mean(np.diff(Delta0s))
dOmega0 = np.mean(np.diff(Omega0s))
fig, ax = plt.subplots(1, 1)
im = ax.imshow(RHOGG, origin='lower',
               extent=(np.amin(Delta0s)-dDelta0/2, np.amax(Delta0s)+dDelta0/2,
                       np.amin(Omega0s)-dOmega0/2, np.amax(Omega0s)+dOmega0/2),
               aspect='auto')
ax_cbar = plt.colorbar(im)
ax_cbar.set_label('$\\rho_{gg}$')
ax.set_xlabel('$\Delta_0/\omega_m$')
ax.set_ylabel('$\Omega_0/\omega_m$');
../../_images/examples_basics_04_adiabatic_passage_13_0.png

Add in damping

These two cells are exactly the same as above, except the user must now specify \(\omega_m/\Gamma\) (\(\Gamma=1\)) and we merely comment out the line that eliminate the damping part of the OBEs.

[8]:
omegam = 2
Delta0s = np.arange(0.25, 25.1, 0.25) # These will be normalized to the value of omegam
Omega0s = np.arange(0.25, 25.1, 0.25)
t = np.linspace(0., np.pi/omegam, 201)

DELTA0S, OMEGA0S = np.meshgrid(Delta0s, Omega0s)

it = np.nditer([DELTA0S, OMEGA0S, None])

progress = progressBar()

for (Delta0, Omega0, rhogg) in it:
    # Set up new laser beams:
    laserBeams = return_lasers(omegam*Delta0, omegam*Omega0, omegam)

    # Set up OBE:
    obe = pylcp.obe(laserBeams, magField, hamiltonian)
    obe.set_initial_rho_from_populations(np.array([1., 0.]))

    # Solve:
    sol_OBE = obe.evolve_density([0, np.pi/omegam], t_eval=t)

    # Save result:
    rhogg[...] = np.real(sol_OBE.rho[0, 0, -1])

    # Update progress bar:
    progress.update((it.iterindex+1)/it.itersize)

RHOGG = it.operands[2]
Completed in 7:22.

Plot it up:

[9]:
dDelta0 = np.mean(np.diff(Delta0s))
dOmega0 = np.mean(np.diff(Omega0s))
fig, ax = plt.subplots(1, 1)
im = ax.imshow(RHOGG, origin='lower',
               extent=(np.amin(Delta0s)-dDelta0/2, np.amax(Delta0s)+dDelta0/2,
                       np.amin(Omega0s)-dOmega0/2, np.amax(Omega0s)+dOmega0/2),
               aspect='auto', clim=(0, 1))
ax_cbar = plt.colorbar(im)
ax_cbar.set_label('$\\rho_{gg}$')
ax.set_xlabel('$\Delta_0/\omega_m$')
ax.set_ylabel('$\Omega_0/\omega_m$');
../../_images/examples_basics_04_adiabatic_passage_17_0.png