Newer
Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Fri May 15 18:11:00 2020
@author: oem
"""
import numpy as np
import matplotlib.pyplot as plt
from odeintw import odeintw
import time
#matplotlib.use('Qt5Agg')
"""
odeintw es un wrapper de la funcion odeint de scipy.integrate que sirve para integrar
sistemas de ecuaciones con parámetros complejos.
En este script se resuelven las ecuaciones
de Bloch de un sistema de 3 niveles en configuración lambda (1 y 3 son ground, 2 es excitado)
para ion de Bario o de Calcio.
Este script considera que hay dos láseres repump prendidos con sus respectivas frecuencias de
rabi y detuning.
Además, se usan dos distintos sistemas de referencia rotantes, condensados en las funciones
RepumpReferenceSystemModel 1 y 2. La 2 es más útil para prender de a poco el láser repump2, y
cuando su frec. de rabi es cero, no aparece en las ecuaciones.
https://github.com/WarrenWeckesser/odeintw
"""
#CHEQUEADOS LOS DAMPINGS
def EquationSystemModel(z, t, rabi12, rabi23, g21, g23, glg, glr, detr, detg, no_IR_lasers):
"""
Se plantean acá las ecuaciones dinámicas de la matriz densidad (OBE: Optical Bloch Equations)
para la interacción entre un átomo y un campo electromagnético.
-rabi12, rabi23: frecuencias de rabi, proporcionales a la intensidad de los láseres
-g21, g23: anchos de línea naturales de las transiciones
-glg, glr: anchos de línea de los láseres doppler (UV) y repump (IR) respectivamente
-detg, detr: detunings de transición doppler (UV) y repump (IR) respectivamente
-no_IR_lasers: número de láseres IR aplicados
"""
rho33, rho22, rho11, rho12, rho13, rho23 = z
rho21 = np.conjugate(rho12)
rho31 = np.conjugate(rho13)
rho32 = np.conjugate(rho23)
drho11dt = (1j)*0.5*rabi12*(rho12 - rho21) + rho22*g21
drho22dt = (-1j)*0.5*rabi12*(rho12 - rho21) - 1j*0.5*(np.conjugate(rabi23)*rho32 - rabi23*rho23) - rho22*(g21 + g23)
drho33dt = (-1j)*0.5*(rabi23*rho23 - np.conjugate(rabi23)*rho32) + g23*rho22
drho12dt = (-1j)*0.5*rabi12*(rho22 - rho11) - (1j)*(detg*rho12 - 0.5*rabi23*rho13) - rho12*(0.5*g21 + 0.5*g23 + glg)
drho13dt = (-1j)*(detg*rho13 + 0.5*rabi12*rho23 - 0.5*np.conjugate(rabi23)*rho12 - detr*rho13) - rho13*(glg + no_IR_lasers*glr)
drho23dt = (-1j)*0.5*rabi12*rho13 - (1j)*0.5*np.conjugate(rabi23)*(rho33-rho22) + (1j)*detr*rho23 - rho23*(0.5*g21 + 0.5*g23 + no_IR_lasers*glr)
dzdt = [drho33dt, drho22dt, drho11dt, drho12dt, drho13dt, drho23dt]
return dzdt
def RepumpReferenceSystemModel(rabi23repump1, rabi23repump2, Detuningrepump1, Detuningrepump2, t):
"""
Cuando hay más de un láser en una transición se producen batidos entre los mismos, haciendo que
la frecuencia de rabi sea un parámetro que varía en el tiempo. Para resolver eso numéricamente se
implementa de esta manera, calculando la frecuencia de rabi efectiva antes de resolver las OBE.
Al resolver el sistema, se pasa a un marco de referencia rotante respecto a los láseres. Como los dos láseres
IR excitan la misma transición, hay que elegir una de ambas frecuencias para rotar. Esto elige al repump1
como tal referencia, pero es posible elegir a repump2, o incluso a un promedio entre ambas frecuencias (por eso se llama detuningmedio).
Es decir, esto considera a la matriz de rotación como:
U = exp(-i.wg.t)|1><1| + |2><2| + exp(-i.wr1.t)|3><3|
"""
rabi23 = [rabi23repump1 + rabi23repump2*np.exp(-1*(Detuningrepump1-Detuningrepump2)*1j*tiemp) for tiemp in t]
DetuningMedioRepump = Detuningrepump1
return rabi23, DetuningMedioRepump
def GetTransitionLinewidths(species):
"""
Devuelve los anchos de línea naturales según la especie iónica
"""
if species == 'Calcium':
g21, g23, = 2*np.pi*21.58e6, 2*np.pi*1.35e6 #anchos de linea de las transiciones
elif species == 'Barium':
g21, g23, = 2*np.pi*15.1e6, 2*np.pi*5.3e6 #anchos de linea de las transiciones
else:
print('Species not implemented')
raise
return g21, g23
def Solve3LevelBloch(EquationSystemModel, species, SatParDoppler, SatParRep1, SatParRep2, DetDoppler, DetRep1, DetRep2,
glg, glr, no_IR_lasers, initcond=1, tfin=10, paso=1e-3, printprogress=False):
"""
Resuelve las ecuaciones de bloch del modelo dado. Los inputs son:
glg, glr: anchos de línea de doppler y repump en MHz
SatPar: parámetro de saturación de cada láser
Det: detunings en MHz
rabi12, rabi23 = sr*g21, sp*g23 #frecuencias de rabi
"""
t = np.arange(0, tfin*1e-6, paso*1e-6)
g21, g23 = GetTransitionLinewidths(species)
#convierto todo a MHz
DopplerLaserLinewidth = glg*2*np.pi*1e6
RepumpLaserLinewidth = glr*2*np.pi*1e6
DetuningDoppler = DetDoppler*2*np.pi*1e6
Detuningrepump1 = DetRep1*2*np.pi*1e6
Detuningrepump2 = DetRep2*2*np.pi*1e6
rabi12 = np.sqrt(SatParDoppler*(4*(DetuningDoppler**2) + g21**2)/2)
rabi23repump1 = np.sqrt(SatParRep1*(4*(Detuningrepump1**2) + g23**2)/2)
rabi23repump2 = np.sqrt(SatParRep2*(4*(Detuningrepump2**2) + g23**2)/2)
rabi23vst, _ = RepumpReferenceSystemModel(rabi23repump1, rabi23repump2, Detuningrepump1, Detuningrepump2, t)
rho33sol = []
rho22sol = []
rho11sol = []
rho12sol = []
rho13sol = []
rho23sol = []
#condicion inicial: poblacion en el nivel 1
if initcond==1:
z0 = [0+0j, 0+0j, 1+0j, 0+0j, 0+0j, 0+0j] #formato: [rho33, rho22, rho11, rho12, rho13, rho23]
elif initcond==2:
z0 = [0+0j, 1+0j, 0+0j, 0+0j, 0+0j, 0+0j]
elif initcond==3:
z0 = [1+0j, 0+0j, 0+0j, 0+0j, 0+0j, 0+0j]
else:
print('Mala condicion inicial')
raise
#z0 = [0.5+0j, 0+0j, 0.5+0j, -0.5+0j, 0+0j, 0+0j] DARK STATE
for i in range(1,len(t)):
# span for next time step
tspan = [t[i-1],t[i]]
# solve for next step
#z = odeintw(EquationSystemModel, z0, tspan, args=(rabi12, rabi23vst[i], g21, g23, DopplerLaserLinewidth, RepumpLaserLinewidth, DetuningMedioRepump, DetuningDoppler), full_output=True)
z = odeintw(EquationSystemModel, z0, tspan, args=(rabi12, rabi23repump1, g21, g23, DopplerLaserLinewidth, RepumpLaserLinewidth, Detuningrepump1, DetuningDoppler, no_IR_lasers), full_output=True)
# store solution for plotting
rho22sol.append(z[0][1][1])
rho33sol.append(z[0][1][0])
rho11sol.append(z[0][1][2])
rho12sol.append(z[0][1][3])
rho13sol.append(z[0][1][4])
rho23sol.append(z[0][1][5])
# next initial condition
z0 = z[0][1]
if printprogress:
print(i, '/', len(t), end="\r", flush=True)
#Tomo la parte real de los diagonales y el módulo de las coherencias
RealRho22 = [r.real for r in rho22sol]
RealRho11 = [r.real for r in rho11sol]
RealRho33 = [r.real for r in rho33sol]
AbsRho12 = [np.abs(r) for r in rho12sol]
AbsRho13 = [np.abs(r) for r in rho13sol]
AbsRho23 = [np.abs(r) for r in rho23sol]
return t, RealRho11, RealRho22, RealRho33, AbsRho12, AbsRho13, AbsRho23
def RepumpSpectrum(EquationSystemModel, species, SatParDoppler, SatParRep1, SatParRep2, DetDoppler, DetRep2,
glg, glr, tfinOBE=10, pasoOBE=1e-3, frecini=-100, frecfin=100, pasofrec=1, plotSpectrum=True, sweepingrepump=True):
"""
Hace un barrido en la frecuencia del repump.
Considerando que puede haber oscilaciones de la población por haber dos repumps,
"""
g21, g23 = GetTransitionLinewidths(species)
FluorescenceVector = []
t = np.arange(0, tfinOBE*1e-6, pasoOBE*1e-6)
DopplerLaserLinewidth = glg*2*np.pi*1e6
RepumpLaserLinewidth = glr*2*np.pi*1e6
DetuningDoppler = DetDoppler*2*np.pi*1e6
Detuningrepump2 = DetRep2*2*np.pi*1e6
rabi12 = np.sqrt(SatParDoppler*(4*(DetuningDoppler**2) + g21**2)/2)
rabi23repump1 = np.sqrt(SatParRep1*(4*(Detuningrepump1**2) + g23**2)/2)
rabi23repump2 = np.sqrt(SatParRep2*(4*(Detuningrepump2**2) + g23**2)/2)
Repump1DetuningVector = np.arange(frecini*2*np.pi*1e6, frecfin*2*np.pi*1e6, pasofrec*2*np.pi*1e6)
counter = 1
FluorescenceVector = []
for DetRep1 in Repump1DetuningVector:
tini = time.time()
rho33sol = []
rho22sol = []
rho11sol = []
rho12sol = []
rho13sol = []
rho23sol = []
rabi23vst, DetuningMedioRepump = RepumpReferenceSystemModel(rabi23repump1, rabi23repump2, DetRep1, Detuningrepump2, t)
z0 = [0+0j, 0+0j, 1+0j, 0+0j, 0+0j, 0+0j] #formato: [rho33, rho22, rho11, rho12, rho13, rho23]
for i in range(1,len(t)):
# span for next time step
tspan = [t[i-1],t[i]]
# solve for next step
z = odeintw(EquationSystemModel,z0,tspan, args=(rabi12, rabi23vst[i], g21, g23, DopplerLaserLinewidth, RepumpLaserLinewidth, DetuningMedioRepump, DetuningDoppler), full_output=True)
# store solution for plotting
rho22sol.append(z[0][1][1])
rho33sol.append(z[0][1][0])
rho11sol.append(z[0][1][2])
rho12sol.append(z[0][1][3])
rho13sol.append(z[0][1][4])
rho23sol.append(z[0][1][5])
# next initial condition
z0 = z[0][1]
# print(i, '/', len(t))
print
FluorescenceVector.append(np.real(rho12sol[-1]))
tfin = time.time()
print(counter, '/', len(Repump1DetuningVector), ', elapsed time: ', round(tfin-tini, 2), ' s')
counter = counter + 1
if plotSpectrum:
plt.plot([1e-6*d/(2*np.pi) for d in Repump1DetuningVector], [100*f for f in FluorescenceVector], 'o')
plt.xlabel('Repump detuning (MHz')
plt.ylabel('Fluorescence (A.U.)')
plt.title('Doppler detuning: ' + str(DetDoppler) + ' MHz, Repump 2 detuning: ' + str(DetRep2) + ' MHz')
return Repump1DetuningVector, FluorescenceVector
#Funciones auxiliares para el análisis
def FindFluoAtTime(fluo, time, timetarget, pasoti):
"""
Encuentra la fluorescencia para determinado tiempo (en us).
El pasoti es la tolerancia temporal para el cual busca esa fluorescencia
"""
ti = timetarget*1e-6
pasoti = pasoti*1e-6
i = 0
fluoattivec = []
while i < len(fluo):
if abs(time[i]-ti) < 2*pasoti:
fluoattivec.append(fluo[i])
i = i + 1
return np.mean(fluoattivec)
def FindTimeForFluo(fluo, time, fluotarget, tolerancefluo=1e-3):
i = 0
timesforfluo = []
while i < len(time)-1:
if np.abs(fluo[i]-fluotarget) < 2*tolerancefluo:
timesforfluo.append(time[i])
i = i + 1
if len(timesforfluo) == 0:
print('La fluorescencia no toma ese valor')
return 0
elif len(timesforfluo) == 1:
return timesforfluo[0]
else:
# return np.mean(timesforfluo[1:])
return timesforfluo[-1]
def FindCharacteristicDecayTime(fluo, time):
pass
def IntegrateWindow(fluo, time, ti, window):
ti = ti*1e-6
window = window*1e-6
i = 0
fluowindow = []
while i<len(fluo):
if time[i]>ti and time[i]<=ti+window:
fluowindow.append(fluo[i])
i = i + 1
if time[i]>ti+window:
return np.mean(fluowindow)
def Lorentz(x, x0, gamma, ymax):
scale = ymax*np.pi*gamma*0.5
return (scale/np.pi)*(0.5*gamma)/((x-x0)**2 + (0.5*gamma)**2)
def rho23model(rabip, gamma, gammas, detuningp, rho0, t):
rho23 = ((1j)*rho0*rabip/(2*gamma))*(np.exp(-gamma*t) - np.exp(-(gammas+(rabip**2)/(4*gamma))*t -(1j)*detuningp*t))
return [np.real(rho23), np.abs(np.imag(rho23))]
def dopplerBroadening(T, wldopp=0.397e-6, wlrep=0.866e-6, alpha=0, mcalcio = 6.655e-23*1e-3):
"""
Calcula el broadening extra semiclásico por temperatura considerando que el ion atrapado se mueve.
wlr es la longitud de onda doppler, wlp la longitud de onda repump, T la temperatura del ion en kelvin, y alpha (en rads) el ángulo
que forman ambos láseres.
"""
kboltzmann = 1.38e-23 #J/K
gammaD = (2*np.pi)*np.sqrt((1/(wldopp*wldopp)) + (1/(wlrep*wlrep)) - 2*(1/(wldopp*wlrep))*np.cos(alpha))*np.sqrt(kboltzmann*T/(2*mcalcio))
return gammaD