CFcal 0.1.6__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.
- cfcal/__init__.py +1 -0
- cfcal/_version.py +24 -0
- cfcal/cfcal.py +695 -0
- cfcal/plugins/cfcal/__init__.py +6 -0
- cfcal/plugins/cfcal/define_model.py +123 -0
- cfcal-0.1.6.dist-info/METADATA +105 -0
- cfcal-0.1.6.dist-info/RECORD +11 -0
- cfcal-0.1.6.dist-info/WHEEL +5 -0
- cfcal-0.1.6.dist-info/entry_points.txt +2 -0
- cfcal-0.1.6.dist-info/licenses/COPYING +48 -0
- cfcal-0.1.6.dist-info/top_level.txt +1 -0
cfcal/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from ._version import version as __version__
|
cfcal/_version.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# file generated by vcs-versioning
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"__version__",
|
|
7
|
+
"__version_tuple__",
|
|
8
|
+
"version",
|
|
9
|
+
"version_tuple",
|
|
10
|
+
"__commit_id__",
|
|
11
|
+
"commit_id",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
version: str
|
|
15
|
+
__version__: str
|
|
16
|
+
__version_tuple__: tuple[int | str, ...]
|
|
17
|
+
version_tuple: tuple[int | str, ...]
|
|
18
|
+
commit_id: str | None
|
|
19
|
+
__commit_id__: str | None
|
|
20
|
+
|
|
21
|
+
__version__ = version = '0.1.6'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 1, 6)
|
|
23
|
+
|
|
24
|
+
__commit_id__ = commit_id = None
|
cfcal/cfcal.py
ADDED
|
@@ -0,0 +1,695 @@
|
|
|
1
|
+
# CFcal - Crystal Field Module
|
|
2
|
+
#
|
|
3
|
+
# Copyright (C) 2008-2026 R. Osborn, E. A. Goremychkin
|
|
4
|
+
#
|
|
5
|
+
# This program is free software; you can redistribute it and/or modify
|
|
6
|
+
# it under the terms of the GNU Lesser General Public License as published by
|
|
7
|
+
# the Free Software Foundation; either version 2 of the License, or
|
|
8
|
+
# (at your option) any later version.
|
|
9
|
+
#
|
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
# GNU Lesser General Public License for more details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU Lesser General Public License
|
|
16
|
+
# along with this program; if not, write to the Free Software
|
|
17
|
+
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
18
|
+
#
|
|
19
|
+
"""
|
|
20
|
+
CFcal: Python calculator for crystal fields
|
|
21
|
+
|
|
22
|
+
Crystal Field calculations using the Stevens Operator formalism.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
import os
|
|
26
|
+
from configparser import ConfigParser
|
|
27
|
+
|
|
28
|
+
import numpy as np
|
|
29
|
+
from scipy.linalg import eigh
|
|
30
|
+
|
|
31
|
+
REs = ['Ce', 'Pr', 'Nd', 'Pm', 'Sm', 'Eu', 'Gd', 'Tb', 'Dy', 'Ho', 'Er', 'Tm',
|
|
32
|
+
'Yb']
|
|
33
|
+
Jf = [2.5, 4.0, 4.5, 4.0, 2.5, 0.0, 3.5, 6.0, 7.5, 8.0, 7.5, 6.0, 3.5]
|
|
34
|
+
gJ = [6.0/7.0, 4.0/5.0, 8.0/11.0, 3.0/5.0, 2.0/7.0, 0.0, 2.0, 3.0/2.0, 4.0/3.0,
|
|
35
|
+
5.0/4.0, 6.0/5.0, 7.0/6.0, 8.0/7.0]
|
|
36
|
+
#<r^2> radial integral for 3+ f-electrons
|
|
37
|
+
r2 = [0.3666, 0.3380, 0.3120, 0.2917, 0.2728, 0.2569, 0.2428, 0.2302, 0.2188,
|
|
38
|
+
0.2085, 0.1991, 0.1905, 0.1826]
|
|
39
|
+
#<r^4> radial integral for 3+ f-electrons
|
|
40
|
+
r4 = [0.3108, 0.2670, 0.2015, 0.1488, 0.1772, 0.1584, 0.1427, 0.1295, 0.1180,
|
|
41
|
+
0.1081, 0.0996, 0.0921, 0.0854]
|
|
42
|
+
#<r^6> radial integral for 3+ f-electrons
|
|
43
|
+
r6 = [0.5119, 0.4150, 0.3300, 0.2787, 0.2317, 0.1985, 0.1720, 0.1505, 0.1328,
|
|
44
|
+
0.1810, 0.1058, 0.0953, 0.0863]
|
|
45
|
+
#Second-degree Stevens factors for 3+ f-electrons
|
|
46
|
+
alphaJ = [-5.7143e-02, -2.1010e-02, -6.4279e-03, 7.7135e-03, 4.1270e-02, 0.0,
|
|
47
|
+
0.0, -1.0101e-02, -6.3492e-03, -2.2222e-03, 2.5397e-03, 1.0101e-02,
|
|
48
|
+
3.1746e-02]
|
|
49
|
+
#Fourth-degree Stevens factors for 3+ f-electrons
|
|
50
|
+
betaJ = [6.3492e-03, -7.3462e-04, -2.9111e-04, 4.0755e-04, 2.5012e-03, 0.0, 0.0,
|
|
51
|
+
1.2244e-04, -5.9200e-05, -3.3300e-05, 4.4400e-05, 1.6325e-04,
|
|
52
|
+
-1.7316e-03]
|
|
53
|
+
#Sixth-degree Stevens factors for 3+ f-electrons
|
|
54
|
+
gammaJ = [0.0, 6.0994e-05, -3.7988e-05, 6.6859e-04, 0.0, 0.0, 0.0, -1.1212e-06,
|
|
55
|
+
1.0350e-06, -1.2937e-06, 2.0699e-06, -5.6061e-06, 1.4800e-04]
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class CF():
|
|
59
|
+
"""
|
|
60
|
+
Class defining the trivalent rare earth compound and its crystal field
|
|
61
|
+
parameters
|
|
62
|
+
|
|
63
|
+
There is only one basic object in CFlib, defining the trivalent rare
|
|
64
|
+
earth, its crystal field parameters, and, if already diagonalized, the
|
|
65
|
+
eigenvalues and eigenfunctions of the CF Hamiltonian.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
def __init__(self, RE=None, name=None, parfile=None):
|
|
69
|
+
"""Initializes the CF object or read it from a file."""
|
|
70
|
+
|
|
71
|
+
if parfile:
|
|
72
|
+
self.load(parfile)
|
|
73
|
+
else:
|
|
74
|
+
if RE:
|
|
75
|
+
self.RE = RE
|
|
76
|
+
else:
|
|
77
|
+
self.RE = 'Ce'
|
|
78
|
+
self.B20 = 0.0
|
|
79
|
+
self.B40 = 0.0
|
|
80
|
+
self.B60 = 0.0
|
|
81
|
+
self.B22 = 0.0
|
|
82
|
+
self.B42 = 0.0
|
|
83
|
+
self.B62 = 0.0
|
|
84
|
+
self.B43 = 0.0
|
|
85
|
+
self.B63 = 0.0
|
|
86
|
+
self.B44 = 0.0
|
|
87
|
+
self.B64 = 0.0
|
|
88
|
+
self.B66 = 0.0
|
|
89
|
+
self.Hz = 0.0
|
|
90
|
+
self.Hx = 0.0
|
|
91
|
+
self.name = name
|
|
92
|
+
self.H = np.matrix(np.zeros((self.size, self.size), dtype='float64'))
|
|
93
|
+
self.Jz = np.zeros((self.size, self.size), dtype='float64')
|
|
94
|
+
self.Jp = np.zeros((self.size, self.size), dtype='float64')
|
|
95
|
+
self.Jm = np.zeros((self.size, self.size), dtype='float64')
|
|
96
|
+
self.TP = np.zeros((self.size, self.size), dtype='float64')
|
|
97
|
+
self.EF = np.zeros((self.size, self.size), dtype='float64')
|
|
98
|
+
self.EV = np.zeros(self.size, dtype='float64')
|
|
99
|
+
self.peaks = []
|
|
100
|
+
self.T = 0.0
|
|
101
|
+
self.Tused = self.T
|
|
102
|
+
self.resolution = 0.01
|
|
103
|
+
self.threshold = 0.0001
|
|
104
|
+
self.Jz_exp = 0.0
|
|
105
|
+
self.Jx_exp = 0.0
|
|
106
|
+
self.muz = 0.0
|
|
107
|
+
self.mux = 0.0
|
|
108
|
+
|
|
109
|
+
def __str__(self):
|
|
110
|
+
"""
|
|
111
|
+
Return a summary of the model parameters
|
|
112
|
+
|
|
113
|
+
This includes the rare earth, the CF parameters, and, if
|
|
114
|
+
diagonalized, the eigenvalues and eigenvectors.
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
if self.name:
|
|
118
|
+
output = [self.name]
|
|
119
|
+
else:
|
|
120
|
+
output = []
|
|
121
|
+
output.append("%s: Nf = %s, J = %s" % (self.RE, self.Nf, self.J))
|
|
122
|
+
line = []
|
|
123
|
+
if self.B20 != 0.0:
|
|
124
|
+
line.append("B20 = %g" % self.B20)
|
|
125
|
+
if self.B22 != 0.0:
|
|
126
|
+
line.append("B22 = %g" % self.B22)
|
|
127
|
+
if self.B40 != 0.0:
|
|
128
|
+
line.append("B40 = %g" % self.B40)
|
|
129
|
+
if self.B42 != 0.0:
|
|
130
|
+
line.append("B42 = %g" % self.B42)
|
|
131
|
+
if self.B43 != 0.0:
|
|
132
|
+
line.append("B43 = %g" % self.B43)
|
|
133
|
+
if self.B44 != 0.0:
|
|
134
|
+
line.append("B44 = %g" % self.B44)
|
|
135
|
+
if self.B60 != 0.0:
|
|
136
|
+
line.append("B60 = %g" % self.B60)
|
|
137
|
+
if self.B62 != 0.0:
|
|
138
|
+
line.append("B62 = %g" % self.B62)
|
|
139
|
+
if self.B63 != 0.0:
|
|
140
|
+
line.append("B63 = %g" % self.B63)
|
|
141
|
+
if self.B64 != 0.0:
|
|
142
|
+
line.append("B64 = %g" % self.B64)
|
|
143
|
+
if self.B66 != 0.0:
|
|
144
|
+
line.append("B66 = %g" % self.B66)
|
|
145
|
+
if self.Hz != 0.0:
|
|
146
|
+
line.append("Hz = %g" % self.Hz)
|
|
147
|
+
if self.Hx != 0.0:
|
|
148
|
+
line.append("Hx = %g" % self.Hx)
|
|
149
|
+
|
|
150
|
+
if line:
|
|
151
|
+
output.append(" ".join(line))
|
|
152
|
+
|
|
153
|
+
self.get_peaks()
|
|
154
|
+
Jx_exp, Jz_exp, mux, muz = self.get_moments()
|
|
155
|
+
|
|
156
|
+
if muz != 0.0 or mux != 0.0:
|
|
157
|
+
output.append("<Jz> = %7.3f <muz> = %7.3f muB" % (Jz_exp, muz))
|
|
158
|
+
output.append("<Jx> = %7.3f <mux> = %7.3f muB" % (Jx_exp, mux))
|
|
159
|
+
|
|
160
|
+
if self.EV.any():
|
|
161
|
+
output.append("Crystal Field Eigenvalues and Eigenfunctions")
|
|
162
|
+
for i in range(self.EV.size):
|
|
163
|
+
line = ["%8.3f:" % self.EV[i]]
|
|
164
|
+
for j in range(self.EV.size):
|
|
165
|
+
if abs(self.EF[j, i]) > 0.0001:
|
|
166
|
+
Jz = j - self.J
|
|
167
|
+
if self.EF[j, i] < 0.0:
|
|
168
|
+
operator = "-"
|
|
169
|
+
else:
|
|
170
|
+
operator = "+"
|
|
171
|
+
line.append("%s%7.4f|%g>" %
|
|
172
|
+
(operator, abs(self.EF[j, i]), Jz))
|
|
173
|
+
output.append(" ".join(line))
|
|
174
|
+
|
|
175
|
+
if self.peaks:
|
|
176
|
+
output.append("Crystal Field Transitions")
|
|
177
|
+
if self.T != self.Tused:
|
|
178
|
+
self.get_peaks()
|
|
179
|
+
output.append("Temperature: %g K" % self.T)
|
|
180
|
+
for peak in self.peaks:
|
|
181
|
+
output.append("Energy: %8.3f meV Intensity: %8.4f" % peak)
|
|
182
|
+
|
|
183
|
+
return "\n".join(output)
|
|
184
|
+
|
|
185
|
+
def save(self, parfile=None):
|
|
186
|
+
"""Store the current object for later use."""
|
|
187
|
+
|
|
188
|
+
parser = ConfigParser()
|
|
189
|
+
parser.optionxform = str
|
|
190
|
+
|
|
191
|
+
parser.add_section('material')
|
|
192
|
+
parser.set('material', 'name', str(self.name))
|
|
193
|
+
parser.set('material', 'RE', str(self.RE))
|
|
194
|
+
|
|
195
|
+
parser.add_section('parameters')
|
|
196
|
+
parser.set('parameters', 'B20', str(self.B20))
|
|
197
|
+
parser.set('parameters', 'B22', str(self.B22))
|
|
198
|
+
parser.set('parameters', 'B40', str(self.B40))
|
|
199
|
+
parser.set('parameters', 'B42', str(self.B42))
|
|
200
|
+
parser.set('parameters', 'B43', str(self.B43))
|
|
201
|
+
parser.set('parameters', 'B44', str(self.B44))
|
|
202
|
+
parser.set('parameters', 'B60', str(self.B60))
|
|
203
|
+
parser.set('parameters', 'B62', str(self.B62))
|
|
204
|
+
parser.set('parameters', 'B63', str(self.B63))
|
|
205
|
+
parser.set('parameters', 'B64', str(self.B64))
|
|
206
|
+
parser.set('parameters', 'B66', str(self.B66))
|
|
207
|
+
parser.set('parameters', 'Hz', str(self.Hz))
|
|
208
|
+
parser.set('parameters', 'Hx', str(self.Hx))
|
|
209
|
+
|
|
210
|
+
if parfile is None:
|
|
211
|
+
parfile = '%s.cfg' % self.name
|
|
212
|
+
with open(parfile, 'wb') as cfg:
|
|
213
|
+
parser.write(cfg)
|
|
214
|
+
|
|
215
|
+
def load(self, parfile):
|
|
216
|
+
"""Load a saved version of the current object."""
|
|
217
|
+
|
|
218
|
+
if not os.path.exists(parfile):
|
|
219
|
+
raise OSError("'%s' does not exist" %
|
|
220
|
+
os.path.realpath(parfile))
|
|
221
|
+
|
|
222
|
+
parser = ConfigParser()
|
|
223
|
+
parser.read(parfile)
|
|
224
|
+
|
|
225
|
+
self.name = parser.get('material', 'name')
|
|
226
|
+
self.RE = parser.get('material', 'RE')
|
|
227
|
+
|
|
228
|
+
self.B20 = parser.getfloat('parameters', 'B20')
|
|
229
|
+
self.B22 = parser.getfloat('parameters', 'B22')
|
|
230
|
+
self.B40 = parser.getfloat('parameters', 'B40')
|
|
231
|
+
self.B42 = parser.getfloat('parameters', 'B42')
|
|
232
|
+
self.B43 = parser.getfloat('parameters', 'B43')
|
|
233
|
+
self.B44 = parser.getfloat('parameters', 'B44')
|
|
234
|
+
self.B60 = parser.getfloat('parameters', 'B60')
|
|
235
|
+
self.B62 = parser.getfloat('parameters', 'B62')
|
|
236
|
+
self.B63 = parser.getfloat('parameters', 'B63')
|
|
237
|
+
self.B64 = parser.getfloat('parameters', 'B64')
|
|
238
|
+
self.B66 = parser.getfloat('parameters', 'B66')
|
|
239
|
+
self.Hz = parser.getfloat('parameters', 'Hz')
|
|
240
|
+
self.Hx = parser.getfloat('parameters', 'Hx')
|
|
241
|
+
|
|
242
|
+
self.initialize()
|
|
243
|
+
|
|
244
|
+
def initialize(self):
|
|
245
|
+
"""Reinitializes the arrays used for the CF Hamiltonian."""
|
|
246
|
+
self.H = np.matrix(np.zeros((self.size, self.size), dtype='float64'))
|
|
247
|
+
self.Jz = np.zeros((self.size, self.size), dtype='float64')
|
|
248
|
+
self.Jp = np.zeros((self.size, self.size), dtype='float64')
|
|
249
|
+
self.Jm = np.zeros((self.size, self.size), dtype='float64')
|
|
250
|
+
self.TP = np.zeros((self.size, self.size), dtype='float64')
|
|
251
|
+
self.EF = np.zeros((self.size, self.size), dtype='float64')
|
|
252
|
+
self.EV = np.zeros(self.size, dtype='float64')
|
|
253
|
+
|
|
254
|
+
@property
|
|
255
|
+
def index(self):
|
|
256
|
+
return REs.index(self.RE)
|
|
257
|
+
|
|
258
|
+
@property
|
|
259
|
+
def Nf(self):
|
|
260
|
+
return self.index + 1
|
|
261
|
+
|
|
262
|
+
@property
|
|
263
|
+
def J(self):
|
|
264
|
+
return Jf[self.index]
|
|
265
|
+
|
|
266
|
+
@property
|
|
267
|
+
def size(self):
|
|
268
|
+
return int(2 * self.J + 1)
|
|
269
|
+
|
|
270
|
+
@property
|
|
271
|
+
def gJ(self):
|
|
272
|
+
return gJ[self.index]
|
|
273
|
+
|
|
274
|
+
@property
|
|
275
|
+
def r2(self):
|
|
276
|
+
return r2[self.index]
|
|
277
|
+
|
|
278
|
+
@property
|
|
279
|
+
def r4(self):
|
|
280
|
+
return r4[self.index]
|
|
281
|
+
|
|
282
|
+
@property
|
|
283
|
+
def r6(self):
|
|
284
|
+
return r6[self.index]
|
|
285
|
+
|
|
286
|
+
@property
|
|
287
|
+
def alphaJ(self):
|
|
288
|
+
return alphaJ[self.index]
|
|
289
|
+
|
|
290
|
+
@property
|
|
291
|
+
def betaJ(self):
|
|
292
|
+
return betaJ[self.index]
|
|
293
|
+
|
|
294
|
+
@property
|
|
295
|
+
def gammaJ(self):
|
|
296
|
+
return gammaJ[self.index]
|
|
297
|
+
|
|
298
|
+
def CFham(self):
|
|
299
|
+
"""Determine the CF Hamiltonian."""
|
|
300
|
+
|
|
301
|
+
J = self.J
|
|
302
|
+
H = np.matrix(np.zeros((self.size, self.size), dtype='float64'))
|
|
303
|
+
|
|
304
|
+
for m in range(self.size):
|
|
305
|
+
mJ = m - J
|
|
306
|
+
O20 = 3*mJ**2 - J*(J+1)
|
|
307
|
+
O40 = (35*mJ**4 - 30*J*(J+1)*mJ**2 +
|
|
308
|
+
25*mJ**2 - 6*J*(J+1) + 3*(J*(J+1))**2)
|
|
309
|
+
O60 = (231*mJ**6 - 315*J*(J+1)*mJ**4 + 735*mJ**4 +
|
|
310
|
+
105*(J*(J+1)*mJ)**2 - 525*J*(J+1)*mJ**2 + 294*mJ**2 -
|
|
311
|
+
5*(J*(J+1))**3 + 40*(J*(J+1))**2 - 60*J*(J+1))
|
|
312
|
+
H[m, m] += self.B20*O20 + self.B40*O40 + self.B60*O60
|
|
313
|
+
|
|
314
|
+
for m in range(self.size-2):
|
|
315
|
+
mJ = m - J
|
|
316
|
+
n = m + 2
|
|
317
|
+
nJ = mJ + 2
|
|
318
|
+
O22 = 0.5*np.sqrt((J*(J+1) - nJ*(nJ-1))*(J*(J+1) - (nJ-1)*(nJ-2)))
|
|
319
|
+
O42 = (3.5*(mJ**2+nJ**2) - J*(J+1) - 5) * O22
|
|
320
|
+
O62 = (16.5*(mJ**4+nJ**4) - 9*(mJ**2+nJ**2)*J*(J+1) -
|
|
321
|
+
61.5*(mJ**2+nJ**2) + (J*(J+1))**2 + 10*J*(J+1) + 102) * O22
|
|
322
|
+
H[m,n] += self.B22*O22 + self.B42*O42 + self.B62*O62
|
|
323
|
+
H[n,m] = H[m,n]
|
|
324
|
+
|
|
325
|
+
for m in range(self.size - 3):
|
|
326
|
+
mJ = m - J
|
|
327
|
+
n = m + 3
|
|
328
|
+
nJ = mJ + 3
|
|
329
|
+
A = 1.0
|
|
330
|
+
for k in range(3):
|
|
331
|
+
A *= J*(J+1) - (nJ-k)*(nJ-k-1)
|
|
332
|
+
O43 = 0.25*np.sqrt(A)*(mJ + nJ)
|
|
333
|
+
O63 = (0.25*(11*(mJ**3+nJ**3) - 3*(mJ+nJ)*J*(J+1) - 59*(mJ+nJ)) *
|
|
334
|
+
np.sqrt(A))
|
|
335
|
+
H[m,n] += self.B43*O43 + self.B63*O63
|
|
336
|
+
H[n,m] = H[m,n]
|
|
337
|
+
|
|
338
|
+
for m in range(self.size - 4):
|
|
339
|
+
mJ = m - J
|
|
340
|
+
n = m + 4
|
|
341
|
+
nJ = mJ + 4
|
|
342
|
+
O44 = 1.0
|
|
343
|
+
for k in range(4):
|
|
344
|
+
O44 *= J*(J+1) - (nJ - k)*(nJ - k - 1)
|
|
345
|
+
O44 = 0.5*np.sqrt(O44)
|
|
346
|
+
O64 = (5.5*(mJ**2 + nJ**2) - J*(J+1) - 38)*O44
|
|
347
|
+
H[m,n] += self.B44*O44 + self.B64*O64
|
|
348
|
+
H[n,m] = H[m,n]
|
|
349
|
+
|
|
350
|
+
for m in range(self.size - 6):
|
|
351
|
+
mJ = m - J
|
|
352
|
+
n = m + 6
|
|
353
|
+
nJ = mJ + 6
|
|
354
|
+
O66 = 1.0
|
|
355
|
+
for k in range(6):
|
|
356
|
+
O66 *= J*(J+1) - (nJ - k)*(nJ - k - 1)
|
|
357
|
+
O66 = 0.5*np.sqrt(O66)
|
|
358
|
+
H[m,n] += self.B66*O66
|
|
359
|
+
H[n,m] = H[m,n]
|
|
360
|
+
|
|
361
|
+
return H
|
|
362
|
+
|
|
363
|
+
def MFham(self):
|
|
364
|
+
"""Determine the Zeeman terms to the Hamiltonian."""
|
|
365
|
+
|
|
366
|
+
J = self.J
|
|
367
|
+
H = np.matrix(np.zeros((self.size, self.size), dtype='float64'))
|
|
368
|
+
|
|
369
|
+
for m in range(self.size):
|
|
370
|
+
mJ = m - J
|
|
371
|
+
H[m, m] -= self.Hz*mJ
|
|
372
|
+
|
|
373
|
+
for m in range(self.size - 1):
|
|
374
|
+
mJ = m - J
|
|
375
|
+
n = m + 1
|
|
376
|
+
nJ = mJ + 1
|
|
377
|
+
H[m,n] -= 0.5*self.Hx*np.sqrt((J*(J+1)-mJ*nJ))
|
|
378
|
+
H[n,m] = H[m,n]
|
|
379
|
+
|
|
380
|
+
return H
|
|
381
|
+
|
|
382
|
+
def Ham(self):
|
|
383
|
+
"""Returns the total Hamiltonian including CF and Zeeman terms."""
|
|
384
|
+
|
|
385
|
+
return (self.CFham() + self.MFham())
|
|
386
|
+
|
|
387
|
+
def EFS(self):
|
|
388
|
+
"""Calculate eigenvalues/functions of the total Hamiltonian."""
|
|
389
|
+
|
|
390
|
+
H = self.Ham()
|
|
391
|
+
self.EV, self.EF = eigh(H)
|
|
392
|
+
self.EV = self.EV - self.EV.min()
|
|
393
|
+
|
|
394
|
+
def TPS(self):
|
|
395
|
+
"""Determine dipole matrix elements of the total Hamiltonian."""
|
|
396
|
+
|
|
397
|
+
J = self.J
|
|
398
|
+
|
|
399
|
+
self.Jz = np.zeros((self.size, self.size), dtype='float64')
|
|
400
|
+
self.Jp = np.zeros((self.size, self.size), dtype='float64')
|
|
401
|
+
self.Jm = np.zeros((self.size, self.size), dtype='float64')
|
|
402
|
+
for m in range(self.size):
|
|
403
|
+
for n in range(self.size):
|
|
404
|
+
for k in range(self.size):
|
|
405
|
+
kJ = k - J
|
|
406
|
+
self.Jz[m,n] += self.EF[k, m]*self.EF[k,n]*kJ
|
|
407
|
+
for k in range(self.size - 1):
|
|
408
|
+
kJ = k - J
|
|
409
|
+
l = k + 1
|
|
410
|
+
lJ = l - J
|
|
411
|
+
self.Jp[m,n] = (self.Jp[m,n] +
|
|
412
|
+
self.EF[l,m]*self.EF[k,n] *
|
|
413
|
+
np.sqrt(J*(J+1)-kJ*lJ))
|
|
414
|
+
for k in range(1, self.size):
|
|
415
|
+
kJ = k - J
|
|
416
|
+
l = k - 1
|
|
417
|
+
lJ = l - J
|
|
418
|
+
self.Jm[m,n] = (self.Jm[m,n] +
|
|
419
|
+
self.EF[l,m]*self.EF[k,n] *
|
|
420
|
+
np.sqrt(J*(J+1)-kJ*lJ))
|
|
421
|
+
self.TP[m,n] = (
|
|
422
|
+
(2*self.Jz[m,n]**2+self.Jp[m,n]**2+self.Jm[m,n]**2) / 3)
|
|
423
|
+
|
|
424
|
+
def get_peaks(self, T=None, Hx=None, Hz=None):
|
|
425
|
+
"""Determine the peak intensities from the total Hamiltonian."""
|
|
426
|
+
|
|
427
|
+
if Hx:
|
|
428
|
+
old_Hx = self.Hx
|
|
429
|
+
self.Hx = Hx
|
|
430
|
+
if Hz:
|
|
431
|
+
old_Hz = self.Hz
|
|
432
|
+
self.Hz = Hz
|
|
433
|
+
self.EFS()
|
|
434
|
+
self.TPS()
|
|
435
|
+
|
|
436
|
+
BF = np.zeros(self.size, dtype='float64')
|
|
437
|
+
|
|
438
|
+
if T is None:
|
|
439
|
+
T = self.T
|
|
440
|
+
kT = T / 11.6045
|
|
441
|
+
if kT <= 0.0:
|
|
442
|
+
BF[0] = 1.0
|
|
443
|
+
else:
|
|
444
|
+
Z = sum(np.exp(-self.EV/kT))
|
|
445
|
+
BF = np.exp(-self.EV/kT) / Z
|
|
446
|
+
|
|
447
|
+
peaks = []
|
|
448
|
+
for n in range(self.size):
|
|
449
|
+
for m in range(self.size):
|
|
450
|
+
ET = self.EV[m] - self.EV[n]
|
|
451
|
+
IT = self.TP[m,n]*BF[n]
|
|
452
|
+
if IT > 0.0:
|
|
453
|
+
peaks.append((ET, IT))
|
|
454
|
+
|
|
455
|
+
self.peaks = []
|
|
456
|
+
for k in range(len(peaks)):
|
|
457
|
+
ETk, ITk = peaks[k]
|
|
458
|
+
if ITk > 0.0:
|
|
459
|
+
sum_peaks = ETk*ITk
|
|
460
|
+
for l in range(len(peaks)):
|
|
461
|
+
if k != l:
|
|
462
|
+
ETl, ITl = peaks[l]
|
|
463
|
+
if ITl > 0.0 and abs(ETk - ETl) < self.resolution:
|
|
464
|
+
ITk = ITk + ITl
|
|
465
|
+
sum_peaks = sum_peaks + ETl*ITl
|
|
466
|
+
peaks[l] = (ETl, 0.0)
|
|
467
|
+
if ITk > self.threshold:
|
|
468
|
+
ETk = sum_peaks / ITk
|
|
469
|
+
self.peaks.append((ETk, ITk))
|
|
470
|
+
self.peaks.sort()
|
|
471
|
+
self.Tused = T
|
|
472
|
+
if Hx:
|
|
473
|
+
self.Hx = old_Hx
|
|
474
|
+
if Hz:
|
|
475
|
+
self.Hz = old_Hz
|
|
476
|
+
|
|
477
|
+
return self.peaks
|
|
478
|
+
|
|
479
|
+
def spectrum(self, eps=None, sigma=None, gamma=None, T=None,
|
|
480
|
+
Hx=None, Hz=None):
|
|
481
|
+
"""Calculates the neutron scattering cross section."""
|
|
482
|
+
|
|
483
|
+
if T is None:
|
|
484
|
+
T = self.T
|
|
485
|
+
|
|
486
|
+
peaks = self.get_peaks(T, Hx, Hz)
|
|
487
|
+
|
|
488
|
+
if eps is None:
|
|
489
|
+
eps = np.linspace(-1.1*self.EV[-1], 1.1*self.EV[-1], 501)
|
|
490
|
+
if sigma is None and gamma is None:
|
|
491
|
+
sigma = 0.01*(max(eps) - min(eps))
|
|
492
|
+
|
|
493
|
+
S = np.zeros(eps.size, dtype='float64')
|
|
494
|
+
|
|
495
|
+
for EV, IT in peaks:
|
|
496
|
+
if sigma is not None and gamma is None:
|
|
497
|
+
S += IT * gauss(eps, EV, sigma)
|
|
498
|
+
elif sigma is None and gamma is not None:
|
|
499
|
+
S += IT * lorentz(eps, EV, gamma)
|
|
500
|
+
else:
|
|
501
|
+
S += IT * pseudovoigt(eps, EV, sigma, gamma)
|
|
502
|
+
|
|
503
|
+
S *= 72.65*self.gJ**2
|
|
504
|
+
|
|
505
|
+
return S
|
|
506
|
+
|
|
507
|
+
def NXspectrum(self, eps=None, sigma=None, gamma=None, T=None,
|
|
508
|
+
Hx=None, Hz=None):
|
|
509
|
+
"""Returns the neutron scattering cross section."""
|
|
510
|
+
|
|
511
|
+
from nexusformat.nexus import NXdata, NXentry, NXfield, NXsample
|
|
512
|
+
|
|
513
|
+
if T is None:
|
|
514
|
+
T = self.T
|
|
515
|
+
|
|
516
|
+
S = self.spectrum(eps, sigma, gamma, T, Hx, Hz)
|
|
517
|
+
entry = NXentry()
|
|
518
|
+
entry.title = "Crystal Field Spectra for %s at %s K" % (self.name, T)
|
|
519
|
+
entry.sample = NXsample()
|
|
520
|
+
entry.sample.temperature = T
|
|
521
|
+
entry.sample.temperature.units = "K"
|
|
522
|
+
entry.data = NXdata(NXfield(S, name="intensity", units="mb/sr/meV"),
|
|
523
|
+
NXfield(eps, name="energy_transfer", units="meV"))
|
|
524
|
+
return entry
|
|
525
|
+
|
|
526
|
+
nxspectrum = NXspectrum
|
|
527
|
+
|
|
528
|
+
def get_moments(self, T=None):
|
|
529
|
+
"""Calculate the magnetic moments of the CF model."""
|
|
530
|
+
|
|
531
|
+
if T is None:
|
|
532
|
+
T = self.T
|
|
533
|
+
if T != self.Tused:
|
|
534
|
+
self.get_peaks(T)
|
|
535
|
+
kT = T / 11.6045
|
|
536
|
+
if kT > 0.0:
|
|
537
|
+
Jz_exp = 0.0
|
|
538
|
+
Jx_exp = 0.0
|
|
539
|
+
Z = sum(np.exp(-self.EV/kT))
|
|
540
|
+
for i in range(self.EV.size):
|
|
541
|
+
Jz_exp = Jz_exp + self.Jz[i,i]*np.exp(-self.EV[i]/kT)
|
|
542
|
+
Jx_exp += (0.5*(self.Jp[i,i] + self.Jm[i,i]) *
|
|
543
|
+
np.exp(-self.EV[i]/kT))
|
|
544
|
+
Jz_exp = Jz_exp / Z
|
|
545
|
+
Jx_exp = Jx_exp / Z
|
|
546
|
+
else:
|
|
547
|
+
Jz_exp = (sum(self.Jz[self.EV==0.0, self.EV==0.0]) /
|
|
548
|
+
self.EV[self.EV==0.0].size)
|
|
549
|
+
Jx_exp = (sum(0.5*(self.Jp[self.EV==0.0, self.EV==0.0] +
|
|
550
|
+
self.Jm[self.EV==0.0, self.EV==0.0])) /
|
|
551
|
+
self.EV[self.EV==0.0].size)
|
|
552
|
+
self.Jz_exp = Jz_exp
|
|
553
|
+
self.muz = self.gJ*self.Jz_exp
|
|
554
|
+
self.Jx_exp = Jx_exp
|
|
555
|
+
self.mux = self.gJ*self.Jx_exp
|
|
556
|
+
|
|
557
|
+
return self.Jx_exp, self.Jz_exp, self.mux, self.muz
|
|
558
|
+
|
|
559
|
+
def chi(self, T=None):
|
|
560
|
+
"""Calculate the susceptibility at a specified temperature."""
|
|
561
|
+
|
|
562
|
+
if T is None:
|
|
563
|
+
T = self.T
|
|
564
|
+
|
|
565
|
+
kT = T / 11.6045
|
|
566
|
+
Z = sum(np.exp(-self.EV/kT))
|
|
567
|
+
|
|
568
|
+
ChiC_zz = 0.0 * T
|
|
569
|
+
ChiC_xx = 0.0 * T
|
|
570
|
+
ChiV_zz = 0.0 * T
|
|
571
|
+
ChiV_xx = 0.0 * T
|
|
572
|
+
for m in range(self.EV.size):
|
|
573
|
+
for n in range(self.EV.size):
|
|
574
|
+
if abs(self.EV[n] - self.EV[m]) < 0.00001 * kT:
|
|
575
|
+
ChiC_zz += self.Jz[m,n]**2 * np.exp(-self.EV[m] / kT)
|
|
576
|
+
ChiC_xx += 0.25 * (self.Jp[m,n]**2 + self.Jm[m,n]**2) \
|
|
577
|
+
* np.exp(-self.EV[m]/kT)
|
|
578
|
+
else:
|
|
579
|
+
ChiV_zz += 2 * self.Jz[m,n]**2 * np.exp(-self.EV[m] / kT) \
|
|
580
|
+
/ (self.EV[n] - self.EV[m])
|
|
581
|
+
ChiV_xx += 0.5 * (self.Jp[m,n]**2 + self.Jm[m,n]**2) \
|
|
582
|
+
* np.exp(-self.EV[m]/kT)/(self.EV[n]-self.EV[m])
|
|
583
|
+
ChiC_zz = self.gJ**2 * ChiC_zz / (kT * Z)
|
|
584
|
+
ChiC_xx = self.gJ**2 * ChiC_xx / (kT * Z)
|
|
585
|
+
ChiV_zz = self.gJ**2 * ChiV_zz / Z
|
|
586
|
+
ChiV_xx = self.gJ**2 * ChiV_xx / Z
|
|
587
|
+
|
|
588
|
+
return ChiC_zz, ChiC_xx, ChiV_zz, ChiV_xx
|
|
589
|
+
|
|
590
|
+
def chis(self, Ts=None):
|
|
591
|
+
"""
|
|
592
|
+
Calculate the susceptibility as a function of temperature.
|
|
593
|
+
|
|
594
|
+
Parameters
|
|
595
|
+
----------
|
|
596
|
+
Ts : array
|
|
597
|
+
The temperatures at which to calculate the susceptibility.
|
|
598
|
+
If Ts is None, then the susceptibility is calculated at 300
|
|
599
|
+
temperatures spaced evenly between 1 and 300 K.
|
|
600
|
+
|
|
601
|
+
Returns
|
|
602
|
+
-------
|
|
603
|
+
ChiC_zz, ChiC_xx, ChiV_zz, ChiV_xx : array
|
|
604
|
+
The calculated susceptibility at each temperature, split
|
|
605
|
+
into the three components.
|
|
606
|
+
"""
|
|
607
|
+
if Ts is None:
|
|
608
|
+
Ts = np.linspace(1.0, 300.0, 300, dtype=np.float32)
|
|
609
|
+
|
|
610
|
+
self.EFS()
|
|
611
|
+
self.TPS()
|
|
612
|
+
|
|
613
|
+
if Ts is None:
|
|
614
|
+
Ts = np.linspace(1.0, 300.0, 300, dtype=np.float32)
|
|
615
|
+
|
|
616
|
+
ChiC_zz = np.zeros(shape=Ts.shape, dtype=np.float32)
|
|
617
|
+
ChiC_xx = np.zeros(shape=Ts.shape, dtype=np.float32)
|
|
618
|
+
ChiV_zz = np.zeros(shape=Ts.shape, dtype=np.float32)
|
|
619
|
+
ChiV_xx = np.zeros(shape=Ts.shape, dtype=np.float32)
|
|
620
|
+
|
|
621
|
+
for i, T in enumerate(Ts):
|
|
622
|
+
ChiC_zz[i], ChiC_xx[i], ChiV_zz[i], ChiV_xx[i] = self.chi(T)
|
|
623
|
+
|
|
624
|
+
return ChiC_zz, ChiC_xx, ChiV_zz, ChiV_xx
|
|
625
|
+
|
|
626
|
+
|
|
627
|
+
def NXchi(self, Ts=None):
|
|
628
|
+
"""
|
|
629
|
+
Return the susceptibility of the crystal field model.
|
|
630
|
+
|
|
631
|
+
Parameters
|
|
632
|
+
----------
|
|
633
|
+
Ts : array
|
|
634
|
+
The temperatures at which to calculate the susceptibility.
|
|
635
|
+
If Ts is None, then the susceptibility is calculated at 300
|
|
636
|
+
temperatures spaced evenly between 1 and 300 K.
|
|
637
|
+
|
|
638
|
+
Returns
|
|
639
|
+
-------
|
|
640
|
+
entry : NXentry
|
|
641
|
+
A NeXus data structure containing the susceptibility of the
|
|
642
|
+
crystal field model.
|
|
643
|
+
"""
|
|
644
|
+
from nexusformat.nexus import NXdata, NXentry, NXfield
|
|
645
|
+
|
|
646
|
+
if Ts is None:
|
|
647
|
+
Ts = np.linspace(1.0, 300.0, 300, dtype=np.float32)
|
|
648
|
+
|
|
649
|
+
entry = NXentry()
|
|
650
|
+
entry.title = "Susceptibility of %s" % self.name
|
|
651
|
+
temperature = NXfield(Ts, name="temperature")
|
|
652
|
+
temperature.units = "K"
|
|
653
|
+
|
|
654
|
+
ChiC_zz, ChiC_xx, ChiV_zz, ChiV_xx = self.chis(Ts)
|
|
655
|
+
|
|
656
|
+
chi = NXfield(ChiC_zz + ChiV_zz + 2*(ChiC_xx + ChiV_xx), name="chi")
|
|
657
|
+
invchi = NXfield(1/chi, name='invchi')
|
|
658
|
+
chiz = NXfield(ChiC_zz+ChiV_zz, name='chiz')
|
|
659
|
+
chix = NXfield(ChiC_xx+ChiV_xx, name='chix')
|
|
660
|
+
|
|
661
|
+
entry.chi = NXdata(chi, temperature)
|
|
662
|
+
entry.chi.title = "Susceptibility of %s" % self.name
|
|
663
|
+
entry.invchi = NXdata(invchi, temperature)
|
|
664
|
+
entry.invchi.title = "Inverse Susceptibility of %s" % self.name
|
|
665
|
+
entry.chiz = NXdata(chiz, temperature)
|
|
666
|
+
entry.chiz.title = "Susceptibility of %s (z-axis)" % self.name
|
|
667
|
+
entry.chix = NXdata(chix, temperature)
|
|
668
|
+
entry.chix.title = "Susceptibility of %s (x-axis)" % self.name
|
|
669
|
+
|
|
670
|
+
return entry
|
|
671
|
+
|
|
672
|
+
nxchi = NXchi
|
|
673
|
+
|
|
674
|
+
integral_factor = np.sqrt(2*np.pi)
|
|
675
|
+
sigma_factor = 2 * np.sqrt(2*np.log(2))
|
|
676
|
+
|
|
677
|
+
def gauss(x, center, sigma):
|
|
678
|
+
return np.exp(-(x-center)**2/(2*sigma**2)) / (sigma * integral_factor)
|
|
679
|
+
|
|
680
|
+
def lorentz(x, center, gamma):
|
|
681
|
+
return (gamma / np.pi) / ((x - center) ** 2 + gamma ** 2)
|
|
682
|
+
|
|
683
|
+
def pseudovoigt(x, center, sigma, gamma):
|
|
684
|
+
GammaG = sigma_factor * sigma
|
|
685
|
+
GammaL = 2 * gamma
|
|
686
|
+
FWHM = (GammaG**5 +
|
|
687
|
+
2.69269 * GammaG**4 * GammaL +
|
|
688
|
+
2.42843 * GammaG**3 * GammaL**2 +
|
|
689
|
+
4.47163 * GammaG**2 + GammaL**3 +
|
|
690
|
+
0.07842 * GammaG**4 * GammaL +
|
|
691
|
+
GammaL**5)**(0.2)
|
|
692
|
+
ratio = GammaL / FWHM
|
|
693
|
+
fraction = 1.36603 * ratio - 0.47719 * ratio**2 + 0.11116 * ratio**3
|
|
694
|
+
return ((1-fraction) * gauss(x, center, sigma) +
|
|
695
|
+
fraction * lorentz(x, center, gamma))
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
from nexpy.gui.dialogs import GridParameters, NXDialog
|
|
2
|
+
from nexpy.gui.pyqt import QtCore, QtGui
|
|
3
|
+
from nexpy.gui.utils import report_error
|
|
4
|
+
from nexusformat.nexus import NeXusError
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def show_dialog(parent=None):
|
|
8
|
+
try:
|
|
9
|
+
dialog = DefineModelDialog()
|
|
10
|
+
dialog.show()
|
|
11
|
+
except NeXusError as error:
|
|
12
|
+
report_error("Defining CF Model", error)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class DefineModelDialog(NXDialog):
|
|
16
|
+
|
|
17
|
+
def __init__(self, parent=None):
|
|
18
|
+
super(DefineModelDialog, self).__init__(parent)
|
|
19
|
+
|
|
20
|
+
node = self.get_node()
|
|
21
|
+
self.root = node.nxroot
|
|
22
|
+
|
|
23
|
+
symmetries = ['cubic', 'tetragonal', 'orthorhombic', 'hexagonal',
|
|
24
|
+
'monoclinic', 'triclinic']
|
|
25
|
+
|
|
26
|
+
self.rare_earths = ['Ce', 'Pr', 'Nd', 'Pm', 'Sm', 'Eu', 'Gd', 'Tb',
|
|
27
|
+
'Dy', 'Ho', 'Er', 'Tm', 'Yb']
|
|
28
|
+
|
|
29
|
+
self.parameters = GridParameters()
|
|
30
|
+
self.parameters.add('symmetry', symmetries, 'Symmetry')
|
|
31
|
+
|
|
32
|
+
action_buttons = self.action_buttons(('Plot', self.plot_lattice),
|
|
33
|
+
('Save', self.write_parameters))
|
|
34
|
+
self.set_layout(self.entry_layout, self.parameters.grid(),
|
|
35
|
+
action_buttons, self.close_buttons())
|
|
36
|
+
self.set_title('Defining CF Model')
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def cf_grid(self):
|
|
40
|
+
parameters = []
|
|
41
|
+
if self.symmetry == 'cubic':
|
|
42
|
+
parameters
|
|
43
|
+
|
|
44
|
+
self.B20_box = QtGui.QLineEdit()
|
|
45
|
+
self.B22_box = QtGui.QLineEdit()
|
|
46
|
+
self.B40_box = QtGui.QLineEdit()
|
|
47
|
+
self.B42_box = QtGui.QLineEdit()
|
|
48
|
+
self.B43_box = QtGui.QLineEdit()
|
|
49
|
+
self.B44_box = QtGui.QLineEdit()
|
|
50
|
+
self.B60_box = QtGui.QLineEdit()
|
|
51
|
+
self.B62_box = QtGui.QLineEdit()
|
|
52
|
+
self.B63_box = QtGui.QLineEdit()
|
|
53
|
+
self.B64_box = QtGui.QLineEdit()
|
|
54
|
+
self.B66_box = QtGui.QLineEdit()
|
|
55
|
+
self.Hz_box = QtGui.QLineEdit()
|
|
56
|
+
self.Hx_box = QtGui.QLineEdit()
|
|
57
|
+
grid = self.parameters.grid()
|
|
58
|
+
grid.addWidget(QtGui.QLabel('B20:'), 0, 0)
|
|
59
|
+
grid.addWidget(QtGui.QLabel('B22:'), 0, 0)
|
|
60
|
+
grid.addWidget(QtGui.QLabel('Unit Cell - a (Ang):'), 1, 0)
|
|
61
|
+
grid.addWidget(QtGui.QLabel('Unit Cell - b (Ang):'), 2, 0)
|
|
62
|
+
grid.addWidget(QtGui.QLabel('Unit Cell - c (Ang):'), 3, 0)
|
|
63
|
+
grid.addWidget(QtGui.QLabel('Unit Cell - alpha (deg):'), 4, 0)
|
|
64
|
+
grid.addWidget(QtGui.QLabel('Unit Cell - beta (deg):'), 5, 0)
|
|
65
|
+
grid.addWidget(QtGui.QLabel('Unit Cell - gamma (deg):'), 6, 0)
|
|
66
|
+
grid.addWidget(QtGui.QLabel('Wavelength (Ang):'), 7, 0)
|
|
67
|
+
grid.addWidget(QtGui.QLabel('Distance (mm):'), 8, 0)
|
|
68
|
+
grid.addWidget(QtGui.QLabel('Yaw (deg):'), 9, 0)
|
|
69
|
+
grid.addWidget(QtGui.QLabel('Pitch (deg):'), 10, 0)
|
|
70
|
+
grid.addWidget(QtGui.QLabel('Roll (deg):'), 11, 0)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def update_parameter(self, box, value):
|
|
74
|
+
if value is not None:
|
|
75
|
+
box.setText(str(value))
|
|
76
|
+
|
|
77
|
+
def update_parameters(self):
|
|
78
|
+
self.update_parameter(self.unitcell_a_box, self.refine.a)
|
|
79
|
+
self.update_parameter(self.unitcell_b_box, self.refine.b)
|
|
80
|
+
self.update_parameter(self.unitcell_c_box, self.refine.c)
|
|
81
|
+
self.update_parameter(self.unitcell_alpha_box, self.refine.alpha)
|
|
82
|
+
self.update_parameter(self.unitcell_beta_box, self.refine.beta)
|
|
83
|
+
self.update_parameter(self.unitcell_gamma_box, self.refine.gamma)
|
|
84
|
+
self.update_parameter(self.wavelength_box, self.refine.wavelength)
|
|
85
|
+
self.update_parameter(self.distance_box, self.refine.distance)
|
|
86
|
+
self.update_parameter(self.yaw_box, self.refine.yaw)
|
|
87
|
+
self.update_parameter(self.pitch_box, self.refine.pitch)
|
|
88
|
+
self.update_parameter(self.roll_box, self.refine.roll)
|
|
89
|
+
self.update_parameter(self.xc_box, self.refine.xc)
|
|
90
|
+
self.update_parameter(self.yc_box, self.refine.yc)
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def symmetry(self):
|
|
94
|
+
return self.symmetry_box.currentText()
|
|
95
|
+
|
|
96
|
+
def set_symmetry(self):
|
|
97
|
+
self.refine.symmetry = self.get_symmetry()
|
|
98
|
+
self.refine.set_symmetry()
|
|
99
|
+
self.update_parameters()
|
|
100
|
+
if self.refine.symmetry == 'cubic':
|
|
101
|
+
self.unitcell_b_checkbox.setCheckState(QtCore.Qt.Unchecked)
|
|
102
|
+
self.unitcell_c_checkbox.setCheckState(QtCore.Qt.Unchecked)
|
|
103
|
+
self.unitcell_alpha_checkbox.setCheckState(QtCore.Qt.Unchecked)
|
|
104
|
+
self.unitcell_beta_checkbox.setCheckState(QtCore.Qt.Unchecked)
|
|
105
|
+
self.unitcell_gamma_checkbox.setCheckState(QtCore.Qt.Unchecked)
|
|
106
|
+
elif self.refine.symmetry == 'tetragonal':
|
|
107
|
+
self.unitcell_b_checkbox.setCheckState(QtCore.Qt.Unchecked)
|
|
108
|
+
self.unitcell_alpha_checkbox.setCheckState(QtCore.Qt.Unchecked)
|
|
109
|
+
self.unitcell_beta_checkbox.setCheckState(QtCore.Qt.Unchecked)
|
|
110
|
+
self.unitcell_gamma_checkbox.setCheckState(QtCore.Qt.Unchecked)
|
|
111
|
+
elif self.refine.symmetry == 'orthorhombic':
|
|
112
|
+
self.unitcell_alpha_checkbox.setCheckState(QtCore.Qt.Unchecked)
|
|
113
|
+
self.unitcell_beta_checkbox.setCheckState(QtCore.Qt.Unchecked)
|
|
114
|
+
self.unitcell_gamma_checkbox.setCheckState(QtCore.Qt.Unchecked)
|
|
115
|
+
elif self.refine.symmetry == 'hexagonal':
|
|
116
|
+
self.unitcell_b_checkbox.setCheckState(QtCore.Qt.Unchecked)
|
|
117
|
+
self.unitcell_alpha_checkbox.setCheckState(QtCore.Qt.Unchecked)
|
|
118
|
+
self.unitcell_beta_checkbox.setCheckState(QtCore.Qt.Unchecked)
|
|
119
|
+
self.unitcell_gamma_checkbox.setCheckState(QtCore.Qt.Unchecked)
|
|
120
|
+
elif self.refine.symmetry == 'monoclinic':
|
|
121
|
+
self.unitcell_alpha_checkbox.setCheckState(QtCore.Qt.Unchecked)
|
|
122
|
+
self.unitcell_gamma_checkbox.setCheckState(QtCore.Qt.Unchecked)
|
|
123
|
+
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: CFcal
|
|
3
|
+
Version: 0.1.6
|
|
4
|
+
Summary: Python calculator for crystal fields
|
|
5
|
+
Author-email: Raymond Osborn <rayosborn@mac.com>
|
|
6
|
+
License: Licensing Terms for NeXpy
|
|
7
|
+
-------------------------
|
|
8
|
+
|
|
9
|
+
NeXpy is licensed under the terms of the Modified BSD License (also known as
|
|
10
|
+
New or Revised BSD), as follows:
|
|
11
|
+
|
|
12
|
+
Copyright (c) 2017, Ray Osborn
|
|
13
|
+
|
|
14
|
+
All rights reserved.
|
|
15
|
+
|
|
16
|
+
Redistribution and use in source and binary forms, with or without
|
|
17
|
+
modification, are permitted provided that the following conditions are met:
|
|
18
|
+
|
|
19
|
+
Redistributions of source code must retain the above copyright notice, this
|
|
20
|
+
list of conditions and the following disclaimer.
|
|
21
|
+
|
|
22
|
+
Redistributions in binary form must reproduce the above copyright notice, this
|
|
23
|
+
list of conditions and the following disclaimer in the documentation and/or
|
|
24
|
+
other materials provided with the distribution.
|
|
25
|
+
|
|
26
|
+
Neither the name of the NeXpy Development Team nor the names of its
|
|
27
|
+
contributors may be used to endorse or promote products derived from this
|
|
28
|
+
software without specific prior written permission.
|
|
29
|
+
|
|
30
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
31
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
32
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
33
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
|
34
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
35
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
36
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
37
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
38
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
39
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
40
|
+
|
|
41
|
+
Copyright
|
|
42
|
+
---------
|
|
43
|
+
|
|
44
|
+
The following banner should be used in any source code file to indicate the
|
|
45
|
+
copyright and license terms:
|
|
46
|
+
|
|
47
|
+
#-----------------------------------------------------------------------------
|
|
48
|
+
# Copyright (c) 2017, Ray Osborn.
|
|
49
|
+
#
|
|
50
|
+
# Distributed under the terms of the Modified BSD License.
|
|
51
|
+
#
|
|
52
|
+
# The full license is in the file COPYING, distributed with this software.
|
|
53
|
+
#-----------------------------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
Project-URL: Homepage, https://github.com/rayosborn/cfcal
|
|
56
|
+
Project-URL: Repository, https://github.com/rayosborn/cfcal.git
|
|
57
|
+
Project-URL: Issues, https://github.com/rayosborn/cfcal/issues
|
|
58
|
+
Project-URL: Documentation, https://github.com/rayosborn/cfcal
|
|
59
|
+
Project-URL: Changelog, https://github.com/rayosborn/cfcal/releases
|
|
60
|
+
Keywords: neutron scattering,crystal fields,data analysis
|
|
61
|
+
Classifier: Development Status :: 4 - Beta
|
|
62
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
63
|
+
Classifier: Intended Audience :: Science/Research
|
|
64
|
+
Classifier: Topic :: Scientific/Engineering
|
|
65
|
+
Classifier: Programming Language :: Python :: 3
|
|
66
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
67
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
68
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
69
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
70
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
71
|
+
Requires-Python: >=3.10
|
|
72
|
+
Description-Content-Type: text/markdown
|
|
73
|
+
License-File: COPYING
|
|
74
|
+
Requires-Dist: numpy
|
|
75
|
+
Requires-Dist: scipy
|
|
76
|
+
Dynamic: license-file
|
|
77
|
+
|
|
78
|
+
Introduction
|
|
79
|
+
============
|
|
80
|
+
CFcal is a Python package for performing calculations of crystal field (CF)
|
|
81
|
+
properties of rare earth ions using the Stevens Operator formalism [K. W. H.
|
|
82
|
+
Stevens, Proc. Phys. Soc. A **65**, 209 (1952)]. Once the CF parameters have
|
|
83
|
+
been initialized, the CF Hamiltonian can be diagonalized to determine the
|
|
84
|
+
energies and wavefunctions of all the CF levels. These can be used to determine
|
|
85
|
+
the magnetic susceptibility and neutron scattering spectra as a function of
|
|
86
|
+
temperature.
|
|
87
|
+
|
|
88
|
+
Installing and Running
|
|
89
|
+
======================
|
|
90
|
+
CFcal requires Python 3.10 or later.
|
|
91
|
+
|
|
92
|
+
The easiest way to install CFcal is from PyPI:
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
$ pip install cfcal
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Alternatively, the latest development version can be installed from the CFcal
|
|
99
|
+
[Git repository](https://github.com/rayosborn/cfcal):
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
$ git clone https://github.com/rayosborn/cfcal.git
|
|
103
|
+
$ cd cfcal
|
|
104
|
+
$ pip install .
|
|
105
|
+
```
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
cfcal/__init__.py,sha256=X20GzQ1bJ735ICDyR_YgkId_HEoTJ-LBCxxfEQr6uPI,45
|
|
2
|
+
cfcal/_version.py,sha256=mSVY638Y8lbMDXqQg68klFY9uyBAV8qKjFmkqikYzOU,520
|
|
3
|
+
cfcal/cfcal.py,sha256=b3fiiPAeir5HKaM4gptlcCDgss6b5s82lMiXRLoo66g,24225
|
|
4
|
+
cfcal/plugins/cfcal/__init__.py,sha256=debg6EjpJh-FxK8QTRj2p1Zx32cQLm9J_o2FoA8i5_A,152
|
|
5
|
+
cfcal/plugins/cfcal/define_model.py,sha256=M81K8Wa3ypGBO8aio0CxNn364ILfYK6OS6PTzyFF68Y,5611
|
|
6
|
+
cfcal-0.1.6.dist-info/licenses/COPYING,sha256=tLIBbKjIFCqBXVWr4KfC5ZW2LgpoIYc-8CPhZ982j2I,2102
|
|
7
|
+
cfcal-0.1.6.dist-info/METADATA,sha256=bswpUeAfzjtqLyDhteE4VYwmGMpTN4Q8fIONmx2dub8,4557
|
|
8
|
+
cfcal-0.1.6.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
9
|
+
cfcal-0.1.6.dist-info/entry_points.txt,sha256=R2VtGrLXlUtVANoLClaoCkonw5EFJAXlcCvqaueKr9E,53
|
|
10
|
+
cfcal-0.1.6.dist-info/top_level.txt,sha256=DfZXDVdEmcIWp6aYS7LW8Q89PZl8Llx47Z6NvV2NIGw,6
|
|
11
|
+
cfcal-0.1.6.dist-info/RECORD,,
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
Licensing Terms for NeXpy
|
|
2
|
+
-------------------------
|
|
3
|
+
|
|
4
|
+
NeXpy is licensed under the terms of the Modified BSD License (also known as
|
|
5
|
+
New or Revised BSD), as follows:
|
|
6
|
+
|
|
7
|
+
Copyright (c) 2017, Ray Osborn
|
|
8
|
+
|
|
9
|
+
All rights reserved.
|
|
10
|
+
|
|
11
|
+
Redistribution and use in source and binary forms, with or without
|
|
12
|
+
modification, are permitted provided that the following conditions are met:
|
|
13
|
+
|
|
14
|
+
Redistributions of source code must retain the above copyright notice, this
|
|
15
|
+
list of conditions and the following disclaimer.
|
|
16
|
+
|
|
17
|
+
Redistributions in binary form must reproduce the above copyright notice, this
|
|
18
|
+
list of conditions and the following disclaimer in the documentation and/or
|
|
19
|
+
other materials provided with the distribution.
|
|
20
|
+
|
|
21
|
+
Neither the name of the NeXpy Development Team nor the names of its
|
|
22
|
+
contributors may be used to endorse or promote products derived from this
|
|
23
|
+
software without specific prior written permission.
|
|
24
|
+
|
|
25
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
26
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
27
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
28
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
|
29
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
30
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
31
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
32
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
33
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
34
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
35
|
+
|
|
36
|
+
Copyright
|
|
37
|
+
---------
|
|
38
|
+
|
|
39
|
+
The following banner should be used in any source code file to indicate the
|
|
40
|
+
copyright and license terms:
|
|
41
|
+
|
|
42
|
+
#-----------------------------------------------------------------------------
|
|
43
|
+
# Copyright (c) 2017, Ray Osborn.
|
|
44
|
+
#
|
|
45
|
+
# Distributed under the terms of the Modified BSD License.
|
|
46
|
+
#
|
|
47
|
+
# The full license is in the file COPYING, distributed with this software.
|
|
48
|
+
#-----------------------------------------------------------------------------
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
cfcal
|