qdmag 1.3.0__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.
Files changed (42) hide show
  1. qdmag/__init__.py +7 -0
  2. qdmag/core/Operators.py +491 -0
  3. qdmag/core/StevensOperators.py +481 -0
  4. qdmag/core/__init__.py +0 -0
  5. qdmag/core/analysis.py +73 -0
  6. qdmag/core/backend.py +76 -0
  7. qdmag/core/classical_rate.py +337 -0
  8. qdmag/core/common.py +1061 -0
  9. qdmag/core/constants.py +8 -0
  10. qdmag/core/effective_basis.py +381 -0
  11. qdmag/core/expm.py +128 -0
  12. qdmag/core/hdf5.py +109 -0
  13. qdmag/core/liouville.py +1618 -0
  14. qdmag/core/powder_dynamics.py +235 -0
  15. qdmag/core/pulse.py +101 -0
  16. qdmag/core/quantum_master.py +153 -0
  17. qdmag/tools/__init__.py +0 -0
  18. qdmag/tools/tool_RK4.py +31 -0
  19. qdmag/tools/tool_basis.py +83 -0
  20. qdmag/tools/tool_benchmark.py +259 -0
  21. qdmag/tools/tool_commutation.py +49 -0
  22. qdmag/tools/tool_conditions_rho.py +33 -0
  23. qdmag/tools/tool_divergence.py +33 -0
  24. qdmag/tools/tool_eigen.py +30 -0
  25. qdmag/tools/tool_hdf5.py +18 -0
  26. qdmag/tools/tool_magnetization.py +35 -0
  27. qdmag/tools/tool_occupation.py +29 -0
  28. qdmag/tools/tool_powder.py +522 -0
  29. qdmag/tools/tool_powder_dynamics.py +51 -0
  30. qdmag/tools/tool_projection.py +23 -0
  31. qdmag/tools/tool_quadrature.py +192 -0
  32. qdmag/tools/tool_rate.py +65 -0
  33. qdmag/tools/tool_spins.py +28 -0
  34. qdmag/tools/tool_staircase.py +31 -0
  35. qdmag/tools/tool_time_step.py +382 -0
  36. qdmag/tools/tool_transform_rho.py +47 -0
  37. qdmag/tools/tool_zeeman.py +35 -0
  38. qdmag-1.3.0.dist-info/METADATA +136 -0
  39. qdmag-1.3.0.dist-info/RECORD +42 -0
  40. qdmag-1.3.0.dist-info/WHEEL +5 -0
  41. qdmag-1.3.0.dist-info/licenses/LICENSE +674 -0
  42. qdmag-1.3.0.dist-info/top_level.txt +1 -0
qdmag/__init__.py ADDED
@@ -0,0 +1,7 @@
1
+ import os
2
+
3
+ __version__ = '1.3.0'
4
+
5
+ # Find the path to this __init__.py file
6
+ root_dir = os.path.dirname(os.path.abspath(__file__))
7
+
@@ -0,0 +1,491 @@
1
+ # The source code is modified from the original code in the PyCrystalField package by Allen Scheie.
2
+ # Here is the link to the original code: https://github.com/asche1/PyCrystalField/tree/master
3
+ # The original code is licensed under the GPL-3.0 license
4
+
5
+ import numpy as np
6
+
7
+ class Ket():
8
+ def __init__(self, array):
9
+ """give an array which defines the angular momentum eigenket in terms
10
+ of the available states. IE, write |1> + |2> as [0,0,0,1,1]"""
11
+ self.ket = np.array(array)
12
+ self.j = len(array)/2.0 - 0.5
13
+ self.m = np.arange(-self.j,self.j+1,1)
14
+
15
+ def Jz(self):
16
+ return Ket( self.ket * self.m )
17
+
18
+ def Jplus(self):
19
+ newvals = np.sqrt((self.j-self.m)*(self.j+self.m+1)) * self.ket
20
+ return Ket( np.roll(newvals,1) )
21
+
22
+ def Jminus(self):
23
+ newvals = np.sqrt((self.j+self.m)*(self.j-self.m+1)) * self.ket
24
+ return Ket( np.roll(newvals,-1) )
25
+
26
+ def Jx(self):
27
+ return Ket(0.5*(self.Jplus().ket + self.Jminus().ket) )
28
+
29
+ def Jy(self):
30
+ return Ket(-1j*0.5*(self.Jplus().ket - self.Jminus().ket) )
31
+
32
+ def R(self, alpha, beta, gamma): # Rotation about general Euler Angles
33
+ return self._Rz(alpha)._Ry(beta)._Rz(gamma)
34
+
35
+ def _Rz(self,theta): # Rotation about z axis
36
+ newvals = np.zeros(len(self.ket), dtype=complex)
37
+ for i in range(len(self.ket)):
38
+ newvals[i] = self.ket[i]* np.exp(-1j*self.m[i] * theta)
39
+ return Ket(newvals)
40
+
41
+ def _Ry(self,beta): # Rotation about y axis
42
+ newvals = np.zeros(len(self.ket), dtype=complex)
43
+ for i in range(len(self.ket)):
44
+ mm = self.m[i]
45
+ for j in range(len(self.ket)):
46
+ mmp = self.m[j]
47
+ newvals[j] += self.ket[i]* self._WignersFormula(mm,mmp,beta)
48
+ return Ket(newvals)
49
+
50
+ def _WignersFormula(self,m,mp,beta):
51
+ """See Sakurai/Napolitano eq. 3.9.33.
52
+ This function was cross-checked with Mathematica's WignerD function."""
53
+
54
+ # determine the limit of the sum over k
55
+ kmin = np.maximum(0, m-mp)
56
+ kmax = np.minimum(self.j+m, self.j-mp)
57
+
58
+ d = 0
59
+ for k in np.arange(kmin,kmax+1):
60
+ d += (-1)**(k-m+mp) * np.sqrt(np.math.factorial(self.j+m) * np.math.factorial(self.j-m) *\
61
+ np.math.factorial(self.j+mp) * np.math.factorial(self.j-mp))/\
62
+ (np.math.factorial(self.j+m-k) * np.math.factorial(k) * np.math.factorial(self.j-k-mp)*\
63
+ np.math.factorial(k-m+mp))*\
64
+ np.cos(beta/2)**(2*self.j -2*k+m-mp) * np.sin(beta/2)**(2*k-m+mp)
65
+ return d
66
+
67
+
68
+ def __mul__(self,other):
69
+ if isinstance(other, Ket):
70
+ # Compute inner product
71
+ return np.dot(np.conjugate(self.ket), other.ket)
72
+ else:
73
+ return Ket( np.dot(self.ket, other))
74
+
75
+ def __add__(self,other):
76
+ if isinstance(other, Ket):
77
+ return Ket(self.ket + other.ket)
78
+ else:
79
+ print("other is not a ket")
80
+
81
+ # def __rmul__(self,other): #Doesn't work. not sure why.
82
+ # if isinstance(other, Ket):
83
+ # return np.vdot(other.ket, self.ket)
84
+ # else: # if not ket, try matrix multiplication.
85
+ # #print "reverse multiply"
86
+ # return np.dot(other, self.ket)
87
+
88
+
89
+
90
+ # spec = [
91
+ # ('O', float32[:,:]), # a simple scalar field
92
+ # #('j', float32),
93
+ # ('m', float32[:]), # an array field
94
+ # ]
95
+
96
+ # @jitclass(spec) # Doesn't work
97
+ class Operator():
98
+ def __init__(self, J):
99
+ self.O = np.zeros((int(2*J+1), int(2*J+1)))
100
+ self.m = np.arange(-J,J+1,1)
101
+ self.j = J
102
+
103
+ @staticmethod
104
+ def Jz(J):
105
+ obj = Operator(J)
106
+ for i in range(len(obj.O)):
107
+ for k in range(len(obj.O)):
108
+ if i == k:
109
+ obj.O[i,k] = (obj.m[k])
110
+ return obj
111
+
112
+ @staticmethod
113
+ def Jplus(J):
114
+ obj = Operator(J)
115
+ for i in range(len(obj.O)):
116
+ for k in range(len(obj.O)):
117
+ if k+1 == i:
118
+ obj.O[i,k] = np.sqrt((obj.j-obj.m[k])*(obj.j+obj.m[k]+1))
119
+ return obj
120
+
121
+ @staticmethod
122
+ def Jminus(J):
123
+ obj = Operator(J)
124
+ for i in range(len(obj.O)):
125
+ for k in range(len(obj.O)):
126
+ if k-1 == i:
127
+ obj.O[i,k] = np.sqrt((obj.j+obj.m[k])*(obj.j-obj.m[k]+1))
128
+ return obj
129
+
130
+ @staticmethod
131
+ def Jx(J):
132
+ objp = Operator.Jplus(J)
133
+ objm = Operator.Jminus(J)
134
+ return 0.5*objp + 0.5*objm
135
+
136
+ @staticmethod
137
+ def Jy(J):
138
+ objp = Operator.Jplus(J)
139
+ objm = Operator.Jminus(J)
140
+ return -0.5j*objp + 0.5j*objm
141
+
142
+ def __add__(self,other):
143
+ newobj = Operator(self.j)
144
+ if isinstance(other, Operator):
145
+ newobj.O = self.O + other.O
146
+ else:
147
+ newobj.O = self.O + other*np.identity(int(2*self.j+1))
148
+ return newobj
149
+
150
+ def __radd__(self,other):
151
+ newobj = Operator(self.j)
152
+ if isinstance(other, Operator):
153
+ newobj.O = self.O + other.O
154
+ else:
155
+ newobj.O = self.O + other*np.identity(int(2*self.j+1))
156
+ return newobj
157
+
158
+ def __sub__(self,other):
159
+ newobj = Operator(self.j)
160
+ if isinstance(other, Operator):
161
+ newobj.O = self.O - other.O
162
+ else:
163
+ newobj.O = self.O - other*np.identity(int(2*self.j+1))
164
+ return newobj
165
+
166
+ def __mul__(self,other):
167
+ newobj = Operator(self.j)
168
+ if (isinstance(other, int) or isinstance(other, float) or isinstance(other, complex)):
169
+ newobj.O = other * self.O
170
+ else:
171
+ newobj.O = np.dot(self.O, other.O)
172
+ return newobj
173
+
174
+ def __rmul__(self,other):
175
+ newobj = Operator(self.j)
176
+ if (isinstance(other, int) or isinstance(other, float) or isinstance(other, complex)):
177
+ newobj.O = other * self.O
178
+ else:
179
+ newobj.O = np.dot(other.O, self.O)
180
+ return newobj
181
+
182
+ def __pow__(self, power):
183
+ newobj = Operator(self.j)
184
+ newobj.O = self.O
185
+ for i in range(power-1):
186
+ newobj.O = np.dot(newobj.O,self.O)
187
+ return newobj
188
+
189
+ def __neg__(self):
190
+ newobj = Operator(self.j)
191
+ newobj.O = -self.O
192
+ return newobj
193
+
194
+ def __repr__(self):
195
+ return repr(self.O)
196
+
197
+
198
+
199
+
200
+
201
+
202
+
203
+ ####################################3
204
+ ##
205
+ ## ## #######
206
+ ## ## #############
207
+ ## ## ### ###
208
+ ## ## ###
209
+ ## ## ####
210
+ ## ## #####
211
+ ## ## #####
212
+ ## ## ####
213
+ ## ## ###
214
+ ## ## ### ###
215
+ ## ########### #### ####
216
+ ## ########### #######
217
+ ##
218
+ ######################################
219
+
220
+
221
+
222
+
223
+
224
+
225
+ class LSOperator():
226
+ '''This is for a full treatment in the intermediate coupling scheme'''
227
+ def __init__(self, L, S):
228
+ self.O = np.zeros((int((2*L+1)*(2*S+1)), int((2*L+1)*(2*S+1)) ))
229
+ self.L = L
230
+ self.S = S
231
+ lm = np.arange(-L,L+1,1)
232
+ sm = np.arange(-S,S+1,1)
233
+ self.Lm = np.repeat(lm, len(sm))
234
+ self.Sm = np.tile(sm, len(lm))
235
+
236
+ @staticmethod
237
+ def Lz(L, S):
238
+ obj = LSOperator(L, S)
239
+ for i in range(len(obj.O)):
240
+ for k in range(len(obj.O)):
241
+ if i == k:
242
+ obj.O[i,k] = (obj.Lm[k])
243
+ return obj
244
+
245
+ @staticmethod
246
+ def Lplus(L, S):
247
+ obj = LSOperator(L, S)
248
+ for i, lm1 in enumerate(obj.Lm):
249
+ for k, lm2 in enumerate(obj.Lm):
250
+ if (lm1 - lm2 == 1) and (obj.Sm[i] == obj.Sm[k] ):
251
+ obj.O[i,k] = np.sqrt((obj.L-obj.Lm[k])*(obj.L+obj.Lm[k]+1))
252
+ return obj
253
+
254
+ @staticmethod
255
+ def Lminus(L, S):
256
+ obj = LSOperator(L, S)
257
+ for i, lm1 in enumerate(obj.Lm):
258
+ for k, lm2 in enumerate(obj.Lm):
259
+ if (lm2 - lm1 == 1) and (obj.Sm[i] == obj.Sm[k]):
260
+ obj.O[i,k] = np.sqrt((obj.L+obj.Lm[k])*(obj.L-obj.Lm[k]+1))
261
+ return obj
262
+
263
+ @staticmethod
264
+ def Lx(L, S):
265
+ objp = LSOperator.Lplus(L, S)
266
+ objm = LSOperator.Lminus(L, S)
267
+ return 0.5*objp + 0.5*objm
268
+
269
+ @staticmethod
270
+ def Ly(L, S):
271
+ objp = LSOperator.Lplus(L, S)
272
+ objm = LSOperator.Lminus(L, S)
273
+ return -0.5j*objp + 0.5j*objm
274
+
275
+ ##################################
276
+ # Spin operators
277
+ @staticmethod
278
+ def Sz(L, S):
279
+ obj = LSOperator(L, S)
280
+ for i in range(len(obj.O)):
281
+ for k in range(len(obj.O)):
282
+ if i == k:
283
+ obj.O[i,k] = (obj.Sm[k])
284
+ return obj
285
+
286
+ @staticmethod
287
+ def Splus(L, S):
288
+ obj = LSOperator(L, S)
289
+ for i, sm1 in enumerate(obj.Sm):
290
+ for k, sm2 in enumerate(obj.Sm):
291
+ if (sm1 - sm2 == 1) and (obj.Lm[i] == obj.Lm[k]):
292
+ obj.O[i,k] = np.sqrt((obj.S-obj.Sm[k])*(obj.S+obj.Sm[k]+1))
293
+ return obj
294
+
295
+ @staticmethod
296
+ def Sminus(L, S):
297
+ obj = LSOperator(L, S)
298
+ for i, sm1 in enumerate(obj.Sm):
299
+ for k, sm2 in enumerate(obj.Sm):
300
+ if (sm2 - sm1 == 1) and (obj.Lm[i] == obj.Lm[k]):
301
+ obj.O[i,k] = np.sqrt((obj.S+obj.Sm[k])*(obj.S-obj.Sm[k]+1))
302
+ return obj
303
+
304
+ @staticmethod
305
+ def Sx(L, S):
306
+ objp = LSOperator.Splus(L, S)
307
+ objm = LSOperator.Sminus(L, S)
308
+ return 0.5*objp + 0.5*objm
309
+
310
+ @staticmethod
311
+ def Sy(L, S):
312
+ objp = LSOperator.Splus(L, S)
313
+ objm = LSOperator.Sminus(L, S)
314
+ return -0.5j*objp + 0.5j*objm
315
+
316
+
317
+ def __add__(self,other):
318
+ newobj = LSOperator(self.L, self.S)
319
+ try:
320
+ newobj.O = np.add(self.O, other.O)
321
+ except AttributeError:
322
+ newobj.O = self.O + other*np.identity(len(self.O))
323
+ return newobj
324
+
325
+ def __radd__(self,other):
326
+ newobj = LSOperator(self.L, self.S)
327
+ try:
328
+ newobj.O = np.add(other.O, self.O)
329
+ except AttributeError:
330
+ newobj.O = self.O + other*np.identity(len(self.O))
331
+ return newobj
332
+
333
+ def __sub__(self,other):
334
+ newobj = LSOperator(self.L, self.S)
335
+ try:
336
+ newobj.O = self.O - other.O
337
+ except AttributeError:
338
+ newobj.O = self.O - other*np.identity(len(self.O))
339
+ return newobj
340
+
341
+ def __mul__(self,other):
342
+ newobj = LSOperator(self.L, self.S)
343
+ try:
344
+ newobj.O = np.dot(self.O, other.O)
345
+ except AttributeError:
346
+ newobj.O = other * self.O
347
+ return newobj
348
+
349
+ def __rmul__(self,other):
350
+ newobj = LSOperator(self.L, self.S)
351
+ try:
352
+ newobj.O = np.dot(other.O, self.O)
353
+ except AttributeError:
354
+ newobj.O = other * self.O
355
+ return newobj
356
+
357
+ def __pow__(self, power):
358
+ newobj = LSOperator(self.L, self.S)
359
+ newobj.O = self.O
360
+ for i in range(power-1):
361
+ newobj.O = np.dot(newobj.O,self.O)
362
+ return newobj
363
+
364
+ def __neg__(self):
365
+ newobj = LSOperator(self.L, self.S)
366
+ newobj.O = -self.O
367
+ return newobj
368
+
369
+ def __repr__(self):
370
+ return repr(self.O)
371
+
372
+
373
+ # Computing magnetization and susceptibility
374
+
375
+ def magnetization(self, Temp, Field):
376
+ '''field should be a 3-component vector. Temps may be an array.'''
377
+ if len(Field) != 3:
378
+ raise TypeError("Field needs to be 3-component vector")
379
+
380
+ # A) Define magnetic Hamiltonian
381
+ Lx = LSOperator.Lx(self.L, self.S)
382
+ Ly = LSOperator.Ly(self.L, self.S)
383
+ Lz = LSOperator.Lz(self.L, self.S)
384
+ Sx = LSOperator.Sx(self.L, self.S)
385
+ Sy = LSOperator.Sy(self.L, self.S)
386
+ Sz = LSOperator.Sz(self.L, self.S)
387
+
388
+ g0 = 2.002319
389
+ Jx = Lx + g0*Sx
390
+ Jy = Ly + g0*Sy
391
+ Jz = Lz + g0*Sz
392
+
393
+ muB = 5.7883818012e-2 # meV/T
394
+ #mu0 = np.pi*4e-7 # T*m/A
395
+ JdotB = muB*((Field[0]*Lx + Field[1]*Ly + Field[2]*Lz) +\
396
+ (Field[0]*Sx + Field[1]*Sy + Field[2]*Sz))
397
+
398
+ # B) Diagonalize full Hamiltonian
399
+ FieldHam = self.O + JdotB.O
400
+ diagonalH = LA.eigh(FieldHam)
401
+
402
+ minE = np.amin(diagonalH[0])
403
+ evals = diagonalH[0] - minE
404
+ evecs = diagonalH[1].T
405
+ # These ARE actual eigenvalues.
406
+
407
+ # C) Compute expectation value along field
408
+ JexpVals = np.zeros((len(evals),3))
409
+ for i, ev in enumerate(evecs):
410
+ #print np.real(np.dot(ev, np.dot( self.O ,ev))), diagonalH[0][i]
411
+ #print np.real(np.dot( FieldHam ,ev)), np.real(diagonalH[0][i]*ev)
412
+ JexpVals[i] =[np.real(np.dot(np.conjugate(ev), np.dot( Jx.O ,ev))),
413
+ np.real(np.dot(np.conjugate(ev), np.dot( Jy.O ,ev))),
414
+ np.real(np.dot(np.conjugate(ev), np.dot( Jz.O ,ev)))]
415
+ k_B = 8.6173303e-2 # meV/K
416
+
417
+ if (isinstance(Temp, int) or isinstance(Temp, float)):
418
+ Zz = np.sum(np.exp(-evals/(k_B*Temp)))
419
+ JexpVal = np.dot(np.exp(-evals/(k_B*Temp)),JexpVals)/Zz
420
+ return np.real(JexpVal)
421
+ else:
422
+ expvals, temps = np.meshgrid(evals, Temp)
423
+ ZZ = np.sum(np.exp(-expvals/temps/k_B), axis=1)
424
+ JexpValList = np.repeat(JexpVals.reshape((1,)+JexpVals.shape), len(Temp), axis=0)
425
+ JexpValList = np.sum(np.exp(-expvals/temps/k_B)*\
426
+ np.transpose(JexpValList, axes=[2,0,1]), axis=2) / ZZ
427
+ # if np.isnan(JexpValList).any():
428
+ # print -expvals[0]/temps[0]/k_B
429
+ # print np.exp(-expvals/temps/k_B)[0]
430
+ # raise ValueError('Nan in result!')
431
+ return np.nan_to_num(JexpValList.T)
432
+
433
+
434
+ def susceptibility(self, Temps, Field, deltaField):
435
+ '''Computes susceptibility numerically with a numerical derivative.
436
+ deltaField needs to be a scalar value.'''
437
+ if not isinstance(deltaField, float):
438
+ raise TypeError("Deltafield needs to be a scalar")
439
+
440
+ if isinstance(Field, float):
441
+ # Assume we are computing a powder average
442
+ VecField = Field * np.array([1,0,0])
443
+ Delta = deltaField*np.array(VecField)/Field
444
+ Mplus1 = self.magnetization(Temps, VecField + Delta)
445
+ Mminus1= self.magnetization(Temps, VecField - Delta)
446
+ Mplus2 = self.magnetization(Temps, VecField + 2*Delta)
447
+ Mminus2= self.magnetization(Temps, VecField - 2*Delta)
448
+
449
+ dMdH_x = (8*(Mplus1 - Mminus1) - (Mplus2 - Mminus2))/(12*deltaField)
450
+
451
+ VecField = Field * np.array([0,1,0])
452
+ Delta = deltaField*np.array(VecField)/Field
453
+ Mplus1 = self.magnetization(Temps, VecField + Delta)
454
+ Mminus1= self.magnetization(Temps, VecField - Delta)
455
+ Mplus2 = self.magnetization(Temps, VecField + 2*Delta)
456
+ Mminus2= self.magnetization(Temps, VecField - 2*Delta)
457
+
458
+ dMdH_y = (8*(Mplus1 - Mminus1) - (Mplus2 - Mminus2))/(12*deltaField)
459
+
460
+ VecField = Field * np.array([0,0,1])
461
+ Delta = deltaField*np.array(VecField)/Field
462
+ Mplus1 = self.magnetization(Temps, VecField + Delta)
463
+ Mminus1= self.magnetization(Temps, VecField - Delta)
464
+ Mplus2 = self.magnetization(Temps, VecField + 2*Delta)
465
+ Mminus2= self.magnetization(Temps, VecField - 2*Delta)
466
+
467
+ dMdH_z = (8*(Mplus1 - Mminus1) - (Mplus2 - Mminus2))/(12*deltaField)
468
+
469
+ return (dMdH_x[:,0]+dMdH_y[:,1]+dMdH_z[:,2])/3.
470
+
471
+ elif len(Field) == 3:
472
+ Delta = deltaField*np.array(Field)/np.linalg.norm(Field)
473
+ Mplus1 = self.magnetization(Temps, Field + Delta)
474
+ Mminus1= self.magnetization(Temps, Field - Delta)
475
+ Mplus2 = self.magnetization(Temps, Field + 2*Delta)
476
+ Mminus2= self.magnetization(Temps, Field - 2*Delta)
477
+
478
+ dMdH = (8*(Mplus1 - Mminus1) - (Mplus2 - Mminus2))/(12*deltaField)
479
+ #dMdH = (Mplus1 - Mminus1)/(2*deltaField)
480
+
481
+ return dMdH
482
+
483
+
484
+
485
+
486
+
487
+
488
+
489
+
490
+
491
+