def test_steady_state_intensity(self): """Test the steady state intensity scaling""" # use both integers and floats intensity_list = [1e-3, 1e-1, 0.3, 1, 1.0, 2, 10., 1.2e4] ion = Ca43(B=5e-4, level_filter=[ground_level, P32]) s_idx = ion.index(ground_level, 4) p_idx = ion.index(P32, +5) rates = Rates(ion) delta = ion.delta(s_idx, p_idx) for I in intensity_list: Lasers = [Laser("393", q=+1, I=I, delta=delta)] # resonant trans = rates.get_transitions(Lasers) Np_ss = _steady_state_population(I) # transition rates normalised by A coefficient dNp_dt = (trans[p_idx, p_idx] * Np_ss + trans[p_idx, s_idx] * (1 - Np_ss)) dNp_dt = dNp_dt / (trans[p_idx, p_idx] + trans[p_idx, s_idx]) self.assertAlmostEqual(0., dNp_dt, places=7) dNs_dt = (trans[s_idx, p_idx] * Np_ss + trans[s_idx, s_idx] * (1 - Np_ss)) dNs_dt = dNs_dt / (trans[s_idx, p_idx] + trans[s_idx, s_idx]) self.assertAlmostEqual(0., dNs_dt, places=7)
def test_steady_state_detuning(self): """Test steady state detuning dependence""" # assume 1 saturation intensity ion = Ca43(B=5e-4, level_filter=[ground_level, P32]) s_idx = ion.index(ground_level, 4) p_idx = ion.index(P32, +5) rates = Rates(ion) delta = ion.delta(s_idx, p_idx) Lasers = [Laser("393", q=+1, I=1., delta=delta)] # resonant trans = rates.get_transitions(Lasers) line_width = abs(trans[p_idx, p_idx] + trans[p_idx, s_idx]) # detuning scan relative to linewidth norm_detuning = [-1e4, 2.3e1, 2, -4, 0.5, 0] for det in norm_detuning: I_eff = 1/(4 * det**2 + 1) Np_ss = _steady_state_population(I_eff) Lasers = [Laser("393", q=+1, I=1., delta=delta + line_width*det)] trans = rates.get_transitions(Lasers) # transition rates normalised by A coefficient dNp_dt = (trans[p_idx, p_idx] * Np_ss + trans[p_idx, s_idx] * (1 - Np_ss)) dNp_dt = dNp_dt / (trans[p_idx, p_idx] + trans[p_idx, s_idx]) self.assertAlmostEqual(0., dNp_dt, places=7) dNs_dt = (trans[s_idx, p_idx] * Np_ss + trans[s_idx, s_idx] * (1 - Np_ss)) dNs_dt = dNs_dt / (trans[s_idx, p_idx] + trans[s_idx, s_idx]) self.assertAlmostEqual(0., dNs_dt, places=7)
def test_multi_transition(self): """Test with lasers on multiple transitions (see #15)""" ion = Ca43(B=146e-4) rates = Rates(ion) Lasers = [ Laser("397", q=0, I=1, delta=0), Laser("866", q=0, I=1, delta=0), ] rates.get_transitions(Lasers)
def test_multi_laser(self): """Test with multiple lasers on one transition""" ion = Ca43(B=146e-4) rates = Rates(ion) Lasers = [ Laser("397", q=0, I=1, delta=0), Laser("397", q=+1, I=1, delta=0), ] rates.get_transitions(Lasers)
def test_LF(self): """ Check that, in the low-field, our scattering rates match a more direct calculation. """ ion = Ca43(B=1e-8) ion.calc_Epole() Gamma_ion = ion.Gamma I = ion.I Idim = int(np.rint(2*I+1)) Gamma = np.zeros((ion.num_states, ion.num_states)) for name, transition in ion.transitions.items(): A = transition.A lower = transition.lower upper = transition.upper Ju = upper.J Jl = lower.J Jdim_l = int(np.rint(2*Jl+1)) l_dim = Idim*Jdim_l dJ = Ju-Jl dL = upper.L - lower.L if dJ in [-1, 0, +1] and dL in [-1, 0, +1]: order = 1 elif abs(dJ) in [0, 1, 2] and abs(dL) in [0, 1, 2]: order = 2 else: raise ValueError("Unsupported transition order {}" .format(order)) subspace = np.r_[ion.slice(lower), ion.slice(upper)] for l_ind in list(subspace[:l_dim]): for u_ind in list(subspace[l_dim:]): Fl = ion.F[l_ind] Fu = ion.F[u_ind] Ml = ion.M[l_ind] Mu = ion.M[u_ind] q = Mu - Ml if q not in range(-order, order+1): continue Gamma[l_ind, u_ind] = A*( (2*Ju+1) * (2*Fl+1) * (2*Fu+1) * (wigner_3j(Fu, order, Fl, -Mu, q, Ml))**2 * (wigner_6j(Ju, I, Fu, Fl, order, Jl)**2)) subspace = np.ix_(subspace, subspace) scale = np.max(np.max(np.abs(Gamma[subspace]))) eps = np.max(np.max(np.abs(Gamma[subspace]-Gamma_ion[subspace]))) self.assertTrue(eps/scale < 1e-4)
def test_rates_relations(self): """Test the spontaneous rates satisfy relations in net rates This relation is used in the steady states tests.""" intensity_list = [1e-3, 1e-1, 0.3, 1, 1.0, 2, 10., 1.2e4] ion = Ca43(B=5e-4, level_filter=[ground_level, P32]) s_idx = ion.index(ground_level, 4) p_idx = ion.index(P32, +5) rates = Rates(ion) delta = ion.delta(s_idx, p_idx) for I in intensity_list: Lasers = [Laser("393", q=+1, I=I, delta=delta)] # resonant trans = rates.get_transitions(Lasers) spont = rates.get_spont() r = spont[p_idx, p_idx] / (trans[p_idx, p_idx]+trans[p_idx, s_idx]) self.assertAlmostEqual(r, 1., places=7)
def main(): t_ax = np.linspace(0, 100e-6, 100) I = 0.02 # 393 intensity ion = Ca43(B=146e-4) stretch = ion.index(ground_level, 4) rates = Rates(ion) delta = ion.delta(stretch, ion.index(P32, +5)) Lasers = [Laser("393", q=+1, I=I, delta=delta)] # resonant 393 sigma+ trans = rates.get_transitions(Lasers) Vi = np.zeros((ion.num_states, 1)) # initial state Vi[stretch] = 1 # start in F=4, M=+4 shelved = np.zeros(len(t_ax)) for idx, t in np.ndenumerate(t_ax): Vf = expm(trans*t)@Vi shelved[idx] = sum(Vf[ion.slice(shelf)]) plt.plot(t_ax*1e6, shelved) plt.ylabel('Shelved Population') plt.xlabel('Shelving time (us)') plt.grid() plt.show()
import numpy as np from ion_phys.ions.ca43 import Ca43, ground_level from ion_phys.utils import (field_insensitive_point, d2f_dB2) if __name__ == '__main__': # all seems about correct (e.g. agrees with TPH thesis) but expect some # numerical inaccuracy, particularly around the second-order field # sensitivities. To improve we should add a special case to the derivative # calculation that uses the BR formula! ion = Ca43(level_filter=[ground_level]) print("Field-independent points:") for M3 in range(-3, +3 + 1): for q in [-1, 0, 1]: ion.setB(1e-4) F4 = ion.index(ground_level, M3 - q, F=4) F3 = ion.index(ground_level, M3, F=3) B0 = field_insensitive_point(ion, F4, F3) if B0 is not None: ion.setB(B0) f0 = ion.delta(F4, F3) d2fdB2 = d2f_dB2(ion, F4, F3) print("4, {} --> 3, {}: {:.6f} GHz @ {:.5f} G ({:.3e} Hz/G^2)". format(M3 - q, M3, f0 / (2 * np.pi * 1e6), B0 * 1e4, d2fdB2 / (2 * np.pi) * 1e-8)) else: print("4, {} --> 3, {}: none found".format(M3 - q, M3))