RF heterodyne with Doppler Example

This notebook demonstrates two-tone detection using a Rydberg sensor in the time domain with Doppler averaging. An RF local oscillator (LO) and signal (sig) are imposed on the Rydberg sensor. This is useful for RF phase detection, and can be used to linearize the detection, as shown below. The main results of this example showing how different levels of Doppler averaging affect the beat signal size of the sensor.

This notebook can be downloaded here.

import datetime
from numba import vectorize, float64
##***LAST UPDATE***##
now = datetime.datetime.now()
print(now)
2025-02-21 21:10:49.588050

Imports

import numpy as np
import rydiqule as rq
import matplotlib.pyplot as plt
%load_ext autoreload
%autoreload 2

Define the Sensors

atom = "Rb85"
(g, e) = rq.D2_states(atom)
r1 = rq.A_QState(150, 2, 2.5)
r2 = rq.A_QState(149, 3, 3.5)
rf_rabi = 100 #Mrad/s
red_laser = {'states':(g,e), 'rabi_frequency':2*np.pi*5}  #fields are stored as dictioniaries
blue_laser = {'states':(e,r1), 'rabi_frequency':2*np.pi*7, 'detuning': 0}
LO_ss = {'states':(r1,r2), 'rabi_frequency':rf_rabi, 'detuning':0}


RbSensor_ss = rq.Cell(atom, [g, e, r1, r2],
                      gamma_transit=2*np.pi*1, cell_length = 0.01)
RbSensor_time = rq.Cell(atom, [g, e, r1, r2],
                      gamma_transit=2*np.pi*1, cell_length = 0.01)
state1 = RbSensor_time.states[2]
state2 = RbSensor_time.states[3]
print("1: ", state1)
print("2: ", state1)
dipoleMoment = RbSensor_time.atom.get_dipole_matrix_element(state1,state2, 0)

field = rf_rabi/rq.scale_dipole(dipoleMoment)

print("applied field, V/m:", field) #V/m
print("Rabi frequency, Mrad/s: ", field*rq.scale_dipole(dipoleMoment))
1:  (150, 2, 2.5)
2:  (150, 2, 2.5)
applied field, V/m: 0.097999300049248
Rabi frequency, Mrad/s:  100.0
def sig_and_LO( delta, beta):
    def fun(t):
        return (1+beta*np.sin(delta*t))
    return fun
rf_freq = RbSensor_time.atom.arc_atom.getTransitionFrequency(*r1[:3],*r2[:3])*1E-6
rf_freq #MHz
658.5872652398125

Observe a heterodyne beat between the Signal and LO.

Define the RF LO and signal

sampleNum = 200
endTime = 10 # microseconds
rf = sig_and_LO( 5, .1)

Solve without Doppler averaging

Observe the beat between signal and LO fields.

red_laser = {'states':(g,e), 'rabi_frequency':2*np.pi*5, 'detuning':0}
blue_laser = {'states':(e,r1), 'rabi_frequency':2*np.pi*7, 'detuning': 0}
rf = {'states':(r1,r2), "rabi_frequency": rf_rabi, 'detuning': 0, 'time_dependence': sig_and_LO( 2*np.pi, .05)}

RbSensor_time.add_couplings(blue_laser, red_laser, rf)
%%time
#Solve Without any doppler broadening

time_sol = rq.solve_time(RbSensor_time, endTime, sampleNum, atol=1e-6, rtol=1e-6)
CPU times: total: 93.8 ms
Wall time: 121 ms
transmission = time_sol.get_transmission_coef()
fig, ax = plt.subplots()
ax.plot(time_sol.t, transmission)
ax.set_xlabel("time (us)")
ax.set_ylabel('transmission')
ax.set_title("Doppler-Free Solution")
Text(0.5, 1.0, 'Doppler-Free Solution')
../_images/6d8f0ed10270045e66199166184a7521bae161b780565c61c3714e90f7d29570.png

Solve with Doppler averaging

Doppler averaged results require larger Rabi frequencies to observe similar sized signals.

red_laser = {'states':(g,e), 'rabi_frequency':2*np.pi*5, 'detuning':0, 'kunit': np.array([1,0,0])}
blue_laser = {'states':(e,r1), 'rabi_frequency':2*np.pi*7, 'detuning': 0,'kunit': np.array([-1,0,0])}
rf = {'states':(r1,r2), "rabi_frequency":rf_rabi, 'detuning': 0, 'time_dependence': sig_and_LO( 2*np.pi, .05)}

RbSensor_time.add_couplings(blue_laser, red_laser, rf)
%%time
#Solve with a doppler peak calculated from physical system properties
sampleNum = 200
endTime = 10
time_sol_doppler = rq.solve_time(RbSensor_time, endTime, sampleNum, doppler=True, rtol = 1e-6, atol = 1e-6)
CPU times: total: 9min 17s
Wall time: 1min 33s
transmission_doppler = time_sol_doppler.get_transmission_coef()
fig, ax = plt.subplots()
ax.plot(time_sol_doppler.t, transmission_doppler)
ax.set_xlabel("time (us)")
ax.set_ylabel('transmission')
ax.set_title("Doppler Solution")
Text(0.5, 1.0, 'Doppler Solution')
../_images/aee4cdc544511beece1ea0180e489ae6ddf7500bce2ed6dfcf006f2f64b14b34.png

Compare the size of the beat signals

Here we ignore the starting transient, and normalize the beat signal. As the Doppler broadening is increased, the size of the beat is reduced (for the same optical depth).

def normalize_trace(trace,expand=1):
    ave = trace[100:].mean()
    return (trace - ave)/ave*expand
fig, ax = plt.subplots()

ax.plot(time_sol.t, normalize_trace(transmission), label='Doppler-Free')
ax.plot(time_sol_doppler.t, normalize_trace(transmission_doppler), label='Doppler broadened')
ax.set_xlim((1,10))
ax.set_xlabel("time (us)")
ax.set_ylabel('transmission (%)')
ax.legend()
<matplotlib.legend.Legend at 0x151d007f990>
../_images/f44b0d478c93e5b79316ef93d58379c6924affb69513a94aac75992be826cdfc.png

The authors recognize financial support from the US Army and Defense Advanced Research Projects Agency (DARPA). Rydiqule has been approved for unlimited public release by DEVCOM Army Research Laboratory and DARPA. This software is released under the xx licence through the University of Maryland Quantum Technology Center. The views, opinions and/or findings expressed here are those of the authors and should not be interpreted as representing the official views or policies of the Department of Defense or the U.S. Government.