-
Notifications
You must be signed in to change notification settings - Fork 4
/
ion_trap.py
442 lines (278 loc) · 18 KB
/
ion_trap.py
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
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
#!/usr/bin/env python
#from __future__ import division, absolute_import, print_function, unicode_literals
import numpy as np
from numpy import *
from simulation_parameters import SimulationParameters
from equilibrium_positions import equilibrium_positions
from simulation_parameters import OptimizationParameters
from scipy import linalg as LA
from matplotlib.pylab import *
import math
class IonTrap:
def __init__(self, potential_config='generic', freq_reference = 0.0, magnetic_field_gradient = 0.0, laser_axis='Y'):
self.p = SimulationParameters()
self.potential = Potential(potential_config)
self.laser_axis = laser_axis
#Frequency reference is the frequency with respect to
# which all other frequencies in the entire simulation
# are measured:
self.freq_reference = freq_reference
self.magnetic_field_gradient = magnetic_field_gradient
self.loaded = False
def load(self, chain):
self.chain = chain
#Set the position of ions:
if self.potential.config == 'harmonic' or self.potential.config == 'generic':
positions = equilibrium_positions.get_positions(self.chain.num_of_ions, self.potential)
self.chain.set_zpositions( positions )
print("positions ", positions)
self.chain.set_carrier_frequencies(self.freq_reference, self.magnetic_field_gradient)
#Find and set the ion-ion couplings:
self.chain.set_couplings( self.potential.get_trap_radial_freq(self.laser_axis) )
#Set expansion of chain's motion local in terms of normal destruction operators and vice versa, and eigenvalues.
#self.chain.set_normal_mode_structure()
self.loaded = True
def reload(self):
self.load(self.chain)
def set_ions_positions(self, zpositions):
self.potential.config = 'positions'
if self.loaded:
if len(zpositions) == self.chain.num_of_ions:
self.chain.set_zpositions( zpositions )
self.reload()
else:
raise Exception("Z position of ions must be given as a list with length equal to\n number of ions")
else:
raise Exception("An ion chain must be loaded before setting positions.")
'''
def set_ions_positions(self):
self.params.dummy_trap.set_potential(self.potential) #Set trap potential
self.params.dummy_trap.load( self.params.chain ) #load ions with this potential
self.ions_positions = self.params.chain.get_positions()
'''
'''
def set_potential(self, potential ):
""" Take fz = V(x_0, y_0, z), the potential along Z axis
and pass it to the Potential instance.
"""
self.potential = potential
if self.potential.config == 'generic':
fz = self.potential.fz_along_Z
self.potential.set_fz( fz )
if not self.loaded:
self.load(self.chain)
'''
def set_used_electrodes(self, used_electrodes):
self.potential.set_used_electrodes(used_electrodes)
def set_electrode_voltages(self, electrode_voltages):
self.potential.set_electrode_voltages(electrode_voltages)
if self.loaded:
self.reload()
def set_trap_radial_freq(self, radial_freq, axis):
""" Set radial_freq in Hertz
"""
self.potential.set_trap_radial_freq(radial_freq, axis)
if self.loaded:
self.reload()
def get_spectrum(self, laser_orientation='Y'):
couplings = self.chain.get_couplings()
#This is the normal modes due to positioning of ions, if radial frequency correction was zero
for c in couplings:
for cc in c:
if math.isnan(cc):
print couplings
raise Exception("Is not a number")
normal_modes_from_Vz = np.sort(abs(LA.eig(couplings)[0]))[::-1]
#Having a 729 laser with general angle could be added later
#Calculating couplings matrix with radial correction:
couplings_with_radial_correction = couplings - self.p.mass * (self.potential.get_trap_radial_freq(laser_orientation)**2) * identity( self.chain.num_of_ions )
radials = self.potential.get_radial_frequency(laser_orientation)(self.chain.get_positions())
for freq in radials:
if math.isnan(freq):
raise Exception("Is not a number.")
couplings_with_radial_correction += self.p.mass * (radials**2) * identity(self.chain.num_of_ions)
normal_modes_with_radial_correction = np.sort(abs(LA.eig(couplings_with_radial_correction)[0]))[::-1] #This is the normal modes due to a generic potential
return normal_modes_from_Vz, normal_modes_with_radial_correction
'''
def get_couplings(self, laser_orientation='Y'):
couplings = self.chain.get_couplings()
#This is the normal modes due to positioning of ions, if radial frequency correction was zero
for c in couplings:
for cc in c:
if math.isnan(cc):
print couplings
raise Exception("Is not a number")
normal_modes_from_Vz = np.sort(abs(LA.eig(couplings)[0]))[::-1]
#Having a 729 laser with general angle could be added later:
couplings_with_radial_correction = couplings - self.potential.get_trap_radial_freq(laser_orientation) * identity( self.chain.num_of_ions )
radials = self.potential.get_radial_frequency(laser_orientation)(self.chain.get_positions())
#print (self.potential.trap_Y_radial_freq**2. + self.potential.params.charge * self.potential.Y_2nd_deriv/self.potential.params.mass)(self.chain.get_positions())
for freq in radials:
if math.isnan(freq):
raise Exception("Is not a number.")
couplings_with_radial_correction += radials * identity(self.chain.num_of_ions)
#why couplings are wrong??!!
return couplings, couplings_with_radial_correction
'''
def plot_spectrum(self, laser_orientation='Y'):
plot(self.get_spectrum(laser_orientation)[0]/(2*np.pi), 'ro')
plot(self.get_spectrum(laser_orientation)[1]/(2*np.pi), 'bo')
show()
class Potential:
def __init__(self, potential_config, trap_Y_radial_freq=1.8e6, trap_X_radial_freq=1.8e6,
optimization_parameters=OptimizationParameters(),
z_expansion_order=10, X_fit_order=4, Y_fit_order=4):
""" Contains information about total potential which is devided by
0.5*M*omegaz**2 along the Z axis.
func is a numpy.polynomial object, determining V(x_0,y_0, z), potential along the Z trap axis
trap_Y_radial_freq and trap_X_radial_freq are in Hertz
"""
self.z_expansion_order = z_expansion_order
self.Y_fit_order = Y_fit_order
self.X_fit_order = X_fit_order
self.set_axial_freq = NaN
self.energy_deriv_along_Z = NaN
self.set_trap_radial_freq( trap_Y_radial_freq, 'Y')
self.set_trap_radial_freq( trap_X_radial_freq, 'X')
self.params = optimization_parameters #simulation_parameters()
self.config = potential_config
self.electrode_voltages_are_set = False
if self.config == 'harmonic':
if axial_freq == 0:
raise Exception("Axial frequency is not set.")
self.axial_freq = self.params.axial_freq #Assuming that it is already multiplied by 2*np.pi before passing to Potential.
#Have YOU not seen it through the end?
def set_fz(self, func):
""" Given func which is a numpy.polynomial (poly1d) object, determining V(x_0,y_0, z), potential along the Z trap axis,
set func which is a numpy.polynomial (poly1d) object, determining U(x_0,y_0, z), potential energy along the Z trap axis,
Units of coefficients should be in S.I.
"""
self.config = 'generic'
self.energy_along_Z = self.params.charge * func #We need to multiply by electron charge to get potential energy.
def set_axial_freq(self, axial_freq ):
self.axial_freq = 2*np.pi * axial_freq
def set_trap_radial_freq(self, radial_freq, axis):
if axis == 'Y':
self.trap_Y_radial_freq = 2*np.pi * radial_freq
elif axis == 'X':
self.trap_X_radial_freq = 2*np.pi * radial_freq
def get_trap_radial_freq(self, axis):
if axis == 'Y':
return self.trap_Y_radial_freq
elif axis == 'X':
return self.trap_X_radial_freq
def get_radial_frequency(self, laser_orientation='Y'):
""" Get radial axis ('X' or 'Y') and return radial frequency along that direction after correction from
static potential.
"""
if laser_orientation == 'Y':
omega_square = self.trap_Y_radial_freq**2. + self.params.charge * self.Y_2nd_deriv/self.params.mass
elif laser_orientation == 'X':
omega_square = self.trap_X_radial_freq**2. + self.params.charge * self.X_2nd_deriv/self.params.mass
return poly1d( polyfit( self.params.Z, sqrt( omega_square(self.params.Z) ), self.z_expansion_order ) )
def set_used_electrodes(self, used_electrodes):
self.used_electrodes = used_electrodes
def set_electrode_voltages(self, electrode_voltages):
if len(electrode_voltages) != len(self.used_electrodes):
self.electrode_voltages_are_set = False
raise Exception("Number of given electrode voltages does not match with the number of used_electrodes")
else:
self.electrode_voltages = electrode_voltages
self.pot = self.get_potential()
self.V_along_Z_axis = self.get_V_along_Z_axis()
self.V_on_Y_axis_along_Z_axis = self.get_V_on_Y_axis_along_Z_axis()
self.V_on_X_axis_along_Z_axis = self.get_V_on_X_axis_along_Z_axis()
self.Y_1st_deriv, self.Y_2nd_deriv = self.get_Y_1st_and_2nd_deriv_fits_along_Z()
self.X_1st_deriv, self.X_2nd_deriv = self.get_X_1st_and_2nd_deriv_fits_along_Z()
self.fz_along_Z = self.get_fz_along_Z()
self.Z_1st_deriv = self.get_Z_derivative(1)
if self.params.do_print:
print( 'fz', self.fz_along_Z )
self.set_fz( self.fz_along_Z )
self.electrode_voltages_are_set = True
#Brought in from Spectrum
def get_potential(self):
""" Return potential in 3 dimensions
"""
#Note: Substracting each electrode's potential at all points from its value at the trap center
#is good for fitting (so that even if fit with higher orders, the lower order coefficients of fit won't
#change much).
s = 0
arr = 0
for i in self.used_electrodes:
arr += self.electrode_voltages[s]*( self.params.pkl['EL_DC_{}'.format(i)] - self.params.pkl['EL_DC_{}'.format(i)][self.params.trap_center] )
s += 1
return arr
def get_V_along_Z_axis(self):
""" Return an array of potentials along the Z axis with the given electrode voltages
"""
return np.array( [self.pot[self.params.trap_center[0:2]+(z,)] for z in range(self.params.data_size[2])] )
def get_V_on_Y_axis_along_Z_axis(self):
""" Return a list of arrays of potentials on Y axis along the Z asis (one array
for each Z position) with the given electrode voltages
"""
arr = [ np.array( [self.pot[self.params.trap_center[0],y, z] for y in range(self.params.data_size[1])] ) for z in range(self.params.data_size[2]) ]
return arr
def get_V_on_X_axis_along_Z_axis(self):
""" Return a list of arrays of potentials on X axis along the Z asis (one array
for each Z position) with the given electrode voltages
"""
return [ np.array( [ self.pot[x, self.params.trap_center[1], z] for x in range(self.params.data_size[0])] ) for z in range(self.params.data_size[2]) ]
def get_Y_1st_and_2nd_deriv_fits_along_Z(self, Y_fit_order=4):
""" Fit each array in V_on_Y_axis_along_Z_axis, and
return an array of first and an array of second derivatives along
the Z axis with the given electrode voltages.
"""
Y_1st_deriv = []
Y_2nd_deriv = []
for z in range(len(self.params.Z)):
fit_polynomial = polyfit( self.params.Y, self.V_on_Y_axis_along_Z_axis[z] , Y_fit_order)
Y_1st_deriv.append( polyder(poly1d(fit_polynomial), 1)( self.params.Y[self.params.trap_center[1]] ) )
Y_2nd_deriv.append( polyder(poly1d(fit_polynomial), 2)( self.params.Y[self.params.trap_center[1]] ) )
fit_Y_1st_deriv = poly1d( polyfit( self.params.Z, Y_1st_deriv, self.z_expansion_order ) )
fit_Y_2nd_deriv = poly1d( polyfit( self.params.Z, Y_2nd_deriv, self.z_expansion_order ) )
return fit_Y_1st_deriv, fit_Y_2nd_deriv
def get_X_1st_and_2nd_deriv_fits_along_Z(self, X_fit_order=4):
""" Fit each array in V_on_X_axis_along_Z_axis, and
return an array of first and an array of second derivatives along
the Z axis with the given electrode voltages.
"""
X_1st_deriv = []
X_2nd_deriv = []
for z in range(len(self.params.Z)):
fit_polynomial = polyfit( self.params.X, self.V_on_X_axis_along_Z_axis[z] , X_fit_order)
X_1st_deriv.append( polyder(poly1d(fit_polynomial), 1)( self.params.X[self.params.trap_center[1]] ) )
X_2nd_deriv.append( polyder(poly1d(fit_polynomial), 2)( self.params.X[self.params.trap_center[1]] ) )
fit_X_1st_deriv = poly1d( polyfit( self.params.Z, X_1st_deriv, self.z_expansion_order ) )
fit_X_2nd_deriv = poly1d( polyfit( self.params.Z, X_2nd_deriv, self.z_expansion_order ) )
return fit_X_1st_deriv, fit_X_2nd_deriv
def get_fz_along_Z(self):
""" Return f(x_0,y_0, z) in potential along Z axis.
"""
return poly1d(polyfit( self.params.Z, self.V_along_Z_axis, self.z_expansion_order ))
def get_Z_derivative(self, n):
""" Return nth derivative of V(x_0,y_0, z) with respect to z (as a polynomial function of z).
"""
return polyder(self.fz_along_Z, n)
def Laplacian(self, zmin_index, zmax_index ):
self.Z2 = self.get_Z_derivative(1)(self.potential.Z)
self.X2 = self.X_2nd_deriv(self.params.Z)
self.Y2 = self.Y_2nd_deriv(self.params.Z)
self.Laplace_eq = self.X2 + self.Y2 + self.Z2
'''
def get_radial_correction(self, radial):
if radial == 'Y':
#Correction to Y radial frequency:
y2 = 0.5*poly1d(self.Y_2nd_deriv) #coeff of y2 in V(y,z) = y2*alpha*f(z)
omega_y_correction = self.params.y_delta_coeff*y2 /(2*np.pi) #correction to the y radial frequency in Hertz, put z in mm
omega_y_correction_zgradient = 0.001*polyder(omega_y_correction,1) #in Hertz per microns unit, but you put z in mm
return omega_y_correction
elif radial == 'X':
#Correction to X radial frequency:
x2 = 0.5*poly1d(spectrum.X_2nd_deriv) #coeff of y2 in V(y,z) = y2*alpha*f(z)
omega_x_correction = self.params.x_delta_coeff*x2 /(2*np.pi) #correction to the x radial frequency in Hertz, put z in mm
omega_x_correction_zgradient = 0.001*polyder(omega_x_correction,1) #in Hertz per microns unit, but you put z in mm
return omega_x_correction
'''
def update(self, update_func):
self.Vz += update_func