ONV 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
nv/nv.py ADDED
@@ -0,0 +1,2091 @@
1
+ #------------------------------------------------------------------------------------------------------------------------------------------------------------
2
+ #------------------------------------------------------------------------------------------------------------------------------------------------------------
3
+ # NV_library.py by Oliver Whaites
4
+ # A library of functions that are useful when simulating NV systems under Dynamical Decoupling
5
+ #------------------------------------------------------------------------------------------------------------------------------------------------------------
6
+ #------------------------------------------------------------------------------------------------------------------------------------------------------------
7
+
8
+ import numpy as np
9
+ import qutip
10
+ import progressbar as pb
11
+ from tqdm import tqdm
12
+ import matplotlib.pyplot as plt
13
+ import matplotlib as mpl
14
+ from numpy import random
15
+ import pandas as pd
16
+ import scipy
17
+ from matplotlib.ticker import AutoMinorLocator
18
+ from scipy.signal.windows import gaussian
19
+
20
+ options = qutip.solver.Options()
21
+ options.store_states = True
22
+
23
+ """
24
+ OPERATIONS
25
+ """
26
+
27
+ def params():
28
+ """
29
+
30
+ Returns the default parameters that define and evaluate the model.
31
+
32
+ Parameters
33
+ -----------
34
+
35
+
36
+ Returns
37
+ -----------
38
+
39
+ params: dictionary of parameters required to determine and calculate the model
40
+
41
+ """
42
+
43
+ params = {}
44
+
45
+ #general physics constants
46
+ params['gamma_e'] = -28.025e9;#Hz/T
47
+ params['gamma_C'] = 10.705e6;#Hz/T
48
+ params['gamma_H'] = 42.577478518e6;#Hz/T
49
+ params['gamma_N'] = 3.077e6;#Hz/T
50
+ params['gamma_14N'] = 3.077e6;#Hz/T
51
+ params['h'] = 6.62607e-34; #kg*m**2/s
52
+ params['mu_red'] = 1e-7;#N/A**2
53
+ params['zfs_NV'] = 2.87e9
54
+ params['k_B'] = 1.380649e-23
55
+ params['Temp'] = 300
56
+
57
+ # model parameters
58
+ params['B0'] = 403e-4#external magnetic field strength
59
+ params['B_mis'] = np.radians(0)
60
+ params['omegaL'] = 2*np.pi*params['B0']*10.705e6#nuclear larmour frequency
61
+ params['N_nuc'] = 1;#the number of nuclei in the system
62
+ params['Overhauser'] = 0
63
+
64
+ params['decoherence'] = False
65
+ params['T2'] = 1e-3
66
+
67
+
68
+ #pulse protocol parameters
69
+ params['Tp'] = 32e-9#duration of a pi pulse
70
+ params['Omega'] = np.pi/params['Tp']
71
+ params['tau'] = 0#the wait time of the pulse protocol
72
+ params['t_ev'] = 0
73
+ params['delta'] = 0#any pulse error introduced
74
+ params['protocol'] = 'CPMG'
75
+ params['pulse_profile'] = 'inst'
76
+ params['Np'] = 1#number of pulses of protocol
77
+
78
+ #plotting parameters
79
+ params['Delta'] = 0#the width of a tau sweep
80
+ params['Reps'] = 100
81
+
82
+ #evolution time average parameters
83
+ params['t_wait'] = 0
84
+ params['t_wait_mu'] = 13e-6;
85
+ params['t_wait_sig'] = 0.5e-6
86
+ params['num_avg'] = 10
87
+
88
+ #cystal parameters
89
+ params['vecs'] = [[0,0,0],#basis atom
90
+ [0,0.5,0.5],
91
+ [0.5,0,0.5],
92
+ [0.5,0.5,0],
93
+ [0.25,0.25,0.25],#basis atom
94
+ [0.25,0.75,0.75],
95
+ [0.75,0.25,0.75],
96
+ [0.75,0.75,0.25]]
97
+ #all above are normalised, return to real vectors by *lattice constant
98
+ params['bond_size'] = 1.54e-10;# distance between basis atoms = lattice_constant*sqrt(3)/4
99
+ params['lattice_constant'] = 3.57e-10;
100
+
101
+
102
+ return params
103
+
104
+ def plot_params():
105
+ """
106
+
107
+ Returns the default parameters that are used when plotting
108
+
109
+ Parameters
110
+ -----------
111
+
112
+
113
+ Returns
114
+ -----------
115
+
116
+ params: dictionary of parameters required to plot figures
117
+
118
+ """
119
+ # key parameters for the model, as described in Spearman 2018
120
+ params = {}
121
+ # model parameters
122
+ params['background_color'] = 'white';
123
+ params['text_color'] = 'black';
124
+ params['line_color'] = 'black';
125
+ params['line_style'] = '-';
126
+ params['line_width'] = 2
127
+ params['tick_color'] = 'black'
128
+ params['line_alpha'] = 1
129
+
130
+
131
+
132
+ return params
133
+
134
+
135
+ species_spin = {'C':int(1/2*2 + 1),
136
+ '14N':int(1*2 + 1),
137
+ 'N':int(1/2*2 + 1),
138
+ 'NV':int(1/2*2 + 1),
139
+ 'NV_full':int(1*2 + 1),
140
+ 'e': int(1/2*2 + 1)}
141
+
142
+ """
143
+ FUNCTIONS FOR GENERAL QM OPERATIONS
144
+ """
145
+
146
+
147
+ #a function that rotates a 3D vector around the x axis anticlockwise by theta radians
148
+ def x_rotation(vec,theta):
149
+
150
+ Rx = np.array([[1,0,0],[0,np.cos(theta),-np.sin(theta)],[0,np.sin(theta),np.cos(theta)]]);
151
+
152
+ return np.dot(Rx,vec)
153
+
154
+
155
+ #a function that rotates a vector around the y axis anticlockwise by theta radians
156
+ def y_rotation(vec,theta,passive = False):
157
+
158
+ Ry = np.array([[np.cos(theta),0,np.sin(theta)],[0,1,0],[-np.sin(theta),0,np.cos(theta)]]);
159
+ if passive == True:
160
+ Ry = Ry.T
161
+
162
+ if vec.shape == (3,):
163
+
164
+ return np.dot(Ry,vec)
165
+ else:
166
+ return np.array([np.dot(Ry,v) for v in vec])
167
+
168
+ def xy_rotation(vec,theta,phase):
169
+
170
+
171
+ ux = np.cos(phase)
172
+ uy = -np.sin(phase)
173
+ uz = 0
174
+
175
+ c = np.cos(theta)
176
+ s = np.sin(theta)
177
+
178
+ R = np.array([[c + ux**2*(1 - c),ux*uy*(1 - c) - uz*s,ux*uz*(1 - c) + uy*s],
179
+ [uy*ux*(1 - c) + uz*s,c + uy**2*(1 - c),uy*uz*(1 - c) - ux*s],
180
+ [uz*ux*(1 - c) - uy*s,uz*uy*(1 - c) + ux*s,c + uz**2*(1 - c)]])
181
+
182
+ return R.dot(vec)
183
+
184
+ # a function which changes the basis of the lattice coordinates to one in line with NV-axis
185
+ def change_basis(vec):
186
+
187
+ #Two rotations about the z by pi/4 then the x by ...
188
+ vectemp = z_rotation(vec,np.pi/4);
189
+ vectemp = x_rotation(vectemp,np.arctan(np.sqrt(2)));
190
+
191
+
192
+ return vectemp
193
+
194
+
195
+ #a function that rotates a vector around the z axis anticlockwise by theta radians
196
+ def z_rotation(vec,theta):
197
+
198
+ Rz = np.array([[np.cos(theta),-np.sin(theta),0],[np.sin(theta),np.cos(theta),0],[0,0,1]]);
199
+
200
+ return np.dot(Rz,vec)
201
+
202
+
203
+ def cc_coupling(spin,ref,params = params(),species = 'C'):
204
+
205
+ x = spin['x'] - ref['x']
206
+ y = spin['y'] - ref['y']
207
+ z = spin['z'] - ref['z']
208
+
209
+ r = np.sqrt(x**2 + y**2 + z**2); #distance between atoms
210
+
211
+ const = -(params['h']/(2*np.pi))*params['mu_red']*(2*np.pi*params['gamma_{}'.format(species)])**2/(r**3);
212
+
213
+ Cij = const*(3*(z**2)/(r**2) - 1);
214
+
215
+ return Cij/(2*np.pi)
216
+
217
+
218
+ #function to find the hyperfine coupling constants between the vacancy and a particular atom in the lattice
219
+ def hyperfine(r,params = params(),species = 'C'):
220
+ #r must be introdued in Angstoms
221
+ x = r[0]*1e-10;
222
+ y = r[1]*1e-10;
223
+ z = r[2]*1e-10;
224
+
225
+
226
+ r = np.sqrt(x**2 + y**2 + z**2); #distance between atoms
227
+
228
+ const = -(params['h']/(2*np.pi))*params['mu_red']*2*np.pi*params['gamma_e']*2*np.pi*params['gamma_{}'.format(species)]/(r**3);
229
+
230
+ #find the coupling constants in units kHz*2pi/T
231
+ Ax = const*(3*z*x/(r**2))*1e-3;
232
+ Ay = const*(3*z*y/(r**2))*1e-3;
233
+ Az = const*(3*(z*z)/(r**2) - 1)*1e-3;
234
+
235
+ A = [Ax/(2*np.pi),Ay/(2*np.pi),Az/(2*np.pi)];
236
+
237
+ return A
238
+
239
+
240
+ #returns the density matrix for a qubit up state
241
+ def rhoU():
242
+
243
+ return qutip.Qobj([[1,0],[0,0]]);
244
+
245
+ #returns the density matrix for a qubit down state
246
+ def rhoD():
247
+
248
+ return qutip.Qobj([[0,0],[0,1]]);
249
+
250
+ #returns the density matrix for a qubit Xp state
251
+ def rhoXp():
252
+
253
+ return (1/2)*qutip.Qobj([[1,1],[1,1]]);
254
+
255
+ #returns the density matrix for a qubit X- state
256
+ def rhoXm():
257
+
258
+ return (1/2)*qutip.Qobj([[1,-1],[-1,1]]);
259
+
260
+ #returns the density matrix for a qubit in a thermal state
261
+ def rhoT(params = params(), species = 'C'):
262
+
263
+ s = species_spin[species]
264
+
265
+ r =(-(params['h']*params[f'gamma_{species}']*params['B0']*Iz(s))/(2*np.pi*params['k_B']*params['Temp'])).expm();
266
+
267
+ return r/r.tr()
268
+ #returns the density matrix for a qubit in a thermal state
269
+ def rhoSup(a = np.cos(2*np.pi/3),b = np.sin(2*np.pi/3)):
270
+
271
+ A = (a + b)**2
272
+ B = (a - b)**2
273
+ AB = (a**2 - b**2)
274
+
275
+ return (1/2)*qutip.Qobj([[A,AB],[AB,B]]);
276
+
277
+ #A function which returns the reduced quibit Qobj for Sx
278
+ def qubit_Sx():
279
+
280
+ return qutip.Qobj(np.array([[0,1/2],[1/2,0]]));
281
+
282
+ #A function which returns the reduced quibit Qobj for Sz in ms = {0,-1} space
283
+ def qubit_Szp():
284
+
285
+ return qutip.Qobj(np.array([[0,0],[0,1]]));
286
+
287
+ def qubit_Szm():
288
+
289
+ return qutip.Qobj(np.array([[0,0],[0,-1]]));
290
+
291
+ #A function which returns the reduced quibit Qobj for Sz
292
+ def qubit_Sy():
293
+
294
+ return qutip.Qobj(np.array([[0,-1j/2],[1j/2,0]]));
295
+
296
+ #A function which returns the reduced quibit Qobj for Sx
297
+ def state_3_Sx():
298
+
299
+ return qutip.Qobj((1/np.sqrt(2))*np.array([[0,1,0],[1,0,1],[0,1,0]]));
300
+
301
+ #A function which returns the reduced quibit Qobj for Sz
302
+ def state_3_Sz():
303
+
304
+ return qutip.Qobj(np.array([[1,0,0],[0,0,0],[0,0,-1]]));
305
+
306
+ #A function which returns the reduced quibit Qobj for Sz
307
+ def state_3_Sy():
308
+
309
+ return qutip.Qobj((1/np.sqrt(2))*np.array([[0,-1j,0],[1j,0,-1j],[0,1j,0]]));
310
+
311
+ #A function which returns the quibit Qobj for Ix
312
+ def Ix(s = 2):
313
+
314
+ if s == 2:
315
+ return qutip.Qobj(np.array([[0,1/2],[1/2,0]]));
316
+
317
+ elif s == 3:
318
+ return state_3_Sx()
319
+
320
+ else: raise Exception('s = %s is not supported'%s)
321
+
322
+ #A function which returns the quibit Qobj for Iz
323
+ def Iz(s = 2):
324
+
325
+ if s == 2:
326
+ return qutip.Qobj(np.array([[1/2,0],[0,-1/2]]));
327
+ elif s == 3:
328
+ return state_3_Sz()
329
+
330
+ else: raise Exception('s = %d is not supported'%(s))
331
+
332
+ #A function which returns the reduced quibit Qobj for Sz
333
+ def Iy(s = 2):
334
+ if s == 2:
335
+ return qutip.Qobj(np.array([[0,-1j/2],[1j/2,0]]));
336
+ elif s == 3:
337
+ return state_3_Sy()
338
+
339
+ else: raise Exception('s = %s is not supported'%s)
340
+
341
+ #function that constructs the spin ith operator in a tensor space size 2^NumSpin
342
+ def spin_tensor_operator(NumSpin, i,space_size = None):
343
+
344
+ """
345
+ function which generates a spin operator of spin i in a NumSpin spin system
346
+
347
+ Parameters
348
+ ----------
349
+
350
+ NumSpin: int for the number of spins in tensor space
351
+ i: int for the position of the spin in the tensor space
352
+
353
+ returns
354
+ _______
355
+
356
+ array of spin operators:
357
+ Iz: Qobj for the z-directional spin operator.
358
+ Iy: Qobj for the y-directional spin operator.
359
+ Ix: Qobj for the x-directional spin operator.
360
+
361
+
362
+ """
363
+ if space_size == None:
364
+ space_size = 2*np.ones(NumSpin)
365
+
366
+
367
+ I = qutip.identity(int(space_size[0]));
368
+
369
+ if i == 0:
370
+
371
+ Ixi = Ix(space_size[i]);
372
+ Iyi = Iy(space_size[i]);
373
+ Izi = Iz(space_size[i]);
374
+
375
+ else:
376
+
377
+ Ixi = I;
378
+ Iyi = I;
379
+ Izi = I;
380
+
381
+ for j in range(1,NumSpin):
382
+
383
+ I = qutip.identity(int(space_size[j]));
384
+
385
+ if j == i:
386
+
387
+ Ixi = qutip.tensor(Ixi,Ix(space_size[j]));
388
+ Iyi = qutip.tensor(Iyi,Iy(space_size[j]));
389
+ Izi = qutip.tensor(Izi,Iz(space_size[j]));
390
+
391
+ else:
392
+
393
+ Ixi = qutip.tensor(Ixi,I);
394
+ Iyi = qutip.tensor(Iyi,I);
395
+ Izi = qutip.tensor(Izi,I);
396
+
397
+
398
+ return [Ixi,Iyi,Izi]
399
+
400
+
401
+ Rho = {'up':rhoU,
402
+ 'down':rhoD,
403
+ 'Xp':rhoXp,
404
+ 'Xm':rhoXm,
405
+ 'Therm':rhoT,
406
+ 'Sup':rhoSup}
407
+
408
+
409
+ def gaussianEnv(t,args):
410
+ """
411
+
412
+
413
+ """
414
+
415
+ #tw = args['tw']
416
+ #t0 = args['t0']
417
+
418
+
419
+ #r2sigma2 = 1/(2*tw**2)
420
+ #gaussian_pulse = np.exp(-((t-t0)**2)*r2sigma2)
421
+ if args['finite'] == True:
422
+ gaussian_pulse = gaussian(args['num_t'],args['std'])
423
+ else:
424
+ gaussian_pulse = scipy.signal.gausspulse(t,2*np.pi/args['tw'],retenv = True)[1]
425
+
426
+
427
+ if args['plot'] == True:
428
+
429
+ fig,ax = plt.subplots(dpi = 200)
430
+ #ax.plot(x,L)
431
+ y = gaussian_pulse
432
+ ax.plot(np.array(t)*1e9,y,color = 'k',label = 'Gaussian')
433
+ ax.set_ylabel('Amplitude',fontsize = 12)
434
+ ax.set_xlabel('Time (ns)',fontsize = 12)
435
+ i = 0
436
+ for ti, A in zip(t,y):
437
+ if i == 0:
438
+ ax.fill_between(np.array([ti,ti + args['dt']])*1e9,0,A,color = 'blue',alpha = 0.4,edgecolor = 'k',label = 'L Amp')
439
+ i +=1
440
+ else: ax.fill_between(np.array([ti,ti + args['dt']])*1e9,0,A,color = 'blue',alpha = 0.4,edgecolor = 'k')
441
+ ax.legend()
442
+
443
+ return gaussian_pulse
444
+
445
+
446
+
447
+
448
+ """
449
+ OPERATOR AND HAMILTONIAN GENERATORS
450
+ """
451
+
452
+
453
+ def generate_operators(Az, Ax,species = None, params = params(), NV_state = 'up', Nuc_state = None,NV_sub = '-'):
454
+
455
+ """
456
+ function which generates the evolution operators for a particular system
457
+
458
+ Parameters
459
+ ----------
460
+
461
+ NV_state: string to describe the initial state of the NV. default is up
462
+ Nuc_state: array of strings with length of the number of spins in the system
463
+ to describe the state of the NV. Default = None which will
464
+ set nuclear states to thermal mixture Therm
465
+ params: dict of system and protocol parameters. Can be retrieved using
466
+ params() function
467
+ Az: array of floats which contains the parallel coupling for the spins in system
468
+ length of this array determines how many spins there are.
469
+ Ax: array of floats which contains the perpendicular couplings
470
+ N14: boo determining whether to include N14 spin
471
+
472
+
473
+ returns
474
+ _______
475
+
476
+ dict_operators: dict containing operators under the following labels
477
+
478
+ Iz: array of Qobjs for the z-directional spin operators. size of array
479
+ is that of the number of spins
480
+ Iy: array of Qobjs for the y-directional spin operators. size of array
481
+ is that of the number of spins
482
+ Ix: array of Qobjs for the x-directional spin operators. size of array
483
+ is that of the number of spins
484
+ Sz: Qobj for the psuedo spin NV z-directional operator. This is in subspace
485
+ {-1,0}
486
+ Sy: Qobj for the psuedo spin NV y-directional operator. This is in subspace
487
+ {-1,0}
488
+ Sx: Qobj for the psuedo spin NV x-directional operator. This is in subspace
489
+ {-1,0}
490
+ H0: Qobj for the free Hamiltonian of the system
491
+ rho0: Qobj the density matrix for the initial state of the system
492
+ rho0NV: Qobj for the initial density matrix of the NV
493
+
494
+ params: updated list of parameters, where the N_nuc reflects the length of
495
+ Az array input
496
+
497
+
498
+ """
499
+ omegaL = params['omegaL'];
500
+
501
+ NV_dict = {'-':qubit_Szm(),
502
+ '+':qubit_Szp(),
503
+ '+-':qubit_Szm() + qubit_Szp()}
504
+
505
+ dict_operators = {}
506
+ #compute the number of nuclei in the system
507
+ N_nuc = len(Az)
508
+
509
+ params['N_nuc'] = N_nuc
510
+
511
+
512
+ if Nuc_state == None:
513
+ Nuc_state = np.full(N_nuc,'Therm');
514
+
515
+
516
+ if species == None:
517
+ species = []
518
+ for i in range(params['N_nuc']):
519
+ species.append('C')
520
+ print(species)
521
+ species.insert(0, 'NV')
522
+ space_size = [species_spin[s] for s in species]
523
+
524
+ rho0 = Rho[NV_state]();
525
+
526
+ dict_operators['rho0NV'] = Rho[NV_state]();
527
+
528
+
529
+ Sx,Sy,Sz = spin_tensor_operator(N_nuc + 1,0,space_size);
530
+ Sz = NV_dict[NV_sub];
531
+
532
+ I = qutip.identity(space_size[0]);
533
+
534
+ Ix = [];
535
+ Iy = [];
536
+ Iz = [];
537
+
538
+ I_op = I
539
+
540
+ #construct spin operators for nuclei
541
+ for i in range(1,N_nuc + 1,1):
542
+
543
+ rho0 = qutip.tensor(rho0,Rho[Nuc_state[i - 1]](params,species[i]));
544
+
545
+ Ixtemp,Iytemp,Iztemp = spin_tensor_operator(N_nuc + 1,i,space_size);
546
+
547
+ I = qutip.identity(space_size[i])
548
+
549
+ Ix.append(Ixtemp);
550
+ Iy.append(Iytemp);
551
+ Iz.append(Iztemp);
552
+
553
+ Sz = qutip.tensor(Sz,I)
554
+ I_op = qutip.tensor(I_op,I)
555
+
556
+ dict_operators['rho0'] = rho0;
557
+
558
+ dict_operators['Iz'] = Iz;
559
+ dict_operators['Ix'] = Ix;
560
+ dict_operators['Iy'] = Iy;
561
+
562
+ dict_operators['Sx'] = Sx;
563
+ dict_operators['Sy'] = Sy;
564
+ dict_operators['Sz'] = Sz;
565
+
566
+ dict_operators['I'] = I_op
567
+
568
+
569
+ #construct free Hamiltonian
570
+ H0 = params['Overhauser']*Sz
571
+ for i in range(N_nuc):
572
+
573
+ H0 += omegaL*Iz[i] + Az[i]*Sz*Iz[i] + Ax[i]*Sz*Ix[i] #magnetic field term
574
+
575
+ dict_operators['H0'] = H0;
576
+
577
+
578
+
579
+ return dict_operators,params
580
+
581
+
582
+ def add_N14(operators,params,couplings,TN = 3):
583
+ """
584
+
585
+
586
+
587
+
588
+ """
589
+
590
+ Sz = state_3_Sz()
591
+ Sy = state_3_Sy()
592
+ Sx = state_3_Sx()
593
+
594
+ Ix,Iy,Iz, = spin_tensor_operator(params['N_nuc'] + 1,params['N_nuc'] + 2)
595
+
596
+ Ix = qutip.tensor(Ix,Sx)
597
+ Iy = qutip.tensor(Iy,Sy)
598
+ Iz = qutip.tensor(Iz,Sz)
599
+
600
+ I = qutip.identity(3)
601
+
602
+ for i in range(params['N_nuc']):
603
+
604
+ operators['Iz'][i] = qutip.tensor(operators['Iz'][i],I)
605
+ operators['Iy'][i] = qutip.tensor(operators['Iy'][i],I)
606
+ operators['Ix'][i] = qutip.tensor(operators['Ix'][i],I)
607
+
608
+ operators['Sz'] = qutip.tensor(operators['Sz'],I)
609
+ operators['Sy'] = qutip.tensor(operators['Sy'],I)
610
+ operators['Sx'] = qutip.tensor(operators['Sx'],I)
611
+
612
+
613
+ operators['Iz'].append(Iz)
614
+ operators['Iy'].append(Iy)
615
+ operators['Ix'].append(Ix)
616
+
617
+ operators['I'] = qutip.tensor(operators['I'],I)
618
+
619
+ params['N_nuc'] += 1
620
+
621
+ r0 = qutip.Qobj([[1,0,0],[0,1,0],[0,0,1]])
622
+ #r0 = (-state_3_Sz()/TN).expm()
623
+
624
+
625
+ operators['rho0'] = qutip.tensor(operators['rho0'],r0/r0.tr())
626
+
627
+ Az = -2*np.pi*2.164689e6
628
+ Ax = -2*np.pi*2.632e6
629
+
630
+ omega = np.sqrt((params['B0']*2*np.pi*params['gamma_N'] + Az/2)**2 + (Ax/2)**2)
631
+
632
+ # couplings = couplings.append({'Species': 'N',
633
+ # 'Ax': Ax,
634
+ # 'Az': Az,
635
+ # 'omega': omega},ignore_index = True)
636
+
637
+ couplings = pd.concat([couplings, pd.DataFrame([{'Species': 'N',
638
+ 'Ax': Ax,
639
+ 'Az': Az,
640
+ 'omega': omega}],index = [0])],ignore_index = True)
641
+
642
+ return operators, params, couplings
643
+
644
+
645
+ def generate_H0(operators,couplings,params):
646
+
647
+ """
648
+ a function which re-froms the free Hamiltonian for a system of operators
649
+
650
+ Parameters
651
+ ----------
652
+
653
+ operators: a dict of operators for the system which can be retrieved using the
654
+ generate operators function
655
+ couplings: dataframe of Az and Ax couplings of nucleat spins
656
+ params: dict of system parameters which define the protocol. Can be found
657
+ using the params() function
658
+
659
+
660
+ returns
661
+ -------
662
+
663
+ H0: a Qobj for the free Hamiltonian
664
+ """
665
+
666
+ N_nuc = params['N_nuc'];
667
+ #omegaL = params['omegaL']
668
+
669
+ Iz = operators['Iz']
670
+ Ix = operators['Ix']
671
+
672
+ Sz = operators['Sz']
673
+
674
+ #construct free Hamiltonian
675
+ H0 = params['Overhauser']*Sz
676
+ for i in range(N_nuc):
677
+
678
+ s = couplings.index.tolist()[i]
679
+
680
+ Ax = couplings['Ax'].loc[s]
681
+ Az = couplings['Az'].loc[s]
682
+ species = couplings['Species'].loc[s]
683
+
684
+ omegaL = 2*np.pi*params['B0']*params['gamma_%s'%species]
685
+
686
+ H0 += omegaL*Iz[i] + Az*Sz*Iz[i] + Ax*Sz*Ix[i] #magnetic field term
687
+
688
+ operators['H0'] = H0;
689
+
690
+
691
+
692
+ return operators
693
+
694
+ def generate_full_H0(operators,couplings,params,CC_coupling = False):
695
+
696
+ """
697
+ a function which re-froms the free Hamiltonian for a system of operators
698
+
699
+ Parameters
700
+ ----------
701
+
702
+ operators: a dict of operators for the system which can be retrieved using the
703
+ generate operators function
704
+ Az: dataframe for couplings
705
+ params: dict of system parameters which define the protocol. Can be found
706
+ using the params() function
707
+ inst: bool to determine whether pulses are instantaneous. Default is False
708
+
709
+
710
+ returns
711
+ -------
712
+
713
+ H0: a Qobj for the free Hamiltonian
714
+ """
715
+
716
+
717
+
718
+
719
+ N_nuc = params['N_nuc'];
720
+
721
+
722
+ Iz = operators['Iz']
723
+ Ix = operators['Ix']
724
+ Iy = operators['Iy']
725
+
726
+
727
+ Sz = operators['Sz']
728
+ Sx = operators['Sx']
729
+ #Sy = operators['Sy']
730
+
731
+ #construct free Hamiltonian
732
+ H0 = params['Overhauser']*Sz + 2*np.pi*params['B0']*params['gamma_e']*Sz + 2*np.pi*params['gamma_e']*params['B_mis']*Sx + 2*np.pi*params['zfs_NV']*Sz*Sz
733
+ H0p = params['Overhauser']*Sz
734
+ for i in range(N_nuc):
735
+ s1 = couplings.index.tolist()[i]
736
+
737
+ Ax = couplings['Ax'].loc[s1]
738
+ Az = couplings['Az'].loc[s1]
739
+
740
+ species = couplings['Species'].loc[s1]
741
+
742
+ omegaL = 2*np.pi*params['B0']*params['gamma_%s'%species]
743
+ omegaLx = 2*np.pi*params['B_mis']*params['gamma_%s'%species]
744
+
745
+ H0 += omegaL*Iz[i] +omegaLx*Ix[i] + Az*Sz*Iz[i] + Ax*Sz*Ix[i] #magnetic field term
746
+ H0p += omegaL*Iz[i] + omegaLx*Ix[i] + Az*Sz*Iz[i] + Ax*Sz*Ix[i]
747
+ if CC_coupling == True:
748
+ for j in range(i,N_nuc):
749
+ s2 = couplings.index.tolist()[j]
750
+
751
+ C = couplings['C{}'.format(s2)].loc[s1]
752
+ if i != j and abs(C) > 0:
753
+ H0 += C*(Iz[i]*Iz[j] + Ix[i]*Ix[j] + Iy[i]*Iy[j])
754
+ H0p += C*(Iz[i]*Iz[j] + Ix[i]*Ix[j] + Iy[i]*Iy[j])
755
+
756
+ operators['H0'] = H0;
757
+
758
+ operators['H0_pulse'] = H0p
759
+
760
+ return operators
761
+
762
+
763
+
764
+
765
+
766
+
767
+
768
+ """
769
+ PULSE PROFILES
770
+ """
771
+
772
+
773
+ def inst_pulse(operators,params,rot = np.pi):
774
+ """
775
+
776
+
777
+ """
778
+
779
+
780
+ #Omega = params['Omega'];
781
+ delta = params['delta'];
782
+ #Tp = (rot + delta)/Omega
783
+
784
+ Tp = params['Tp']
785
+ Omega = (abs(rot + delta))/Tp
786
+
787
+
788
+ UX = (-1j*(np.sign(rot)*Omega*operators['Sx'])*Tp).expm();
789
+ UY = (-1j*(np.sign(rot)*Omega*operators['Sy'])*Tp).expm();
790
+
791
+ return UX, UY
792
+
793
+ def square_pulse(operators,params, rot = np.pi):
794
+ """
795
+
796
+
797
+ """
798
+
799
+ #Omega = params['Omega'];
800
+ delta = params['delta'];
801
+ #Tp = (rot + delta)/Omega
802
+
803
+ Tp = params['Tp']
804
+ Omega = (abs(rot + delta))/Tp
805
+
806
+ H0 = operators['H0']
807
+ if params['full_H0'] == True:
808
+ H0 = operators['H0_pulse']
809
+
810
+
811
+ UX = (-1j*(H0 + np.sign(rot)*Omega*operators['Sx'])*Tp).expm();
812
+ UY = (-1j*(H0 + np.sign(rot)*Omega*operators['Sy'])*Tp).expm();
813
+
814
+
815
+ return UX, UY
816
+
817
+ def gaussian_pulse(operators,params, rot = np.pi, direction = 'x',dt = 1e-9):
818
+ """
819
+
820
+
821
+ """
822
+
823
+
824
+
825
+ Tp = params['Tp']
826
+ tw = Tp/2
827
+
828
+
829
+ t = np.linspace(-tw/2,tw/2,100)
830
+ #t = [0 + n*dt for n in range(int(Tp/dt))]
831
+
832
+
833
+ H0 = operators['H0']
834
+ if params['full_H0'] == True:
835
+ H0 = operators['H0_pulse']
836
+ Om = 1.6824844873848788/Tp
837
+ Hp = (2*rot/np.pi)*2*np.pi*Om*operators['S%s'%direction]
838
+
839
+ H = [H0,[Hp,gaussianEnv]]
840
+
841
+ output = qutip.mesolve(H,
842
+ operators['rho'],
843
+ t,
844
+ e_ops = [operators['Sz']],
845
+ args = {'finite':False,
846
+ 'tw': tw,
847
+ 'plot': False},
848
+ options = options)
849
+
850
+ operators['rho'] = output.states[-1]
851
+
852
+
853
+ return operators
854
+
855
+ def gaussian_pulse_finite(operators,params, rot = np.pi,dt = 1e-9):
856
+ """
857
+
858
+
859
+ """
860
+ #print('here')
861
+
862
+ Tp = params['Tp']
863
+
864
+ Omega = params['Omega']
865
+ #f = (np.abs(rot)/np.pi)
866
+
867
+ #t,dt = np.linspace(-Tp*f/2,Tp*f/2,int(Tp*f/dt) + 1,restep = True)
868
+ t = [0 + n*dt for n in range(int(Tp/dt))]
869
+
870
+ args = {'num_t':len(t),
871
+ 'std':len(t)/5,
872
+ 'finite':True,
873
+ 'plot':False,
874
+ 'dt': dt}
875
+
876
+ env = gaussianEnv(t, args = args)
877
+
878
+ #A = 2*np.pi*1.0432600729457604/Tp*f
879
+ #A = 2*np.pi/(2*Tp*f)
880
+ A = Omega
881
+
882
+ H0 = operators['H0']
883
+ if params['full_H0'] == True:
884
+ H0 = operators['H0_pulse']
885
+
886
+ UX = operators['I']
887
+ UY = operators['I']
888
+ for i in range(len(t)):
889
+ UX = (-1j*(H0 + A*env[i]*operators['Sx'])*dt).expm()*UX
890
+ UY = (-1j*(H0 + A*env[i]*operators['Sy'])*dt).expm()*UY
891
+
892
+
893
+
894
+ return UX,UY
895
+
896
+ pulse_profiles = {'inst': inst_pulse,
897
+ 'square': square_pulse,
898
+ 'gaussian_finite':gaussian_pulse_finite,
899
+ 'gaussian':gaussian_pulse}
900
+
901
+
902
+
903
+
904
+
905
+
906
+
907
+ """
908
+ PULSE PROTOCOLS
909
+ """
910
+
911
+
912
+ def Free(operators,params,measure,e_ops = [],c_ops = []):
913
+
914
+ """
915
+ a function which constructs the free evolution operator
916
+
917
+ Parameters
918
+ ----------
919
+
920
+ operators: a dict of operators for the system which can be retrieved using the
921
+ generate operators function
922
+ params: float for free evolution time
923
+
924
+
925
+ returns
926
+ -------
927
+
928
+ U: Qobj for the free evolution
929
+ """
930
+
931
+ if params['decoherence'] == False:
932
+
933
+ U0 = (-1j*operators['H0']*params['t_ev']).expm();
934
+
935
+ rho = U0*operators['rho']*U0.dag()
936
+
937
+ coh = (operators['S%s'%measure]*rho).tr().real
938
+
939
+ return coh, rho, U0
940
+
941
+ elif params['decoherence'] == True:
942
+ t = np.linspace(0,params['t_ev'],50)
943
+
944
+ output = qutip.mesolve(operators['H0'],operators['rho'],t, e_ops = e_ops,c_ops = c_ops,options = options)
945
+
946
+
947
+ return output.expect[0][-1], output.states[-1]
948
+
949
+ else: raise Exception('Invalid value for params["decoherence"]. Must be bool not {params["decoherence"]}.')
950
+
951
+
952
+
953
+ # a function which takes a free evolution operator and pulse operators and returns a packet of CPMG. Note that UX must be a pi/2 pulse
954
+ def XY8(operators, params,measure):
955
+
956
+ """
957
+ a function which constructs the pulse packet for a CPMG sequence using free evolution
958
+ and pi/2 pulses. pulse errors, delta, may be included as Tp = (np.pi + delta)/2.
959
+ CPMG sequence here is U0*UX*UX*U0*U0*UX*UX*U0
960
+
961
+ Parameters
962
+ ----------
963
+
964
+ operators: a dict of operators for the system which can be retrieved using the
965
+ generate operators function
966
+ params: dict of system parameters which define the protocol. Can be found
967
+ using the params() function
968
+ inst: bool to determine whether pulses are instantaneous. Default is False
969
+
970
+
971
+ returns
972
+ -------
973
+
974
+ U: Qobj for the CPMG pulse packet
975
+ """
976
+
977
+ tau = params['tau'];
978
+ profile = params['pulse_profile']
979
+
980
+ params['t_ev'] = tau/2
981
+
982
+ #construct the pulse operators
983
+ UX, UY = pulse_profiles[profile](operators,params,rot = np.pi)
984
+
985
+ if params['decoherence'] == False:
986
+
987
+ U0 = (-1j*operators['H0']*params['t_ev']).expm();
988
+
989
+
990
+ U = (U0*UX*U0*U0*UY*U0*U0*UX*U0*U0*UY*U0*U0*UY*U0*U0*UX*U0*U0*UY*U0*U0*UX*U0)**(params['Np'])
991
+
992
+
993
+ rho = U*operators['rho0']*U.dag();
994
+
995
+ coh = (operators['S%s'%measure]*rho).tr().real
996
+
997
+
998
+ return coh,rho, U
999
+
1000
+ elif params['decoherence'] == True:
1001
+ #raise Exception('Decoherence has not been initialised with this pulse protocol.')
1002
+ rho = operators['rho0']
1003
+ params['t_ev'] = tau
1004
+
1005
+ for i in range(params['Np']):
1006
+
1007
+ operators['rho'] = rho
1008
+
1009
+ params['t_ev'] = tau/2
1010
+ coh, rho = Free(operators, params, measure,e_ops = [operators['Sz']],c_ops = [(1/np.sqrt(params['T2']))*operators['Sx']])
1011
+ operators['rho'] = UX*rho*UX.dag()
1012
+
1013
+ params['t_ev'] = tau
1014
+ coh, rho = Free(operators, params, measure,e_ops = [operators['Sz']],c_ops = [(1/np.sqrt(params['T2']))*operators['Sx']])
1015
+
1016
+ operators['rho'] = UY*rho*UY.dag()
1017
+ coh, rho = Free(operators, params, measure,e_ops = [operators['Sz']],c_ops = [(1/np.sqrt(params['T2']))*operators['Sx']])
1018
+
1019
+ operators['rho'] = UX*rho*UX.dag()
1020
+ coh, rho = Free(operators, params, measure,e_ops = [operators['Sz']],c_ops = [(1/np.sqrt(params['T2']))*operators['Sx']])
1021
+
1022
+ operators['rho'] = UY*rho*UY.dag()
1023
+ coh, rho = Free(operators, params, measure,e_ops = [operators['Sz']],c_ops = [(1/np.sqrt(params['T2']))*operators['Sx']])
1024
+
1025
+ operators['rho'] = UY*rho*UY.dag()
1026
+ cho, rho = Free(operators, params, measure,e_ops = [operators['Sz']],c_ops = [(1/np.sqrt(params['T2']))*operators['Sx']])
1027
+
1028
+ operators['rho'] = UX*rho*UX.dag()
1029
+ cho, rho = Free(operators, params, measure,e_ops = [operators['Sz']],c_ops = [(1/np.sqrt(params['T2']))*operators['Sx']])
1030
+
1031
+ operators['rho'] = UY*rho*UY.dag()
1032
+ cho, rho = Free(operators, params, measure,e_ops = [operators['Sz']],c_ops = [(1/np.sqrt(params['T2']))*operators['Sx']])
1033
+
1034
+ operators['rho'] = UX*rho*UX.dag()
1035
+ params['t_ev'] = tau/2
1036
+ cho, rho = Free(operators, params, measure,e_ops = [operators['Sz']],c_ops = [(1/np.sqrt(params['T2']))*operators['Sx']])
1037
+
1038
+ operators['rho'] = rho
1039
+
1040
+ coh = (operators['rho']*operators['Sz']).tr().real
1041
+
1042
+ return coh, operators['rho']
1043
+
1044
+ else: raise Exception('Invalid value for params["decoherence"]. Must be bool not {params["decoherence"]}.')
1045
+
1046
+
1047
+
1048
+
1049
+ # a function which takes a free evolution operator and pulse operators and returns a packet of CPMG. Note that UX must be a pi/2 pulse
1050
+ def CPMG(operators, params,measure):
1051
+
1052
+ """
1053
+ a function which constructs the pulse packet for a CPMG sequence using free evolution
1054
+ and pi/2 pulses. pulse errors, delta, may be included as Tp = (np.pi + delta)/2.
1055
+ CPMG sequence here is U0*UX*UX*U0*U0*UX*UX*U0
1056
+
1057
+ Parameters
1058
+ ----------
1059
+
1060
+ operators: a dict of operators for the system which can be retrieved using the
1061
+ generate operators function
1062
+ params: dict of system parameters which define the protocol. Can be found
1063
+ using the params() function
1064
+ inst: bool to determine whether pulses are instantaneous. Default is False
1065
+
1066
+
1067
+ returns
1068
+ -------
1069
+
1070
+ U: Qobj for the CPMG pulse packet
1071
+ """
1072
+
1073
+ tau = params['tau'];
1074
+ profile = params['pulse_profile']
1075
+
1076
+ params['t_ev'] = tau/2
1077
+
1078
+ if params['decoherence'] == False:
1079
+
1080
+
1081
+ U0 = (-1j*operators['H0']*params['t_ev']).expm();
1082
+
1083
+
1084
+ UX, UY = pulse_profiles[profile](operators,params,rot = np.pi)
1085
+
1086
+
1087
+ U = (U0*UX*U0*U0*UX*U0)**params['Np']
1088
+
1089
+ rho = U*operators['rho0']*U.dag();
1090
+
1091
+ coh = (operators['S%s'%measure]*rho).tr().real
1092
+
1093
+
1094
+ return coh,rho, U
1095
+
1096
+ elif params['decoherence'] == False:
1097
+ raise Exception('Decoherence has not been initialised with this pulse protocol.')
1098
+
1099
+
1100
+
1101
+ else: raise Exception('Invalid value for params["decoherence"]. Must be bool not {params["decoherence"]}.')
1102
+
1103
+
1104
+
1105
+ # a function which takes a free evolution operator and pulse operators and returns a packet of CPMG. Note that UX must be a pi/2 pulse
1106
+ def spin_locking(operators, params,measure):
1107
+
1108
+ """
1109
+ a function which constructs the pulse packet for a CPMG sequence using free evolution
1110
+ and pi/2 pulses. pulse errors, delta, may be included as Tp = (np.pi + delta)/2.
1111
+ CPMG sequence here is U0*UX*UX*U0*U0*UX*UX*U0
1112
+
1113
+ Parameters
1114
+ ----------
1115
+
1116
+ operators: a dict of operators for the system which can be retrieved using the
1117
+ generate operators function
1118
+ params: dict of system parameters which define the protocol. Can be found
1119
+ using the params() function
1120
+ inst: bool to determine whether pulses are instantaneous. Default is False
1121
+
1122
+
1123
+ returns
1124
+ -------
1125
+
1126
+ U: Qobj for the CPMG pulse packet
1127
+ """
1128
+
1129
+ Omega = params['Omega'];
1130
+ tau = params['tau'];
1131
+
1132
+
1133
+ params['t_ev'] = tau/2
1134
+
1135
+
1136
+ if params['decoherence'] == False:
1137
+ #construct the pulse operators
1138
+ U = (-1j*(operators['H0'] + Omega*operators['Sx'])*tau).expm();
1139
+
1140
+ rho = U*operators['rho']*U.dag();
1141
+
1142
+ coh = (operators['S%s'%measure]*rho).tr().real
1143
+
1144
+
1145
+ return coh,rho, U
1146
+
1147
+
1148
+ elif params['decoherence'] == False:
1149
+ raise Exception('Decoherence has not been initialised with this pulse protocol.')
1150
+
1151
+ else: raise Exception('Invalid value for params["decoherence"]. Must be bool not {params["decoherence"]}.')
1152
+
1153
+
1154
+
1155
+ # a function which takes a free evolution operator and pulse operators and returns a packet of PulsePol. Note that UX and UY must be pi/2 pulses
1156
+ def PulsePol(operators,params,measure):
1157
+
1158
+ """
1159
+ a function which constructs the pulse packet for a PulsePol sequence using free evolution
1160
+ and pi/2 pulses.
1161
+ PulsePol sequence here is (UX*U0*UY*UY*U0*UX*UY*U0*UXm*UXm*U0*UY)**2
1162
+
1163
+ Parameters
1164
+ ----------
1165
+
1166
+ operators: a dict of operators for the system which can be retrieved using the
1167
+ generate operators function
1168
+ params: dict of system parameters which define the protocol. Can be found
1169
+ using the params() function
1170
+ inst: bool to determine whether pulses are instantaneous. Default is False
1171
+
1172
+ returns
1173
+ -------
1174
+
1175
+ U: Qobj for the CPMG pulse packet
1176
+ """
1177
+
1178
+ tau = params['tau'];
1179
+ profile = params['pulse_profile']
1180
+
1181
+ if params['decoherence'] == False:
1182
+
1183
+ U0 = (-1j*operators['H0']*tau).expm();
1184
+
1185
+ #construct the pulse operators
1186
+ UX, UY = pulse_profiles[profile](operators,params,rot = np.pi/2)
1187
+
1188
+
1189
+ UXm = (UX).dag()
1190
+ UYm = (UY).dag()
1191
+ U = (UX*U0*UY*UY*U0*UX*UY*U0*UXm*UXm*U0*UY)**params['Np']
1192
+ #U = (UX*U0*UYm*UYm*U0*UX*UY*U0*UX*UX*U0*UY*UX*U0*UY*UY*U0*UX*UY*U0*UXm*UXm*U0*UY)**params['Np']
1193
+
1194
+
1195
+ rho = U*operators['rho0']*U.dag();
1196
+
1197
+ coh = (operators['S%s'%measure]*rho).tr().real
1198
+
1199
+ return coh,rho, U
1200
+
1201
+
1202
+ elif params['decoherence'] == False:
1203
+ raise Exception('Decoherence has not been initialised with this pulse protocol.')
1204
+
1205
+ else: raise Exception('Invalid value for params["decoherence"]. Must be bool not {params["decoherence"]}.')
1206
+
1207
+
1208
+
1209
+ def PulsePol_cam(operators,params,measure):
1210
+
1211
+ """
1212
+ a function which constructs the pulse packet for a PulsePol sequence using free evolution
1213
+ and pi/2 pulses.
1214
+ PulsePol sequence here is (UX*U0*UY*UY*U0*UX*UY*U0*UXm*UXm*U0*UY)**2
1215
+
1216
+ Parameters
1217
+ ----------
1218
+
1219
+ operators: a dict of operators for the system which can be retrieved using the
1220
+ generate operators function
1221
+ params: dict of system parameters which define the protocol. Can be found
1222
+ using the params() function
1223
+ inst: bool to determine whether pulses are instantaneous. Default is False
1224
+
1225
+ returns
1226
+ -------
1227
+
1228
+ U: Qobj for the CPMG pulse packet
1229
+ """
1230
+
1231
+ tau = params['tau'];
1232
+ profile = params['pulse_profile']
1233
+ #Tp = params['Tp']
1234
+ Omega = params['Omega']
1235
+
1236
+ params['Omega'] = Omega/2
1237
+ UX, UY = pulse_profiles[profile](operators,params,rot = np.pi/2)
1238
+
1239
+ #params['Tp'] = Tp/2
1240
+ params['Omega'] = Omega
1241
+ UXp, UYp = pulse_profiles[profile](operators,params,rot = np.pi)
1242
+
1243
+ UXm = (UXp).dag()
1244
+
1245
+ if params['decoherence'] == False:
1246
+
1247
+ U0 = (-1j*operators['H0']*tau).expm();
1248
+
1249
+
1250
+ U = (UX*U0*UYp*U0*UX*UY*U0*UXm*U0*UY)**params['Np']
1251
+
1252
+ rho = U*operators['rho0']*U.dag();
1253
+
1254
+ coh = (operators['S%s'%measure]*rho).tr().real
1255
+
1256
+
1257
+ return coh,rho, U
1258
+
1259
+ elif params['decoherence'] == True:
1260
+ rho = operators['rho0']
1261
+ params['t_ev'] = tau
1262
+
1263
+ for i in range(params['Np']):
1264
+
1265
+ operators['rho'] = UY*rho*UY.dag()
1266
+ coh, rho = Free(operators, params, measure,e_ops = [operators['Sz']],c_ops = [(1/np.sqrt(params['T2']))*operators['Sx']])
1267
+ operators['rho'] = UXm*rho*UXm.dag()
1268
+ coh, rho = Free(operators, params, measure,e_ops = [operators['Sz']],c_ops = [(1/np.sqrt(params['T2']))*operators['Sx']])
1269
+ operators['rho'] = UX*UY*rho*UY.dag()*UX.dag()
1270
+
1271
+
1272
+ coh, rho = Free(operators, params, measure,e_ops = [operators['Sz']],c_ops = [(1/np.sqrt(params['T2']))*operators['Sx']])
1273
+ operators['rho'] = UYp*rho*UYp.dag()
1274
+ cho, rho = Free(operators, params, measure,e_ops = [operators['Sz']],c_ops = [(1/np.sqrt(params['T2']))*operators['Sx']])
1275
+ operators['rho'] = UX*rho*UX.dag()
1276
+
1277
+ rho = operators['rho']
1278
+
1279
+
1280
+ coh = (operators['rho']*operators['Sz']).tr().real
1281
+
1282
+ return coh, rho
1283
+
1284
+ else: raise Exception('Invalid value for params["decoherence"]. Must be bool not {params["decoherence"]}.')
1285
+
1286
+
1287
+
1288
+ def Ramsey(operators,params,measure):
1289
+ """
1290
+ a function which constructs the Ramsey sequence using free evolution
1291
+ and pi/2 pulses.
1292
+ Ramsey here is (U0*UX)
1293
+
1294
+ Parameters
1295
+ ----------
1296
+
1297
+ operators: a dict of operators for the system which can be retrieved using the
1298
+ generate operators function
1299
+ params: dict of system parameters which define the protocol. Can be found
1300
+ using the params() function
1301
+ inst: bool to determine whether pulses are instantaneous. Default is False
1302
+
1303
+ returns
1304
+ -------
1305
+
1306
+ U: Qobj for the Ramsey pulse packet
1307
+ """
1308
+
1309
+ tau = params['tau'];
1310
+ profile = params['pulse_profile']
1311
+
1312
+ if params['decoherence'] == False:
1313
+
1314
+ U0 = (-1j*operators['H0']*tau).expm();
1315
+
1316
+
1317
+ #construct the pulse operators
1318
+ UX, UY = pulse_profiles[profile](operators,params,rot = np.pi/2)
1319
+
1320
+
1321
+ U = UX*U0*UX
1322
+
1323
+
1324
+ rho = U*operators['rho0']*U.dag();
1325
+
1326
+ coh = (operators['S%s'%measure]*rho).tr().real
1327
+
1328
+
1329
+ return coh,rho, U
1330
+
1331
+ elif params['decoherence'] == False:
1332
+ raise Exception('Decoherence has not been initialised with this pulse protocol.')
1333
+
1334
+ else: raise Exception('Invalid value for params["decoherence"]. Must be bool not {params["decoherence"]}.')
1335
+
1336
+
1337
+ def PMC(operators,params,measure,drive_dir = 'x'):
1338
+ """
1339
+ This is a function which performs PCM (phase modulated control), a Continuous
1340
+ driving control method which modulates the phase of a second MW to engineer
1341
+ interactions
1342
+
1343
+ Parameters
1344
+ ----------
1345
+
1346
+ operators: a dict of operators for the system which can be retrieved using the
1347
+ generate operators function
1348
+ params: dict of system parameters which define the protocol. Can be found
1349
+ using the params() function
1350
+ measure: str of which direction to measure NV
1351
+ drive_dir: str for which direction to drive NV. Default = 'x'
1352
+
1353
+ returns
1354
+ -------
1355
+
1356
+ coh: float value of measure NV coherence, in direction {measure}
1357
+ rho: Qobj of the final state of the system
1358
+ U: Qobj for the Ramsey pulse packet
1359
+ """
1360
+
1361
+ tau = params['tau'];
1362
+
1363
+ if params['decoherence'] == False:
1364
+
1365
+
1366
+ Up = (-1j*(operators['H0'] + (params['Omega_R'] + np.exp(-1j*0)*params['Omega'])*operators[f'S{drive_dir}'])*tau/2).expm()
1367
+ Um = (-1j*(operators['H0'] + (params['Omega_R'] + np.exp(-1j*np.pi)*params['Omega'])*operators[f'S{drive_dir}'])*tau).expm()
1368
+
1369
+ U = (Up*Um*Up)**params['Np']
1370
+
1371
+ rho = U*operators['rho0']*U.dag();
1372
+
1373
+ coh = (operators['S%s'%measure]*rho).tr().real
1374
+
1375
+
1376
+ return coh,rho, U
1377
+
1378
+ elif params['decoherence'] == False:
1379
+ raise Exception('Decoherence has not been initialised with this pulse protocol.')
1380
+
1381
+
1382
+
1383
+ def COSY(operators,params,measure,drive_dir = 'z'):
1384
+ """
1385
+ This is a function which performs COSY (phase modulated control), a pulsed
1386
+ correlation method which separates two Hahn echos with tc
1387
+
1388
+ Parameters
1389
+ ----------
1390
+
1391
+ operators: a dict of operators for the system which can be retrieved using the
1392
+ generate operators function
1393
+ params: dict of system parameters which define the protocol. Can be found
1394
+ using the params() function
1395
+ measure: str of which direction to measure NV
1396
+ drive_dir: str for which direction to drive NV. Default = 'x'
1397
+
1398
+ returns
1399
+ -------
1400
+
1401
+ coh: float value of measure NV coherence, in direction {measure}
1402
+ rho: Qobj of the final state of the system
1403
+ U: Qobj for the Ramsey pulse packet
1404
+ """
1405
+
1406
+ tau = params['tau'];
1407
+ tc = params['t_corr']
1408
+ profile = params['pulse_profile']
1409
+
1410
+ if params['decoherence'] == False:
1411
+
1412
+ U0 = (-1j*operators['H0']*tau).expm();
1413
+ U0c = (-1j*operators['H0']*tc).expm();
1414
+
1415
+ #construct the pulse operators
1416
+ UX, UY = pulse_profiles[profile](operators,params,rot = np.pi/2)
1417
+
1418
+ UH = UX*U0*UY*UY*U0*UY
1419
+
1420
+ U = (UH*U0c*UH)**params['Np']
1421
+
1422
+ rho = U*operators['rho0']*U.dag();
1423
+
1424
+ coh = (operators['S%s'%measure]*rho).tr().real
1425
+
1426
+
1427
+ return coh,rho, U
1428
+
1429
+ elif params['decoherence'] == False:
1430
+ raise Exception('Decoherence has not been initialised with this pulse protocol.')
1431
+
1432
+
1433
+
1434
+ """
1435
+ NV SYSTEM EVOLUTION
1436
+ """
1437
+
1438
+
1439
+ def pulse_NV(operators,params,measure = 'x'):
1440
+
1441
+ """
1442
+ a function which constructs the evolved density matrix and the coherence of the NV
1443
+ after a particular protocol
1444
+
1445
+ Parameters
1446
+ ----------
1447
+
1448
+ operators: dict of quantum objects found using the generate_operators() function
1449
+ params: dict of system and protocol parameters, found using the params() function
1450
+ inst: bool to determine whether pulses are instantaneous. default = True
1451
+
1452
+
1453
+ returns
1454
+ -------
1455
+
1456
+ rho: QObj for the final state of the system
1457
+ coh: float for the final coherence of the NV
1458
+ """
1459
+
1460
+ #Np = params['Np'];
1461
+ protocol = params['protocol']
1462
+
1463
+ protocols_dict = {'PulsePol':PulsePol,
1464
+ 'PulsePol_cam':PulsePol_cam,
1465
+ 'CPMG':CPMG,
1466
+ 'XY8':XY8,
1467
+ 'Free':Free,
1468
+ 'spin_locking':spin_locking,
1469
+ 'Ramsey': Ramsey,
1470
+ 'PMC': PMC};
1471
+
1472
+
1473
+
1474
+ #U = protocols_dict[protocol](operators,params)**Np;
1475
+
1476
+ #rho = U*operators['rho0']*U.dag();
1477
+
1478
+ #coh = (operators['S%s'%measure]*rho).tr().real
1479
+
1480
+ if params['decoherence'] == True:
1481
+ coh, rho = protocols_dict[protocol](operators,params,measure);
1482
+ elif params['decoherence'] == False:
1483
+ coh, rho, U = protocols_dict[protocol](operators,params,measure);
1484
+
1485
+ else: raise Exception('Invalid value for params["decoherence"]. Must be bool not {params["decoherence"]}.')
1486
+
1487
+
1488
+
1489
+ return coh, rho, U
1490
+
1491
+
1492
+
1493
+ def hyperpol(operators,params,pbar = True, re_ini = True,wait = False,random_seed2 = 1,measure ='z'):
1494
+
1495
+ """
1496
+ A function which an initial density matrix of rho0 and applys Np pulses of a pulse scheme protocol
1497
+ using inputed free evolution U0 and X/Y pulses UX/Y. The NV is then re-initialised and the protocol repeated.
1498
+ the list of polarisations for each nuclear spin is returned as well as the final density matrix.
1499
+
1500
+ Parameters
1501
+ ----------
1502
+ operators: dict of operators of the system found by using function generate_operators()
1503
+ params: dict of parameters set by using function params()
1504
+ inst: bool for whether pulses are instantaneous. default = False
1505
+ re_ini: bool for whether to re-initialise the NV.
1506
+
1507
+ Returns
1508
+ -------
1509
+ rho: Qobj for the final state of the system.
1510
+ Pol_array: numpy array of Pol array size N_nuc and length Reps
1511
+ """
1512
+
1513
+ #compute the number of nuclei in the system
1514
+ N_nuc = params['N_nuc'];
1515
+ Np = params['Np']
1516
+ protocol = params['protocol'];
1517
+ Reps = params['Reps']
1518
+
1519
+ mu = params['t_wait_mu'];
1520
+ sigma = params['t_wait_sig']
1521
+
1522
+
1523
+ Retain = [int(x) for x in range(1,N_nuc + 1)]
1524
+
1525
+ rho0NV = operators['rho0NV'];
1526
+
1527
+
1528
+ #compute the evolution operator for one pulse packet
1529
+ protocols_dict = {'PulsePol':PulsePol,'CPMG':CPMG,'PulsePol_cam':PulsePol_cam};
1530
+ if params['decoherence'] == False:
1531
+ coh, rho, U = protocols_dict[protocol](operators,params,measure = measure)
1532
+ else: raise Exception('This decoherence value is not supported yet.')
1533
+
1534
+ np.random.seed(int(random_seed2))
1535
+ a = -1.46*np.log(np.random.random(Reps))*1e-6
1536
+
1537
+ Pol_array = [];
1538
+ for i in range(N_nuc):
1539
+ Pol_array.append([]);
1540
+
1541
+ if pbar == True:
1542
+ #propagate the initial matrix and find the evolution of the nuclear spin evolution
1543
+ rho = operators['rho0']
1544
+ for i in tqdm(range(Reps)):
1545
+
1546
+
1547
+ rho = U*rho*U.dag();
1548
+
1549
+
1550
+ #any wait time after re-initialisation
1551
+ if wait == True:
1552
+ #params['t_wait'] = random.normal(mu,sigma);
1553
+ params['t_wait'] = 10e-6
1554
+
1555
+ if a[i] > 10e-6:
1556
+ a[i] = 10e-6
1557
+
1558
+ #a[i] = 6e-6
1559
+ t_wait = params['t_wait'];
1560
+
1561
+ h0 = params['omegaL']*operators['Iz'][0]
1562
+
1563
+ U_wait = (-1j*h0*(t_wait - a[i])).expm()
1564
+ rho = U_wait*rho*U_wait.dag()
1565
+
1566
+ #re-initiailse the NV
1567
+ rho = qutip.tensor(rho0NV,rho.ptrace(Retain))
1568
+
1569
+ U_wait = (-1j*h0*(a[i])).expm()
1570
+ rho = U_wait*rho*U_wait.dag()
1571
+
1572
+ else:
1573
+ #re-initiailse the NV
1574
+ rho = qutip.tensor(rho0NV,rho.ptrace(Retain))
1575
+
1576
+ #calculate the polarisation
1577
+ for j in range(N_nuc):
1578
+ rhoTr = rho.ptrace([j + 1])
1579
+ if rhoTr.shape[0] == 2:
1580
+ Ptemp = (2*Iz()*rhoTr).tr().real;
1581
+ Pol_array[j].append(Ptemp);
1582
+
1583
+ elif rhoTr.shape[0] == 3:
1584
+ Ptemp = (2*state_3_Sz()*rhoTr).tr().real;
1585
+ Pol_array[j].append(Ptemp);
1586
+
1587
+
1588
+
1589
+
1590
+
1591
+
1592
+
1593
+
1594
+
1595
+ elif pbar == False:
1596
+ #propagate the initial matrix and find the evolution of the nuclear spin evolution
1597
+ rho = operators['rho0']
1598
+ for i in range(Reps):
1599
+
1600
+
1601
+
1602
+ rho = U*rho*U.dag();
1603
+
1604
+
1605
+
1606
+
1607
+ #any wait time after re-initialisation
1608
+
1609
+ if wait == True:
1610
+ #params['t_wait'] = random.normal(mu,sigma);
1611
+ params['t_wait'] = 10e-6
1612
+
1613
+ if a[i] > 10e-6:
1614
+ a[i] = 10e-6
1615
+ t_wait = params['t_wait'];
1616
+
1617
+ #a[i] = 6e-6
1618
+ h0 = params['omegaL']*operators['Iz'][0]
1619
+ U_wait = (-1j*h0*(t_wait - a[i])).expm()
1620
+ rho = U_wait*rho*U_wait.dag()
1621
+
1622
+ #re-initiailse the NV
1623
+ rho = qutip.tensor(rho0NV,rho.ptrace(Retain))
1624
+
1625
+ U_wait = (-1j*h0*(a[i])).expm()
1626
+ rho = U_wait*rho*U_wait.dag()
1627
+
1628
+ else:
1629
+ #re-initiailse the NV
1630
+ rho = qutip.tensor(rho0NV,rho.ptrace(Retain))
1631
+
1632
+ #calculate the polarisation
1633
+ for j in range(N_nuc):
1634
+ rhoTr = rho.ptrace([j + 1])
1635
+ if rhoTr.shape[0] == 2:
1636
+ Ptemp = (2*Iz()*rhoTr).tr().real;
1637
+ Pol_array[j].append(Ptemp);
1638
+
1639
+ elif rhoTr.shape[0] == 3:
1640
+ Ptemp = (2*state_3_Sz()*rhoTr).tr().real;
1641
+ Pol_array[j].append(Ptemp);
1642
+
1643
+
1644
+
1645
+
1646
+
1647
+
1648
+ return rho, Pol_array
1649
+
1650
+
1651
+
1652
+ def hyperpol_avg(operators,params, pbar = True,random_seed = 1):
1653
+
1654
+
1655
+ """
1656
+ A function which an initial density matrix of rho0 and applys Np pulses of a pulse scheme protocol
1657
+ using inputed free evolution U0 and X/Y pulses UX/Y. The NV is then re-initialised and the protocol repeated.
1658
+ the list of polarisations for each nuclear spin is returned as well as the final density matrix.
1659
+
1660
+ Parameters
1661
+ ----------
1662
+ operators: dict of operators of the system found by using function generate_operators()
1663
+ params: dict of parameters set by using function params()
1664
+ num_avg: int for the number of t_waits averaged over. default = 100
1665
+ inst: bool for whether pulses are instantaneous. default = False
1666
+ pbar: bool for whether to include progress bar. default = True
1667
+
1668
+ Returns
1669
+ -------
1670
+ rho: Qobj for the final state of the system.
1671
+ Pol_array: numpy array of Pol array size N_nuc and length Reps
1672
+ """
1673
+
1674
+ num_avg = params['num_avg']
1675
+ np.random.seed(random_seed)
1676
+ seeds = np.floor(np.random.random(num_avg)*100)
1677
+
1678
+
1679
+ if pbar == True:
1680
+ Pol_array = 0
1681
+ for i in pb.progressbar(range(num_avg)):
1682
+
1683
+
1684
+ rho,Pol = hyperpol(operators,params,pbar = False, wait = True,random_seed2 = seeds[i])
1685
+ Pol_array += np.array(Pol);
1686
+
1687
+ if pbar == False:
1688
+ Pol_array = 0
1689
+ for i in range(num_avg):
1690
+
1691
+
1692
+ rho,Pol = hyperpol(operators,params,pbar = False,wait = True,random_seed2 = seeds[i])
1693
+ Pol_array += np.array(Pol);
1694
+
1695
+
1696
+ return rho,Pol_array/num_avg
1697
+
1698
+
1699
+ """
1700
+ PLOTTING FUNCTIONS
1701
+ """
1702
+
1703
+
1704
+ def plot_D_pol(P_df,fig_size = [7,4],dpi = 100,fig = None, ax = None, plot_avg = True,title = None,plot_params = plot_params(),label = None):
1705
+
1706
+ """
1707
+ A function which plots the polarisation time series of nuclear spins under repeated
1708
+ NV polarisation
1709
+
1710
+ Parameters
1711
+ ----------
1712
+
1713
+ P_df: dataframe containing all the polarisation series of the nuclear spins
1714
+ plot_avg: bool which determines whether average polarisation should be plotted
1715
+ title: str which will be displayed as a title
1716
+
1717
+
1718
+ Returns
1719
+ --------
1720
+
1721
+ fig: matplotlib object which conatains the plot for the figure
1722
+
1723
+ """
1724
+
1725
+ rc = {"font.family" : "serif",
1726
+ "mathtext.fontset" : "stix"}
1727
+ plt.rcParams.update(rc)
1728
+ plt.rcParams["font.serif"] = ["Helvectica"] + plt.rcParams["font.serif"]
1729
+
1730
+
1731
+
1732
+
1733
+
1734
+ if plot_avg == True:
1735
+ P_df['avg'] = P_df.mean(axis = 1);
1736
+
1737
+
1738
+
1739
+ if fig == None or ax == None:
1740
+
1741
+ fig = plt.figure(figsize = fig_size,dpi = dpi);
1742
+
1743
+ ax = fig.add_subplot(1,1,1);
1744
+
1745
+ mpl.rcParams['xtick.color'] = plot_params['tick_color'];
1746
+ mpl.rcParams['ytick.color'] = plot_params['tick_color'];
1747
+
1748
+ fig.set_facecolor(plot_params['background_color'])
1749
+ ax.patch.set_facecolor(plot_params['background_color'])
1750
+
1751
+ ax.tick_params(which = 'minor', length = 2)
1752
+ ax.tick_params(which = 'major', length = 4,labelsize = 16)
1753
+
1754
+ ax.grid(ls = 'dotted',lw = 0.5,color = 'gray',zorder = 1)
1755
+
1756
+
1757
+
1758
+
1759
+ ax.set_ylabel('Polarisation',
1760
+ fontsize = 16,
1761
+ color = plot_params['text_color'])
1762
+
1763
+ ax.set_xlabel(r'$T$ ($\mu$s)',
1764
+ fontsize = 16,
1765
+ color = plot_params['text_color'])
1766
+
1767
+
1768
+ ax.set_ylim(0,1)
1769
+
1770
+ if title != None:
1771
+ fig.text(0.15,0.9,title,
1772
+ fontsize = 16,fontweight = 'bold',
1773
+ color = plot_params['text_color'])
1774
+
1775
+
1776
+ for column in P_df.columns:
1777
+
1778
+
1779
+
1780
+ if column == 'avg':
1781
+ ax.plot(P_df.index,P_df[column],
1782
+ linestyle = plot_params['line_style'],
1783
+ label = label,
1784
+ alpha = plot_params['line_alpha'],
1785
+ linewidth = plot_params['line_width'],
1786
+ color = plot_params['line_color'])
1787
+
1788
+ else:
1789
+ ax.plot(P_df.index,P_df[column],
1790
+ linestyle = plot_params['line_style'],
1791
+ label = label,
1792
+ alpha = plot_params['line_alpha'],
1793
+ linewidth = plot_params['line_width'],
1794
+ color = plot_params['line_color'])
1795
+
1796
+ ax.xaxis.set_minor_locator(AutoMinorLocator())
1797
+ ax.yaxis.set_minor_locator(AutoMinorLocator())
1798
+
1799
+ ax.legend(loc = 'lower right',fontsize = 12)
1800
+
1801
+
1802
+ return fig,ax
1803
+
1804
+ def plot_D_pol_N(P_df,fig_size = [7,4],fig = None, ax = None, plot_avg = True,title = None,plot_params = plot_params(),dpi = 100):
1805
+
1806
+ """
1807
+ A function which plots the polarisation time series of nuclear spins under repeated
1808
+ NV polarisation
1809
+
1810
+ Parameters
1811
+ ----------
1812
+
1813
+ P_df: dataframe containing all the polarisation series of the nuclear spins
1814
+ plot_avg: bool which determines whether average polarisation should be plotted
1815
+ title: str which will be displayed as a title
1816
+
1817
+
1818
+ Returns
1819
+ --------
1820
+
1821
+ fig: matplotlib object which conatains the plot for the figure
1822
+
1823
+ """
1824
+
1825
+
1826
+ if plot_avg == True:
1827
+ P_df['avg'] = P_df.mean(axis = 1);
1828
+
1829
+
1830
+
1831
+ if fig == None or ax == None:
1832
+
1833
+ fig = plt.figure(figsize = fig_size,dpi = dpi);
1834
+
1835
+ ax = fig.add_subplot(1,1,1);
1836
+
1837
+ mpl.rcParams['xtick.color'] = plot_params['tick_color'];
1838
+ mpl.rcParams['ytick.color'] = plot_params['tick_color'];
1839
+
1840
+ fig.set_facecolor(plot_params['background_color'])
1841
+ ax.patch.set_facecolor(plot_params['background_color'])
1842
+
1843
+ ax.tick_params(which = 'minor', length = 2)
1844
+ ax.tick_params(which = 'major', length = 4,labelsize = 16)
1845
+
1846
+ ax.grid(ls = 'dotted',lw = 0.5,color = 'gray',zorder = 1)
1847
+
1848
+
1849
+
1850
+
1851
+ ax.set_ylabel('Polarisation',
1852
+ fontsize = 16,
1853
+ color = plot_params['text_color'])
1854
+
1855
+ ax.set_xlabel(r'$2\tau$ ($\mu$s)',
1856
+ fontsize = 16,
1857
+ color = plot_params['text_color'])
1858
+
1859
+
1860
+ ax.set_ylim(0,0.5)
1861
+
1862
+ if title != None:
1863
+ fig.text(0.15,0.9,title,
1864
+ fontsize = 16,fontweight = 'bold',
1865
+ color = plot_params['text_color'])
1866
+
1867
+
1868
+ for column in P_df.columns:
1869
+
1870
+ if column == 'avg':
1871
+ ax.plot(P_df.index,P_df[column]/2,
1872
+ linestyle = plot_params['line_style'],
1873
+ label = '%s'%column,
1874
+ linewidth = 2)
1875
+
1876
+ else:
1877
+ ax.plot(P_df.index,P_df[column]/2,
1878
+ linestyle = plot_params['line_style'],
1879
+ label = '%s'%column,
1880
+ linewidth = 2)
1881
+
1882
+ ax.legend(loc = 'lower right',fontsize = 12)
1883
+
1884
+
1885
+ return fig,ax
1886
+
1887
+ """
1888
+ FLOQUET
1889
+ """
1890
+
1891
+ def floquet_spectrum(operators,params,taus, bound = 1,tan2 = False,inst = True):
1892
+
1893
+ """
1894
+ A function which takes the free Hamiltonian of a system and a partiuclar pulse
1895
+ protocol structure and finds the Floquet phase structure over a range of tau
1896
+
1897
+ Parameters
1898
+ ----------
1899
+ operators: dict of operators for system constructed using generate_operators()
1900
+ function
1901
+ params: dict of parameters for protocol and system found by using function
1902
+ params()
1903
+ taus: array of float points which describe the range of tau for floquet spectrum
1904
+
1905
+
1906
+ Returns
1907
+ -------
1908
+ rho: Qobj for the final state of the system.
1909
+ Pol_array: numpy array of Pol array size N_nuc and length Reps
1910
+ """
1911
+ protocols_dict = {'PulsePol':PulsePol,'CPMG':CPMG};
1912
+
1913
+ #retrieve protocol parameters
1914
+ N_nuc = params['N_nuc'];
1915
+ protocol = params['protocol']
1916
+
1917
+
1918
+ ''' find the unperturbed starting floquet states '''
1919
+ params['tau'] = 1e-9;
1920
+
1921
+ U_ini = protocols_dict[protocol](operators,params,inst);
1922
+ lbda, vecOld = U_ini.eigenstates(sort = 'low');
1923
+
1924
+
1925
+
1926
+
1927
+ ''' initialise arrays '''
1928
+ E = []
1929
+ Vec = []
1930
+ wind = [];
1931
+ for i in range(2**(N_nuc + 1)):
1932
+
1933
+ E.append([]);
1934
+ Vec.append([])
1935
+ wind.append(0);
1936
+
1937
+ ''' run through tau array and find all floquet phases and states '''
1938
+ for j in pb.progressbar(range(len(taus))):
1939
+
1940
+ states = []
1941
+ params['tau'] = taus[j]
1942
+
1943
+
1944
+ U = protocols_dict[protocol](operators,params,inst);
1945
+
1946
+ lbda,vectemp = U.eigenstates(sort = 'low');
1947
+
1948
+ if tan2 == True:
1949
+ #convert into floquet phases
1950
+ etemp = np.arctan2(lbda.imag,lbda.real)
1951
+ f = 1/2
1952
+ elif tan2 == False:
1953
+ #convert into floquet phases
1954
+ etemp = np.arctan(lbda.imag/lbda.real)
1955
+ f = 1
1956
+
1957
+ etemp = np.array(etemp)
1958
+
1959
+
1960
+ idx = np.argsort(etemp);
1961
+ etemp = etemp[idx]
1962
+ vectemp = vectemp[idx]
1963
+ #assign the correct floquet state to the correct array to prevent discontinuities
1964
+ for i in range(len(etemp)):
1965
+
1966
+ overlapMax = 0
1967
+
1968
+ for k in range(len(vecOld)):
1969
+ overlap = abs((vectemp[i].overlap(vecOld[k])))**2
1970
+
1971
+ if overlap > overlapMax and k not in states:
1972
+ state = k;
1973
+ overlapMax = overlap;
1974
+
1975
+ states.append(state)
1976
+
1977
+
1978
+ #assign winding number for states to remove discontinuities due to oscillatory wrapping
1979
+ if j > 0:
1980
+ if E[state][-1] - etemp[i] - 2*f*np.pi*wind[state] > np.pi/2*f:
1981
+
1982
+ wind[state] +=1;
1983
+
1984
+ elif E[state][-1] - etemp[i] - 2*f*np.pi*wind[state] < -np.pi/2*f:
1985
+
1986
+ wind[state] -=1;
1987
+
1988
+ E[state].append(etemp[i] + 2*f*np.pi*wind[state]);
1989
+ Vec[state].append(vectemp[i]);
1990
+
1991
+ #store the new floquet states as the old floquet states for the next loop
1992
+ vecNew = [];
1993
+ for n in range(len(states)):
1994
+ vecNew.append([])
1995
+
1996
+ for n in range(len(states)):
1997
+ vecNew[states[n]] = vectemp[n]
1998
+ vecOld = vecNew;
1999
+
2000
+ return E,Vec
2001
+
2002
+
2003
+
2004
+
2005
+ """
2006
+ NV GENERATOR
2007
+ """
2008
+
2009
+
2010
+ def nv_generator(params = params(),lat = [-10,10],p = 0.011,species = 'C',random_state = None):
2011
+ """
2012
+ nv_generator is a function which takes in the size of a lattice and generates
2013
+ a random bath of species C.
2014
+
2015
+ Parameters:
2016
+ params: a dict of parameters commonly used for NV centres. This
2017
+ function will use the lattice vectors
2018
+ lat: array of integers for start N_s and end N_f of the number of lattice
2019
+ repetitions. Default = [-10,10]
2020
+ p: the probability that the lattice site is the chosen species.
2021
+ species: The label for the species of impurity. Choice between H or C
2022
+
2023
+ returns:
2024
+ df: dataframe of Nv hyperfine couplings, positions and species label
2025
+ """
2026
+
2027
+ R = params['vecs']
2028
+ sites = []
2029
+
2030
+ np.random.seed(random_state)
2031
+
2032
+ # for loop for x direction
2033
+ for n in range(lat[0],lat[1] + 1):
2034
+ #for loop for y direction
2035
+ for l in range(lat[0],lat[1] + 1):
2036
+ #for loop for z direction
2037
+ for m in range(lat[0],lat[1] + 1):
2038
+ #loop over vectors
2039
+ for vec in range(len(R)):
2040
+
2041
+ r = random.random()
2042
+ new = np.add(R[vec], [n,l,m])
2043
+
2044
+ if new[0] == 0.25 and new[1] == 0.25 and new[2] == 0.25:
2045
+
2046
+ Val = new;
2047
+
2048
+
2049
+ elif new[0] == 0 and new[1] == 0 and new[2] == 0:
2050
+
2051
+ N14 = new;
2052
+
2053
+
2054
+ elif r <= p:
2055
+ temp = np.subtract(new,[0.25,0.25,0.25]);
2056
+
2057
+ #change basis
2058
+ temp = change_basis(temp);
2059
+
2060
+
2061
+ if species == 'p1':
2062
+ #find hyperfine coupling using previously defined function
2063
+ s = '14N' if np.random.random() < 1 else 'N'
2064
+ A = hyperfine(temp*params['lattice_constant']*1e10,params=params,species = s)
2065
+ Apar = A[2];
2066
+ Aper = np.linalg.norm([A[0],A[1]]);
2067
+ sites.append([s,A[0],A[1],A[2],Apar,Aper,(temp*params['lattice_constant']*1e10).round(2)])
2068
+ #find hyperfine coupling using previously defined function
2069
+
2070
+ A = hyperfine(temp*params['lattice_constant']*1e10,params=params,species = 'e')
2071
+ Apar = A[2];
2072
+ Aper = np.linalg.norm([A[0],A[1]]);
2073
+ sites.append(['p1',A[0],A[1],A[2],Apar,Aper,(temp*params['lattice_constant']*1e10).round(2)])
2074
+ else:
2075
+ #find hyperfine coupling using previously defined function
2076
+ A = hyperfine(temp*params['lattice_constant']*1e10,params=params,species = species)
2077
+ Apar = A[2];
2078
+ Aper = np.linalg.norm([A[0],A[1]]);
2079
+
2080
+
2081
+
2082
+ sites.append([species,A[0],A[1],A[2],Apar,Aper,(temp*params['lattice_constant']*1e10).round(2)])
2083
+
2084
+ df = pd.DataFrame(sites,columns = ['Species','Ax','Ay','Az','Apar','Aper','pos'])
2085
+
2086
+
2087
+
2088
+
2089
+ return df
2090
+
2091
+