orGUI 1.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.
- orGUI-1.0.1.dist-info/LICENSE +21 -0
- orGUI-1.0.1.dist-info/METADATA +147 -0
- orGUI-1.0.1.dist-info/RECORD +83 -0
- orGUI-1.0.1.dist-info/WHEEL +5 -0
- orGUI-1.0.1.dist-info/entry_points.txt +2 -0
- orGUI-1.0.1.dist-info/top_level.txt +1 -0
- orgui/__init__.py +36 -0
- orgui/app/ArrayTableDialog.py +433 -0
- orgui/app/QReflectionSelector.py +538 -0
- orgui/app/QScanSelector.py +692 -0
- orgui/app/QUBCalculator.py +1210 -0
- orgui/app/__init__.py +36 -0
- orgui/app/database.py +487 -0
- orgui/app/orGUI.py +2613 -0
- orgui/app/qutils.py +51 -0
- orgui/backend/__init__.py +32 -0
- orgui/backend/backends.py +157 -0
- orgui/backend/beamline/ID31DiffractLinTilt.py +77 -0
- orgui/backend/beamline/P212_tools.py +577 -0
- orgui/backend/beamline/__init__.py +36 -0
- orgui/backend/beamline/fio_reader.py +110 -0
- orgui/backend/beamline/id31_tools.py +651 -0
- orgui/backend/scans.py +95 -0
- orgui/backend/udefaults.py +163 -0
- orgui/backend/universalScanLoader.py +105 -0
- orgui/datautils/__init__.py +32 -0
- orgui/datautils/util.py +705 -0
- orgui/datautils/xrayutils/CTRcalc.py +3022 -0
- orgui/datautils/xrayutils/CTRopt.py +623 -0
- orgui/datautils/xrayutils/CTRplotutil.py +904 -0
- orgui/datautils/xrayutils/DetectorCalibration.py +685 -0
- orgui/datautils/xrayutils/HKLVlieg.py +1360 -0
- orgui/datautils/xrayutils/ReciprocalNavigation.py +401 -0
- orgui/datautils/xrayutils/_CTRcalc_accel.py +181 -0
- orgui/datautils/xrayutils/__init__.py +46 -0
- orgui/datautils/xrayutils/element_data.py +213 -0
- orgui/datautils/xrayutils/test/__init__.py +57 -0
- orgui/datautils/xrayutils/test/test_CTRcalc.py +152 -0
- orgui/datautils/xrayutils/test/test_DetectorCalibration.py +336 -0
- orgui/datautils/xrayutils/test/test_HKLcalc.py +88 -0
- orgui/datautils/xrayutils/unitcells/Fe3O4(100).bul +59 -0
- orgui/datautils/xrayutils/unitcells/Pt100.bul +7 -0
- orgui/datautils/xrayutils/unitcells/Pt100_small.bul +5 -0
- orgui/datautils/xrayutils/unitcells/Pt110.bul +5 -0
- orgui/datautils/xrayutils/unitcells/Pt111.bul +6 -0
- orgui/datautils/xrayutils/unitcells/Pt310.bul +13 -0
- orgui/datautils/xrayutils/unitcells/Pt3O4(100).bul +19 -0
- orgui/datautils/xrayutils/unitcells/PtO(001).bul +9 -0
- orgui/datautils/xrayutils/unitcells/PtO(010).bul +9 -0
- orgui/datautils/xrayutils/unitcells/PtO(100).bul +9 -0
- orgui/datautils/xrayutils/unitcells/__init__.py +67 -0
- orgui/datautils/xrayutils/unitcells/a-PtO2(0001).bul +6 -0
- orgui/main.py +101 -0
- orgui/resources/__init__.py +40 -0
- orgui/resources/icons/alpha.png +0 -0
- orgui/resources/icons/alpha.svg +67 -0
- orgui/resources/icons/diffractometer_v3.png +0 -0
- orgui/resources/icons/disable-image.png +0 -0
- orgui/resources/icons/disable-image.svg +68 -0
- orgui/resources/icons/document-nx-open.png +0 -0
- orgui/resources/icons/document-nx-open.svg +152 -0
- orgui/resources/icons/document-nx-save.png +0 -0
- orgui/resources/icons/document-nx-save.svg +73 -0
- orgui/resources/icons/logo.png +0 -0
- orgui/resources/icons/logo.svg +808 -0
- orgui/resources/icons/max_image.png +0 -0
- orgui/resources/icons/max_image.svg +77 -0
- orgui/resources/icons/max_image2.png +0 -0
- orgui/resources/icons/max_image2.svg +83 -0
- orgui/resources/icons/search-image.png +0 -0
- orgui/resources/icons/search-image.svg +94 -0
- orgui/resources/icons/search-reflection.png +0 -0
- orgui/resources/icons/search-reflection.svg +126 -0
- orgui/resources/icons/search.png +0 -0
- orgui/resources/icons/search.svg +91 -0
- orgui/resources/icons/select-image.png +0 -0
- orgui/resources/icons/select-image.svg +60 -0
- orgui/resources/icons/set-reflection.png +0 -0
- orgui/resources/icons/set-reflection.svg +91 -0
- orgui/resources/icons/sum_image.png +0 -0
- orgui/resources/icons/sum_image.svg +63 -0
- orgui/resources/icons/sum_image2.png +0 -0
- orgui/resources/icons/sum_image2.svg +75 -0
|
@@ -0,0 +1,1360 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# /*##########################################################################
|
|
3
|
+
#
|
|
4
|
+
# Copyright (c) 2020-2024 Timo Fuchs
|
|
5
|
+
#
|
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
# in the Software without restriction, including without limitation the rights
|
|
9
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
# furnished to do so, subject to the following conditions:
|
|
12
|
+
#
|
|
13
|
+
# The above copyright notice and this permission notice shall be included in
|
|
14
|
+
# all copies or substantial portions of the Software.
|
|
15
|
+
#
|
|
16
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
22
|
+
# THE SOFTWARE.
|
|
23
|
+
#
|
|
24
|
+
# ###########################################################################*/
|
|
25
|
+
__author__ = "Timo Fuchs"
|
|
26
|
+
__copyright__ = "Copyright 2020-2024 Timo Fuchs"
|
|
27
|
+
__license__ = "MIT License"
|
|
28
|
+
__version__ = "1.0.0"
|
|
29
|
+
__maintainer__ = "Timo Fuchs"
|
|
30
|
+
__email__ = "fuchs@physik.uni-kiel.de"
|
|
31
|
+
|
|
32
|
+
import numpy as np
|
|
33
|
+
import numpy.linalg as LA
|
|
34
|
+
from .. import util
|
|
35
|
+
import scipy.optimize as opt
|
|
36
|
+
import warnings
|
|
37
|
+
from scipy.spatial.transform import Rotation
|
|
38
|
+
|
|
39
|
+
# needs to be multiplied with K = 2*pi/lambda to get Qphi
|
|
40
|
+
def calculate_q_phi(pos,K=1.):
|
|
41
|
+
[ALPHA, DELTA, GAMMA, OMEGA, CHI, PHI] = createVliegMatrices(pos)
|
|
42
|
+
|
|
43
|
+
u1a = (DELTA @ GAMMA - ALPHA.T) @ np.array([0.,K,0.])
|
|
44
|
+
u1p = PHI.T @ CHI.T @ OMEGA.T @ u1a
|
|
45
|
+
return u1p
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
"""
|
|
49
|
+
pos = [ALPHA, DELTA, GAMMA, OMEGA, CHI, PHI] (angles)
|
|
50
|
+
accepts also 1d-arrays of the angles in pos
|
|
51
|
+
"""
|
|
52
|
+
def createVliegMatrices(pos):
|
|
53
|
+
|
|
54
|
+
ALPHA = None if pos[0] is None else calcALPHA(pos[0])
|
|
55
|
+
DELTA = None if pos[1] is None else calcDELTA(pos[1])
|
|
56
|
+
GAMMA = None if pos[2] is None else calcGAMMA(pos[2])
|
|
57
|
+
OMEGA = None if pos[3] is None else calcOMEGA(pos[3])
|
|
58
|
+
CHI = None if pos[4] is None else calcCHI(pos[4])
|
|
59
|
+
PHI = None if pos[5] is None else calcPHI(pos[5])
|
|
60
|
+
return ALPHA, DELTA, GAMMA, OMEGA, CHI, PHI
|
|
61
|
+
|
|
62
|
+
def calcALPHA(alpha): # alpha = mu if GID geometry and there is no miscut
|
|
63
|
+
if isinstance(alpha,np.ndarray):
|
|
64
|
+
return util.x_rotationArray(alpha)
|
|
65
|
+
return util.x_rotation(alpha)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def calcDELTA(delta):
|
|
69
|
+
if isinstance(delta,np.ndarray):
|
|
70
|
+
return util.z_rotationArray(-delta)
|
|
71
|
+
return util.z_rotation(-delta)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def calcGAMMA(gamma):
|
|
75
|
+
if isinstance(gamma,np.ndarray):
|
|
76
|
+
return util.x_rotationArray(gamma)
|
|
77
|
+
return util.x_rotation(gamma)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def calcOMEGA(omega):
|
|
81
|
+
if isinstance(omega,np.ndarray):
|
|
82
|
+
return util.z_rotationArray(-omega)
|
|
83
|
+
return util.z_rotation(-omega)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def calcCHI(chi):
|
|
87
|
+
if isinstance(chi,np.ndarray):
|
|
88
|
+
return util.y_rotationArray(chi)
|
|
89
|
+
return util.y_rotation(chi)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def calcPHI(phi):
|
|
93
|
+
if isinstance(phi,np.ndarray):
|
|
94
|
+
return util.x_rotationArray(phi)
|
|
95
|
+
return util.x_rotation(phi)
|
|
96
|
+
|
|
97
|
+
def calcSIGMA(sigma):
|
|
98
|
+
if isinstance(sigma,np.ndarray):
|
|
99
|
+
return util.y_rotationArray(sigma)
|
|
100
|
+
return util.y_rotation(sigma)
|
|
101
|
+
|
|
102
|
+
def calcTAU(tau):
|
|
103
|
+
if isinstance(tau,np.ndarray):
|
|
104
|
+
return util.y_rotationArray(-tau)
|
|
105
|
+
return util.y_rotation(-tau)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def primBeamAngles(pos):
|
|
109
|
+
[alpha,delta,gamma,omega,chi,phi] = pos
|
|
110
|
+
gamma_p = np.arcsin( np.cos(alpha)*np.sin(gamma) + np.sin(alpha)*np.cos(delta)*np.cos(gamma) )
|
|
111
|
+
delta_p = np.arcsin( (np.sin(delta)*np.cos(gamma))/np.cos(gamma_p) )
|
|
112
|
+
return [alpha,delta_p,gamma_p,omega,chi,phi]
|
|
113
|
+
|
|
114
|
+
def vliegDiffracAngles(pos_p):
|
|
115
|
+
[alpha,delta_p,gamma_p,omega,chi,phi] = pos_p
|
|
116
|
+
gamma = np.arcsin( np.cos(alpha)*np.sin(gamma_p) - np.sin(alpha)*np.cos(delta_p)*np.cos(gamma_p) )
|
|
117
|
+
delta = np.arcsin( (np.sin(delta_p)*np.cos(gamma_p))/np.cos(gamma) )
|
|
118
|
+
return [alpha,delta,gamma,omega,chi,phi]
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
"""
|
|
122
|
+
transforms alpha and gamma into the crystal frame using
|
|
123
|
+
snellius refraction law
|
|
124
|
+
|
|
125
|
+
angles smaller than 0 are treated as below sample horizon, i.e. no refraction
|
|
126
|
+
|
|
127
|
+
"""
|
|
128
|
+
def crystalAngles(pos,refraction_index):
|
|
129
|
+
pos = np.array(pos)
|
|
130
|
+
if len(pos.shape) == 1:
|
|
131
|
+
pos[0] = crystalAngles_singleArray(pos[0], refraction_index) #np.arccos(np.cos(pos[0]) / refraction_index)
|
|
132
|
+
pos[2] = crystalAngles_singleArray(pos[2], refraction_index) #np.arccos(np.cos(pos[2]) / refraction_index)
|
|
133
|
+
#pos[np.isnan(pos)] = 0.
|
|
134
|
+
else:
|
|
135
|
+
pos[:,0] = crystalAngles_singleArray(pos[:,0], refraction_index) #np.arccos(np.cos(pos[:,0]) / refraction_index)
|
|
136
|
+
pos[:,2] = crystalAngles_singleArray(pos[:,2], refraction_index) #np.arccos(np.cos(pos[:,2]) / refraction_index)
|
|
137
|
+
#pos[np.isnan(pos)] = 0.
|
|
138
|
+
return pos
|
|
139
|
+
|
|
140
|
+
def crystalAngles_singleArray(angle,refraction_index):
|
|
141
|
+
if isinstance(angle,np.ndarray):
|
|
142
|
+
sign = np.sign(angle)
|
|
143
|
+
mask = sign > 0.
|
|
144
|
+
angle[mask] = np.arccos(np.cos(angle[mask]) / refraction_index)
|
|
145
|
+
angle[np.isnan(angle)] = 0.
|
|
146
|
+
#angle *= sign
|
|
147
|
+
else:
|
|
148
|
+
sign = np.sign(angle)
|
|
149
|
+
if sign < 0.:
|
|
150
|
+
return angle
|
|
151
|
+
angle = np.arccos(np.cos(angle) / refraction_index)
|
|
152
|
+
if np.isnan(angle):
|
|
153
|
+
angle = 0.
|
|
154
|
+
return angle
|
|
155
|
+
|
|
156
|
+
def vacAngles(pos,refraction_index):
|
|
157
|
+
pos = np.array(pos)
|
|
158
|
+
if len(pos.shape) == 1:
|
|
159
|
+
pos[0] = vacAngles_singleArray(pos[0], refraction_index) #np.arccos(np.cos(pos[0]) * refraction_index)
|
|
160
|
+
pos[2] = vacAngles_singleArray(pos[2], refraction_index) #np.arccos(np.cos(pos[2]) * refraction_index)
|
|
161
|
+
#pos[np.isnan(pos)] = 0.
|
|
162
|
+
else:
|
|
163
|
+
pos[:,0] = vacAngles_singleArray(pos[:,0], refraction_index) #np.arccos(np.cos(pos[:,0]) * refraction_index)
|
|
164
|
+
pos[:,2] = vacAngles_singleArray(pos[:,2], refraction_index) #np.arccos(np.cos(pos[:,2]) * refraction_index)
|
|
165
|
+
#pos[np.isnan(pos)] = 0.
|
|
166
|
+
return pos
|
|
167
|
+
|
|
168
|
+
def vacAngles_singleArray(angle,refraction_index):
|
|
169
|
+
if isinstance(angle,np.ndarray):
|
|
170
|
+
sign = np.sign(angle)
|
|
171
|
+
mask = sign > 0.
|
|
172
|
+
angle[mask] = np.arccos(np.cos(angle[mask]) * refraction_index)
|
|
173
|
+
angle = np.nan_to_num(angle,nan=0.0)
|
|
174
|
+
#angle *= sign
|
|
175
|
+
else:
|
|
176
|
+
sign = np.sign(angle)
|
|
177
|
+
if sign < 0.:
|
|
178
|
+
return angle
|
|
179
|
+
angle = np.arccos(np.cos(angle) * refraction_index)
|
|
180
|
+
angle = np.nan_to_num(angle,nan=0.0)
|
|
181
|
+
#angle *= sign
|
|
182
|
+
return angle
|
|
183
|
+
|
|
184
|
+
def printPos(pos,phichi=False):
|
|
185
|
+
pos = np.rad2deg(pos)
|
|
186
|
+
if phichi:
|
|
187
|
+
print("alp=%.2f, del=%.2f, gam=%.2f, om=%.2f, phi=%.2f, chi=%.2f" % tuple(pos))
|
|
188
|
+
else:
|
|
189
|
+
print("alp=%.2f, del=%.2f, gam=%.2f, om=%.2f" % tuple(np.array((pos))[:-2]) )
|
|
190
|
+
|
|
191
|
+
def strPos(pos,phichi=False):
|
|
192
|
+
pos = np.rad2deg(pos)
|
|
193
|
+
if phichi:
|
|
194
|
+
spos = "alp=%.2f, del=%.2f, gam=%.2f, om=%.2f, phi=%.2f, chi=%.2f" % tuple(pos)
|
|
195
|
+
else:
|
|
196
|
+
spos = "alp=%.2f, del=%.2f, gam=%.2f, om=%.2f" % tuple(np.array((pos))[:-2])
|
|
197
|
+
return spos
|
|
198
|
+
|
|
199
|
+
def strPos_prim(pos,phichi=False):
|
|
200
|
+
spos = "Vlieg: "
|
|
201
|
+
spos += strPos(pos,phichi)
|
|
202
|
+
spos += " pb:"
|
|
203
|
+
spos += strPos(primBeamAngles(pos),phichi)
|
|
204
|
+
return spos
|
|
205
|
+
|
|
206
|
+
def printPos_prim(pos,phichi=False):
|
|
207
|
+
print("Vlieg angles:")
|
|
208
|
+
printPos(pos,phichi)
|
|
209
|
+
print("angles ref prim beam")
|
|
210
|
+
printPos(primBeamAngles(pos),phichi)
|
|
211
|
+
|
|
212
|
+
def spec_pa(ub):
|
|
213
|
+
print(str(ub.lattice))
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
"""
|
|
217
|
+
|
|
218
|
+
"""
|
|
219
|
+
|
|
220
|
+
class Lattice(object):
|
|
221
|
+
"""The coordiante system of the lattice is defined by first calculating the\
|
|
222
|
+
reciprocal lattice's vector lengths and angles. After that, the\
|
|
223
|
+
definition of the reciprocal lattice coordiante system as in\
|
|
224
|
+
Busing, W. R. & Levy, H. A. Angle calculations for 3- and 4-circle\
|
|
225
|
+
X-ray and neutron diffractometers. Acta Crystallogr. 22, 457–464 (1967).\
|
|
226
|
+
is used:
|
|
227
|
+
|
|
228
|
+
The x axis parallel to b1,
|
|
229
|
+
the y axis in the plane of bl and b2,
|
|
230
|
+
and the z axis perpendicular to that plane
|
|
231
|
+
|
|
232
|
+
The factor 2pi is included in the reciprocal lattice vectors.
|
|
233
|
+
"""
|
|
234
|
+
def __init__(self,a,alpha):
|
|
235
|
+
self.setLattice(a,alpha)
|
|
236
|
+
|
|
237
|
+
def setLattice(self,a,alpha):
|
|
238
|
+
"""Sets real space lattice from lengths and angles of lattice vectors.
|
|
239
|
+
|
|
240
|
+
:param np.ndarray a: lattice vector lengths in Angstrom
|
|
241
|
+
:param np.ndarray alpha: angles between lattice vectors in degrees
|
|
242
|
+
"""
|
|
243
|
+
self._alpha = np.deg2rad(alpha)
|
|
244
|
+
self._a = np.asarray(a)
|
|
245
|
+
self._b, self._beta = Lattice._calcReciprocalLattice(self._a, self._alpha)
|
|
246
|
+
self.volume = (np.prod(self._a) *
|
|
247
|
+
np.sqrt(1 + 2 * np.cos(self._alpha[0]) * np.cos(self._alpha[1]) * np.cos(self._alpha[2]) -
|
|
248
|
+
np.cos(self._alpha[0]) ** 2 - np.cos(self._alpha[1]) ** 2 - np.cos(self._alpha[2]) ** 2))
|
|
249
|
+
self.uc_area = self._a[0]*self._a[1]*np.sin(self._alpha[2]) #unit cell area
|
|
250
|
+
|
|
251
|
+
self.B_mat = Lattice.reciprocalMatrix(self._a,self._alpha,self._b,self._beta)
|
|
252
|
+
self.B_mat_inv = LA.inv(self.B_mat)
|
|
253
|
+
self.R_mat = 2*np.pi * np.ascontiguousarray(self.B_mat_inv.T)
|
|
254
|
+
self.RealspaceMatrix = self.R_mat # old: Lattice.realspaceMatrix(self._a,self._alpha,self._b,self._beta)
|
|
255
|
+
self.R_mat_inv = LA.inv(self.R_mat)
|
|
256
|
+
|
|
257
|
+
#def setReciprocalLattice(self, b, beta):
|
|
258
|
+
"""Sets reciprocal space lattice.
|
|
259
|
+
|
|
260
|
+
:param np.ndarray b: reciprocal lattice vector lengths in Angstrom-1. Must include the factor 2pi.
|
|
261
|
+
:param np.ndarray beta: angles between lattice vectors in degrees
|
|
262
|
+
"""
|
|
263
|
+
|
|
264
|
+
@property
|
|
265
|
+
def a(self):
|
|
266
|
+
return self._a
|
|
267
|
+
|
|
268
|
+
@a.setter
|
|
269
|
+
def a(self, a):
|
|
270
|
+
self._a = np.asarray(a)
|
|
271
|
+
self._b, self._beta = Lattice._calcReciprocalLattice(self._a, self._alpha)
|
|
272
|
+
self.volume = (np.prod(self._a) *
|
|
273
|
+
np.sqrt(1 + 2 * np.cos(self._alpha[0]) * np.cos(self._alpha[1]) * np.cos(self._alpha[2]) -
|
|
274
|
+
np.cos(self._alpha[0]) ** 2 - np.cos(self._alpha[1]) ** 2 - np.cos(self._alpha[2]) ** 2))
|
|
275
|
+
|
|
276
|
+
self.uc_area = self._a[0]*self._a[1]*np.sin(self._alpha[2]) #unit cell area
|
|
277
|
+
|
|
278
|
+
self.B_mat = Lattice.reciprocalMatrix(self._a,self._alpha,self._b,self._beta)
|
|
279
|
+
self.B_mat_inv = LA.inv(self.B_mat)
|
|
280
|
+
self.R_mat = 2*np.pi * np.ascontiguousarray(self.B_mat_inv.T)
|
|
281
|
+
self.RealspaceMatrix = self.R_mat # old: Lattice.realspaceMatrix(self._a,self._alpha,self._b,self._beta)
|
|
282
|
+
self.R_mat_inv = LA.inv(self.R_mat)
|
|
283
|
+
|
|
284
|
+
@property
|
|
285
|
+
def alpha(self):
|
|
286
|
+
"""Angle between real space lattice vectors.
|
|
287
|
+
|
|
288
|
+
in radians.
|
|
289
|
+
"""
|
|
290
|
+
return self._alpha
|
|
291
|
+
|
|
292
|
+
@alpha.setter
|
|
293
|
+
def alpha(self, alpha):
|
|
294
|
+
self._alpha = np.asarray(alpha)
|
|
295
|
+
self._b, self._beta = Lattice._calcReciprocalLattice(self._a, self._alpha)
|
|
296
|
+
self.volume = (np.prod(self._a) *
|
|
297
|
+
np.sqrt(1 + 2 * np.cos(self._alpha[0]) * np.cos(self._alpha[1]) * np.cos(self._alpha[2]) -
|
|
298
|
+
np.cos(self._alpha[0]) ** 2 - np.cos(self._alpha[1]) ** 2 - np.cos(self._alpha[2]) ** 2))
|
|
299
|
+
self.uc_area = self._a[0]*self._a[1]*np.sin(self._alpha[2]) #unit cell area
|
|
300
|
+
|
|
301
|
+
self.B_mat = Lattice.reciprocalMatrix(self._a,self._alpha,self._b,self._beta)
|
|
302
|
+
self.B_mat_inv = LA.inv(self.B_mat)
|
|
303
|
+
self.R_mat = 2*np.pi * np.ascontiguousarray(self.B_mat_inv.T)
|
|
304
|
+
self.RealspaceMatrix = self.R_mat # old: Lattice.realspaceMatrix(self._a,self._alpha,self._b,self._beta)
|
|
305
|
+
self.R_mat_inv = LA.inv(self.R_mat)
|
|
306
|
+
|
|
307
|
+
@property
|
|
308
|
+
def beta(self):
|
|
309
|
+
"""Angle between reciprocal lattice vectors.
|
|
310
|
+
|
|
311
|
+
in radians.
|
|
312
|
+
"""
|
|
313
|
+
return self._beta
|
|
314
|
+
|
|
315
|
+
@property
|
|
316
|
+
def b(self):
|
|
317
|
+
return self._b
|
|
318
|
+
|
|
319
|
+
def getLatticeParameters(self):
|
|
320
|
+
"""Legacy. Use Lattice.a and Lattice.alpha instead
|
|
321
|
+
"""
|
|
322
|
+
return self._a, self._alpha, self._b, self._beta
|
|
323
|
+
|
|
324
|
+
def getB(self):
|
|
325
|
+
"""Legacy. Use Lattice.B_mat instead
|
|
326
|
+
"""
|
|
327
|
+
return self.B_mat
|
|
328
|
+
|
|
329
|
+
#in rad, shape of hkl must be either (3,) or (3,n)
|
|
330
|
+
def get2ThetaFromHKL(self,hkl,energy):
|
|
331
|
+
"""Calculates scattering angle 2theta for given hkl.
|
|
332
|
+
|
|
333
|
+
From |B_mat @ H| = |Q| = (4pi / lambda) sin(theta)
|
|
334
|
+
"""
|
|
335
|
+
wavelength = 12.39842 / energy
|
|
336
|
+
hkl = np.array(hkl)
|
|
337
|
+
if len(hkl.shape) == 1:
|
|
338
|
+
G = LA.norm(self.reciprocalVectorCart(hkl).T)
|
|
339
|
+
else:
|
|
340
|
+
G = LA.norm(self.reciprocalVectorCart(hkl).T,axis=1)
|
|
341
|
+
return 2*np.arcsin((G*wavelength)/(4*np.pi))
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
# calculates atomic positions from fractional coordinates xyz_frac
|
|
345
|
+
# to cartesian coordinates in Angstroms
|
|
346
|
+
def directVectorCart(self,xyz_frac):
|
|
347
|
+
"""Calculates real space vector in cartesian coordinates in crystal frame\
|
|
348
|
+
from fractional coordinates xyz_frac.
|
|
349
|
+
|
|
350
|
+
See definition of the reciprocalMatrix B_mat. The
|
|
351
|
+
|
|
352
|
+
in Angstrom
|
|
353
|
+
"""
|
|
354
|
+
return self.R_mat @ np.asarray(xyz_frac).T
|
|
355
|
+
|
|
356
|
+
def reciprocalVectorCart(self,hkl):
|
|
357
|
+
"""Calculates reciprocal vector in cartesian coordinates in crystal frame\
|
|
358
|
+
from reciprocal lattice units hkl.
|
|
359
|
+
|
|
360
|
+
in Angstrom-1
|
|
361
|
+
"""
|
|
362
|
+
return self.B_mat @ np.asarray(hkl).T
|
|
363
|
+
|
|
364
|
+
# calculates reciprocal vector in cartesian coordinates
|
|
365
|
+
# from lattice units hkl
|
|
366
|
+
def getReciprocalVectorCart(self,hkl):
|
|
367
|
+
"""deprecated. use reciprocalVectorCart instead.
|
|
368
|
+
"""
|
|
369
|
+
warnings.warn("getReciprocalVectorCart is deprecated. Use reciprocalVectorCart instead.", FutureWarning)
|
|
370
|
+
return self.B_mat @ np.asarray(hkl).T
|
|
371
|
+
|
|
372
|
+
@staticmethod
|
|
373
|
+
def _calcReciprocalLattice(a, alpha):
|
|
374
|
+
beta = np.empty(3)
|
|
375
|
+
|
|
376
|
+
beta[0] = np.arccos((np.cos(alpha[1]) * np.cos(alpha[2]) - np.cos(alpha[0])) /
|
|
377
|
+
(np.sin(alpha[1]) * np.sin(alpha[2])))
|
|
378
|
+
|
|
379
|
+
beta[1] = np.arccos((np.cos(alpha[0]) * np.cos(alpha[2]) - np.cos(alpha[1])) /
|
|
380
|
+
(np.sin(alpha[0]) * np.sin(alpha[2])))
|
|
381
|
+
|
|
382
|
+
beta[2] = np.arccos((np.cos(alpha[0]) * np.cos(alpha[1]) - np.cos(alpha[2])) /
|
|
383
|
+
(np.sin(alpha[0]) * np.sin(alpha[1])))
|
|
384
|
+
|
|
385
|
+
b = np.empty(3)
|
|
386
|
+
|
|
387
|
+
volume = (np.prod(a) *
|
|
388
|
+
np.sqrt(1 + 2 * np.cos(alpha[0]) * np.cos(alpha[1]) * np.cos(alpha[2]) -
|
|
389
|
+
np.cos(alpha[0]) ** 2 - np.cos(alpha[1]) ** 2 - np.cos(alpha[2]) ** 2))
|
|
390
|
+
|
|
391
|
+
b[0] = 2 * np.pi * a[1] * a[2] * np.sin(alpha[0]) / volume
|
|
392
|
+
b[1] = 2 * np.pi * a[0] * a[2] * np.sin(alpha[1]) / volume
|
|
393
|
+
b[2] = 2 * np.pi * a[0] * a[1] * np.sin(alpha[2]) / volume
|
|
394
|
+
|
|
395
|
+
return b, beta
|
|
396
|
+
|
|
397
|
+
@staticmethod
|
|
398
|
+
def reciprocalMatrix(a,alpha,b,beta):
|
|
399
|
+
"""Creates a matrix that transforms reciprocal lattice coordinates into cartesian coordinates.
|
|
400
|
+
h along x, k
|
|
401
|
+
|
|
402
|
+
"""
|
|
403
|
+
return np.array([(b[0], b[1]*np.cos(beta[2]), b[2]*np.cos(beta[1])),
|
|
404
|
+
(0., b[1]*np.sin(beta[2]), -b[2]*np.sin(beta[1])*np.cos(alpha[0])),
|
|
405
|
+
(0., 0., 2.*np.pi/a[2])])
|
|
406
|
+
@staticmethod
|
|
407
|
+
def realspaceMatrix(a,alpha,b,beta):
|
|
408
|
+
"""Creates a matrix that transforms lattice coordinates into cartesian coordinates.
|
|
409
|
+
|
|
410
|
+
legacy version! This matrix is not the reciprocal to reciprocalMatrix!
|
|
411
|
+
|
|
412
|
+
"""
|
|
413
|
+
return np.array([(a[0], a[1]*np.cos(alpha[2]), a[2]*np.cos(alpha[1])),
|
|
414
|
+
(0., a[1]*np.sin(alpha[2]), -a[2]*np.sin(alpha[1])*np.cos(beta[0])),
|
|
415
|
+
(0., 0., 2.*np.pi/b[2])])
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def __str__(self):
|
|
420
|
+
name = "Lattice:\nReal space: \t" + str(self._a) + " / " + str(np.rad2deg(self._alpha)) + "\n"
|
|
421
|
+
name += "Reciprocal space: \t" + str(self._b) + " / " + str(np.rad2deg(self._beta))
|
|
422
|
+
return name
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
def __repr__(self):
|
|
426
|
+
name = "Lattice:\nReal space: \t" + str(self._a) + " / " + str(np.rad2deg(self._alpha)) + "\n"
|
|
427
|
+
name += "Reciprocal space: \t" + str(self._b) + " / " + str(np.rad2deg(self._beta))
|
|
428
|
+
return name
|
|
429
|
+
|
|
430
|
+
class Crystal(Lattice):
|
|
431
|
+
def __init__(self, *args, **kwargs):
|
|
432
|
+
warnings.warn("Crystal is deprecated. Use Lattice instead.", FutureWarning)
|
|
433
|
+
super().__init__(*args, **kwargs)
|
|
434
|
+
|
|
435
|
+
class UBCalculator():
|
|
436
|
+
|
|
437
|
+
def __init__(self,lattice, energy):
|
|
438
|
+
self._U = None
|
|
439
|
+
self._UB = None
|
|
440
|
+
self.setLattice(lattice)
|
|
441
|
+
self.setEnergy(energy)
|
|
442
|
+
|
|
443
|
+
def setLattice(self, lattice):
|
|
444
|
+
self.lattice = lattice
|
|
445
|
+
if self._U is not None:
|
|
446
|
+
self._UB = self._U @ self.lattice.B_mat
|
|
447
|
+
|
|
448
|
+
def setEnergy(self,energy):
|
|
449
|
+
"""in keV
|
|
450
|
+
|
|
451
|
+
"""
|
|
452
|
+
self._energy = energy
|
|
453
|
+
self._lambda = 12.39842 / energy
|
|
454
|
+
self._K = (2*np.pi)/self._lambda
|
|
455
|
+
|
|
456
|
+
def setLambda(self,lmbda):
|
|
457
|
+
"""in Angstrom
|
|
458
|
+
|
|
459
|
+
"""
|
|
460
|
+
self._energy = 12.39842 / lmbda
|
|
461
|
+
self._lambda = lmbda
|
|
462
|
+
self._K = (2*np.pi)/self._lambda
|
|
463
|
+
|
|
464
|
+
def getLattice(self):
|
|
465
|
+
"""depricated. use UBCalculator.lattice instead.
|
|
466
|
+
"""
|
|
467
|
+
return self.lattice
|
|
468
|
+
|
|
469
|
+
def getEnergy(self):
|
|
470
|
+
return self._energy
|
|
471
|
+
|
|
472
|
+
def getLambda(self):
|
|
473
|
+
return self._lambda
|
|
474
|
+
|
|
475
|
+
def getK(self):
|
|
476
|
+
return self._K
|
|
477
|
+
|
|
478
|
+
def setPrimaryReflection(self,pos,hkl):
|
|
479
|
+
self._primary = (pos,hkl)
|
|
480
|
+
|
|
481
|
+
def setSecondayReflection(self,pos,hkl):
|
|
482
|
+
self._secondary = (pos,hkl)
|
|
483
|
+
|
|
484
|
+
# l in z-direction
|
|
485
|
+
def defaultU(self):
|
|
486
|
+
"""For compatibility of legacy code. Use one of the explicit defaults for future developments instead.
|
|
487
|
+
"""
|
|
488
|
+
self.defaultU_GID()
|
|
489
|
+
"""
|
|
490
|
+
TwoTheta1 = self.lattice.get2ThetaFromHKL([0,0,1],self._energy)
|
|
491
|
+
TwoTheta2 = self.lattice.get2ThetaFromHKL([0,1,0],self._energy)
|
|
492
|
+
self.setPrimaryReflection([TwoTheta1/2.,0.,TwoTheta1/2.,0,0.,0.],[0,0,1])
|
|
493
|
+
self.setSecondayReflection([0.,TwoTheta2,0.,0.,0.,0.],[0,1,0])
|
|
494
|
+
self.calculateU()
|
|
495
|
+
"""
|
|
496
|
+
|
|
497
|
+
def alignU_lab(self, hkl, pos, orientation):
|
|
498
|
+
"""Rotate current U matrix in the shortest way,
|
|
499
|
+
so that the reflection ´hkl´ with the angles ´pos´
|
|
500
|
+
points along ´orientation´ in lab coordinates.
|
|
501
|
+
|
|
502
|
+
:param hkl np.ndarray: Miller indices of reflection
|
|
503
|
+
:param pos np.ndarray: sixc diffractometer angles [ALPHA, DELTA, GAMMA, OMEGA, CHI, PHI] in rad
|
|
504
|
+
:param orientation np.ndarray: direction to rotate in laboratory coordinates
|
|
505
|
+
"""
|
|
506
|
+
ALPHA, _, _, OMEGA, CHI, PHI = createVliegMatrices(pos)
|
|
507
|
+
|
|
508
|
+
Vlab = ALPHA @ OMEGA @ CHI @ PHI
|
|
509
|
+
Qphi = np.asarray(self.getUB()) @ hkl
|
|
510
|
+
Qlab = Vlab @ Qphi
|
|
511
|
+
|
|
512
|
+
Qlab_norm = Qlab / np.linalg.norm(Qlab)
|
|
513
|
+
orientation_norm = orientation / np.linalg.norm(orientation)
|
|
514
|
+
|
|
515
|
+
rotation = util.rotation_matrix_from_vectors(Qlab_norm, orientation_norm)
|
|
516
|
+
|
|
517
|
+
Unew = Vlab.T @ rotation @ Vlab @ np.asarray(self.getU())
|
|
518
|
+
|
|
519
|
+
self.setU(Unew)
|
|
520
|
+
|
|
521
|
+
def alignU_alpha(self, hkl, pos, orientation):
|
|
522
|
+
"""Rotate current U matrix in the shortest way,
|
|
523
|
+
so that the reflection ´hkl´ with the angles ´pos´
|
|
524
|
+
points along ´orientation´ in lab coordinates.
|
|
525
|
+
|
|
526
|
+
:param hkl np.ndarray: Miller indices of reflection
|
|
527
|
+
:param pos np.ndarray: sixc diffractometer angles [ALPHA, DELTA, GAMMA, OMEGA, CHI, PHI] in rad
|
|
528
|
+
:param orientation np.ndarray: direction to rotate in laboratory coordinates
|
|
529
|
+
"""
|
|
530
|
+
_, _, _, OMEGA, CHI, PHI = createVliegMatrices(pos)
|
|
531
|
+
|
|
532
|
+
Valp = OMEGA @ CHI @ PHI
|
|
533
|
+
Qphi = np.asarray(self.getUB()) @ hkl
|
|
534
|
+
Qalp = Valp @ Qphi
|
|
535
|
+
|
|
536
|
+
Qalp_norm = Qalp / np.linalg.norm(Qalp)
|
|
537
|
+
orientation_norm = orientation / np.linalg.norm(orientation)
|
|
538
|
+
|
|
539
|
+
rotation = util.rotation_matrix_from_vectors(Qalp_norm, orientation_norm)
|
|
540
|
+
|
|
541
|
+
Unew = Valp.T @ rotation @ Valp @ np.asarray(self.getU())
|
|
542
|
+
|
|
543
|
+
self.setU(Unew)
|
|
544
|
+
|
|
545
|
+
def defaultU_GID(self):
|
|
546
|
+
"""Sets a default U matrix for Grazing incidence surface diffraction geometry.
|
|
547
|
+
|
|
548
|
+
Geometry:
|
|
549
|
+
L along z axis in alpha frame
|
|
550
|
+
K along x axis in alpha frame
|
|
551
|
+
for chi = 0°, phi = 0°
|
|
552
|
+
|
|
553
|
+
Aligns crystal L axis along omega rotation axis.
|
|
554
|
+
Alpha tilts this rotation axis. Typically alpha is equal to the angle of incidence.
|
|
555
|
+
|
|
556
|
+
"""
|
|
557
|
+
# Compute the two reflections' reciprical lattice vectors in the
|
|
558
|
+
# cartesian crystal frame (hc = B @ hkl)
|
|
559
|
+
h1c = self.lattice.reciprocalVectorCart([0.,0.,1.]).flatten() # for hkl = (0, 0, 1)
|
|
560
|
+
h2c = self.lattice.reciprocalVectorCart([0.,1.,0.]).flatten() # for hkl = (0, 1, 0)
|
|
561
|
+
|
|
562
|
+
# Calculate the sample rotation matrices
|
|
563
|
+
|
|
564
|
+
chi1 = np.deg2rad(0.)
|
|
565
|
+
phi1 = np.deg2rad(0.)
|
|
566
|
+
omega1 = np.deg2rad(0.)
|
|
567
|
+
|
|
568
|
+
chi2 = np.deg2rad(0.)
|
|
569
|
+
phi2 = np.deg2rad(0.)
|
|
570
|
+
omega2 = np.deg2rad(0.)
|
|
571
|
+
|
|
572
|
+
# [ALPHA, DELTA, GAMMA, OMEGA, CHI, PHI]
|
|
573
|
+
pos1 = np.array([None, None, None, omega1, chi1, phi1])
|
|
574
|
+
pos2 = np.array([None, None, None, omega2, chi2, phi2])
|
|
575
|
+
|
|
576
|
+
_,_,_, OM1, CHI1, PHI1 = createVliegMatrices(pos1) # rotation matrices
|
|
577
|
+
_,_,_, OM2, CHI2, PHI2 = createVliegMatrices(pos2) # rotation matrices
|
|
578
|
+
|
|
579
|
+
# define reference directions in alpha frame
|
|
580
|
+
|
|
581
|
+
Qalp1 = np.array([0.,0.,1.]) * np.linalg.norm(h1c) # reference 1: z direction
|
|
582
|
+
Qalp2 = np.array([1.,0.,0.]) * np.linalg.norm(h2c) # reference 2: x direction
|
|
583
|
+
|
|
584
|
+
# Transform Qalp in Qphi
|
|
585
|
+
# hint: matrix inverse of rotation matrices is the transpose (T)
|
|
586
|
+
Qphi1 = PHI1.T @ CHI1.T @ OM1.T @ Qalp1
|
|
587
|
+
Qphi2 = PHI2.T @ CHI2.T @ OM2.T @ Qalp2
|
|
588
|
+
|
|
589
|
+
# 1: primary vector, 2: secondary vector
|
|
590
|
+
|
|
591
|
+
U, stats = UBCalculator.calc_U_from_vectors(Qphi1, Qphi2, h1c, h2c)
|
|
592
|
+
|
|
593
|
+
self.setU(U)
|
|
594
|
+
|
|
595
|
+
def defaultU_TSD(self):
|
|
596
|
+
"""Sets a default U matrix for Transmission Surface Diffraction geometry.
|
|
597
|
+
|
|
598
|
+
Geometry:
|
|
599
|
+
L along y axis in laboratory frame (along beam direction) at omega = 0°
|
|
600
|
+
K along z axis in laboratory frame
|
|
601
|
+
for phi = 90°
|
|
602
|
+
chi = 0°
|
|
603
|
+
|
|
604
|
+
In TSD mode, the crystal L axis points towards the beam direction at one
|
|
605
|
+
specific omega value as described here:
|
|
606
|
+
|
|
607
|
+
Transmission Surface Diffraction for Operando Studies of Heterogeneous Interfaces
|
|
608
|
+
Finn Reikowski, Tim Wiegmann, Jochim Stettner, Jakub Drnec, Veijo Honkimäki, Fouad Maroun, Philippe Allongue, and Olaf M. Magnussen
|
|
609
|
+
The Journal of Physical Chemistry Letters 2017 8 (5), 1067-1071
|
|
610
|
+
DOI: 10.1021/acs.jpclett.7b00332
|
|
611
|
+
|
|
612
|
+
"""
|
|
613
|
+
# Compute the two reflections' reciprical lattice vectors in the
|
|
614
|
+
# cartesian crystal frame (hc = B @ hkl)
|
|
615
|
+
h1c = self.lattice.reciprocalVectorCart([0.,0.,1.]).flatten() # for hkl = (0, 0, 1)
|
|
616
|
+
h2c = self.lattice.reciprocalVectorCart([0.,1.,0.]).flatten() # for hkl = (0, 1, 0)
|
|
617
|
+
|
|
618
|
+
# Calculate the sample rotation matrices
|
|
619
|
+
|
|
620
|
+
chi1 = np.deg2rad(00.)
|
|
621
|
+
phi1 = np.deg2rad(90.)
|
|
622
|
+
omega1 = np.deg2rad(0.)
|
|
623
|
+
alpha1 = np.deg2rad(0.)
|
|
624
|
+
|
|
625
|
+
chi2 = np.deg2rad(0.)
|
|
626
|
+
phi2 = np.deg2rad(90.)
|
|
627
|
+
omega2 = np.deg2rad(0.)
|
|
628
|
+
alpha2 = np.deg2rad(0.)
|
|
629
|
+
|
|
630
|
+
# [ALPHA, DELTA, GAMMA, OMEGA, CHI, PHI]
|
|
631
|
+
pos1 = np.array([alpha1, None, None, omega1, chi1, phi1])
|
|
632
|
+
pos2 = np.array([alpha2, None, None, omega2, chi2, phi2])
|
|
633
|
+
|
|
634
|
+
A1,_,_, OM1, CHI1, PHI1 = createVliegMatrices(pos1) # rotation matrices
|
|
635
|
+
A2,_,_, OM2, CHI2, PHI2 = createVliegMatrices(pos2) # rotation matrices
|
|
636
|
+
|
|
637
|
+
# define reference directions in laboratory frame
|
|
638
|
+
|
|
639
|
+
Qlab1 = np.array([0.,1.,0.]) * np.linalg.norm(h1c) # reference 1: y direction
|
|
640
|
+
Qlab2 = np.array([0.,0.,1.]) * np.linalg.norm(h2c) # reference 2: z direction
|
|
641
|
+
|
|
642
|
+
# Transform Qlab in Qphi
|
|
643
|
+
# hint: matrix inverse of rotation matrices is the transpose (T)
|
|
644
|
+
Qphi1 = PHI1.T @ CHI1.T @ OM1.T @ A1.T @ Qlab1
|
|
645
|
+
Qphi2 = PHI2.T @ CHI2.T @ OM2.T @ A2.T @ Qlab2
|
|
646
|
+
|
|
647
|
+
# 1: primary vector, 2: secondary vector
|
|
648
|
+
|
|
649
|
+
U, stats = UBCalculator.calc_U_from_vectors(Qphi1, Qphi2, h1c, h2c)
|
|
650
|
+
|
|
651
|
+
self.setU(U)
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
def zmodeUSingleRefl(self,pos,hkl):
|
|
655
|
+
"""
|
|
656
|
+
assumes L pointing in z-direction
|
|
657
|
+
|
|
658
|
+
Parameters
|
|
659
|
+
----------
|
|
660
|
+
pos : TYPE
|
|
661
|
+
pos = [ALPHA, DELTA, GAMMA, OMEGA, CHI, PHI] (angles)
|
|
662
|
+
hkl : TYPE
|
|
663
|
+
DESCRIPTION.
|
|
664
|
+
|
|
665
|
+
Returns
|
|
666
|
+
-------
|
|
667
|
+
None.
|
|
668
|
+
|
|
669
|
+
"""
|
|
670
|
+
TwoTheta1 = self.lattice.get2ThetaFromHKL([0,0,1],self._energy)
|
|
671
|
+
self.setPrimaryReflection([TwoTheta1/2.,0.,TwoTheta1/2.,0,0.,0.],[0,0,1])
|
|
672
|
+
self.setSecondayReflection(pos,hkl)
|
|
673
|
+
self.calculateU()
|
|
674
|
+
"""
|
|
675
|
+
U = np.asarray(self.getU())
|
|
676
|
+
U[0,2] = 0
|
|
677
|
+
U[1,2] = 0
|
|
678
|
+
U[2,0] = 0
|
|
679
|
+
U[2,1] = 0
|
|
680
|
+
eigenvalue = np.linalg.eigvals(U) # make matrix unitary
|
|
681
|
+
evreal = eigenvalue[~np.iscomplex(eigenvalue)]
|
|
682
|
+
if len(evreal.shape) > 0:
|
|
683
|
+
evreal = evreal[0]
|
|
684
|
+
U /= np.real(evreal)
|
|
685
|
+
self.setU(U)
|
|
686
|
+
"""
|
|
687
|
+
|
|
688
|
+
@staticmethod
|
|
689
|
+
def calc_U_from_vectors(Q_phi_1, Q_phi_2, Q_c_1, Q_c_2):
|
|
690
|
+
"""Calculate the orientation matrix ´U´ from two reference reflections.
|
|
691
|
+
|
|
692
|
+
Calculates a orthogonal matrix U that closely satisfies:
|
|
693
|
+
|
|
694
|
+
Q_phi_1 = U @ Q_c_1
|
|
695
|
+
Q_phi_2 = U @ Q_c_2
|
|
696
|
+
|
|
697
|
+
The procedure is described in
|
|
698
|
+
Busing, W. R. & Levy, H. A. Angle calculations for 3- and 4-circle X-ray and neutron diffractometers. Acta Crystallogr. 22, 457–464 (1967).
|
|
699
|
+
|
|
700
|
+
Q_phi_1, Q_phi_2 are the momentum transfer vector in the phi frame
|
|
701
|
+
(attached to the diffractometer at all angles = 0)
|
|
702
|
+
|
|
703
|
+
Q_c_1, Q_c_2 are the momentum transfer vectors in the crystal system
|
|
704
|
+
|
|
705
|
+
reflection vectors Q_phi_1, Q_c_1 are the primary vectors, i.e. they define a fixed reference,
|
|
706
|
+
and the secondary vectors Q_phi_2, Q_c_2 only lead to a rotation around the primary vector.
|
|
707
|
+
"""
|
|
708
|
+
|
|
709
|
+
# Normalize vectors as we are only interested in orientations
|
|
710
|
+
|
|
711
|
+
u_phi_1 = Q_phi_1 / np.linalg.norm(Q_phi_1) # could also normalize after doing cross products...
|
|
712
|
+
u_phi_2 = Q_phi_2 / np.linalg.norm(Q_phi_2)
|
|
713
|
+
|
|
714
|
+
h_c_1 = Q_c_1 / np.linalg.norm(Q_c_1)
|
|
715
|
+
h_c_2 = Q_c_2 / np.linalg.norm(Q_c_2)
|
|
716
|
+
|
|
717
|
+
vector_norm_mismatch_1 = np.linalg.norm(Q_phi_1) / np.linalg.norm(Q_c_1)
|
|
718
|
+
vector_norm_mismatch_2 = np.linalg.norm(Q_phi_2) / np.linalg.norm(Q_c_2)
|
|
719
|
+
|
|
720
|
+
# Create modified, orthogonal unit vectors t_c and t_phi in crystal and phi frame, respectively
|
|
721
|
+
|
|
722
|
+
# t_c_1 parallel to h_c_1, t_c_2 lies in the plane of h_c_1 and h_c_2, and t_c_3 is perpendicular to this plane.
|
|
723
|
+
t_c_1 = h_c_1
|
|
724
|
+
t_c_3 = np.cross(h_c_1, h_c_2)
|
|
725
|
+
t_c_2 = np.cross(t_c_3, t_c_1)
|
|
726
|
+
|
|
727
|
+
vector_angle_c = np.arcsin(np.linalg.norm(t_c_3))
|
|
728
|
+
|
|
729
|
+
t_p_1 = u_phi_1
|
|
730
|
+
t_p_3 = np.cross(u_phi_1, u_phi_2)
|
|
731
|
+
t_p_2 = np.cross(t_p_3, t_p_1)
|
|
732
|
+
|
|
733
|
+
vector_angle_p = np.arcsin(np.linalg.norm(t_p_3))
|
|
734
|
+
|
|
735
|
+
vector_angle_mismatch = np.abs(vector_angle_c - vector_angle_p)
|
|
736
|
+
vector_angle = np.mean([vector_angle_c, vector_angle_p])
|
|
737
|
+
|
|
738
|
+
if np.rad2deg(vector_angle) < 5.:
|
|
739
|
+
raise Exception("Angle between reference vectors < 5°, this would result in a bad orientation matrix")
|
|
740
|
+
|
|
741
|
+
t_c_3 /= np.linalg.norm(t_c_3)
|
|
742
|
+
t_c_2 /= np.linalg.norm(t_c_2)
|
|
743
|
+
|
|
744
|
+
t_p_3 /= np.linalg.norm(t_p_3)
|
|
745
|
+
t_p_2 /= np.linalg.norm(t_p_2)
|
|
746
|
+
|
|
747
|
+
Tc = np.column_stack([t_c_1, t_c_2, t_c_3])
|
|
748
|
+
Tp = np.column_stack([t_p_1, t_p_2, t_p_3])
|
|
749
|
+
|
|
750
|
+
U = Tp @ Tc.T
|
|
751
|
+
|
|
752
|
+
stats = {'angle' : vector_angle,
|
|
753
|
+
'angle_mismatch' : vector_angle_mismatch,
|
|
754
|
+
'norm_mismatch_1' : vector_norm_mismatch_1,
|
|
755
|
+
'norm_mismatch_2' : vector_norm_mismatch_2}
|
|
756
|
+
|
|
757
|
+
return U, stats
|
|
758
|
+
|
|
759
|
+
def calculateU(self):
|
|
760
|
+
"""Calculate the orientation matrix ´U´ from the two reference reflections.
|
|
761
|
+
|
|
762
|
+
The procedure is described in
|
|
763
|
+
Busing, W. R. & Levy, H. A. Angle calculations for 3- and 4-circle X-ray and neutron diffractometers. Acta Crystallogr. 22, 457–464 (1967).
|
|
764
|
+
|
|
765
|
+
"""
|
|
766
|
+
ppos, phkl = self._primary
|
|
767
|
+
spos, shkl = self._secondary
|
|
768
|
+
|
|
769
|
+
# Compute the two reflections' reciprical lattice vectors in the
|
|
770
|
+
# cartesian crystal frame (hc = B * hkl)
|
|
771
|
+
h1c = self.lattice.reciprocalVectorCart(phkl).flatten()
|
|
772
|
+
h2c = self.lattice.reciprocalVectorCart(shkl).flatten()
|
|
773
|
+
|
|
774
|
+
# Calculate observed vectors in the phi frame.
|
|
775
|
+
u1p = calculate_q_phi(ppos, self._K).flatten()
|
|
776
|
+
u2p = calculate_q_phi(spos, self._K).flatten()
|
|
777
|
+
|
|
778
|
+
U, stats = UBCalculator.calc_U_from_vectors(u1p, u2p, h1c, h2c)
|
|
779
|
+
|
|
780
|
+
self.setU(U)
|
|
781
|
+
return self._U
|
|
782
|
+
|
|
783
|
+
def getU(self):
|
|
784
|
+
return self._U
|
|
785
|
+
|
|
786
|
+
def getUB(self):
|
|
787
|
+
return self._UB
|
|
788
|
+
|
|
789
|
+
def getUmB(self):
|
|
790
|
+
B = self.lattice.B_mat
|
|
791
|
+
return self._U @ B
|
|
792
|
+
|
|
793
|
+
def setU(self,U):
|
|
794
|
+
U = np.asarray(U).reshape((3,3))
|
|
795
|
+
self._U = np.ascontiguousarray(U)
|
|
796
|
+
self._UB = np.ascontiguousarray(self._U @ self.lattice.getB())
|
|
797
|
+
|
|
798
|
+
# this fits the U matrix and also fits the lattice constants
|
|
799
|
+
# you can either scale the lattice constants equally or fit them
|
|
800
|
+
# individually
|
|
801
|
+
# mode: either 'scale' or 'indivdual'
|
|
802
|
+
def refineULattice(self,hkl,angles,mode='scale',rod=None,factor=100.):
|
|
803
|
+
qphi = []
|
|
804
|
+
for pos in angles:
|
|
805
|
+
qphi.append(calculate_q_phi(pos,self._K).T)
|
|
806
|
+
qphi = np.array(qphi).T
|
|
807
|
+
# p[0], p[1], p[2]: rotation angles
|
|
808
|
+
|
|
809
|
+
weights = np.ones(angles.shape[0])
|
|
810
|
+
|
|
811
|
+
if rod is not None:
|
|
812
|
+
weights[np.all(hkl[:,:2] == rod,axis=1)] = factor
|
|
813
|
+
|
|
814
|
+
#a,alpha,_,_ = self.lattice.getLatticeParameters()
|
|
815
|
+
a = self.lattice.a
|
|
816
|
+
alpha = np.rad2deg(self.lattice.alpha)
|
|
817
|
+
a = np.array(a)
|
|
818
|
+
if mode=='scale':
|
|
819
|
+
def Chi2(p):
|
|
820
|
+
self.lattice.setLattice(a*p[3],alpha)
|
|
821
|
+
UBnew = util.x_rotation(p[0]) @ util.y_rotation(p[1]) @ util.z_rotation(p[2]) @ self._U @ self.lattice.B_mat
|
|
822
|
+
hklnew = (np.linalg.inv(UBnew) @ qphi).T
|
|
823
|
+
return np.sum(LA.norm(hkl - hklnew,axis=1) * weights)
|
|
824
|
+
res = opt.minimize(Chi2,[0,0,0,1.])
|
|
825
|
+
else:
|
|
826
|
+
|
|
827
|
+
def Chi2(p):
|
|
828
|
+
self.lattice.setLattice(a*np.array(p[3:]),alpha)
|
|
829
|
+
UBnew = util.x_rotation(p[0]) @ util.y_rotation(p[1]) @ util.z_rotation(p[2]) @ self._U @ self.lattice.B_mat
|
|
830
|
+
hklnew = (np.linalg.inv(UBnew) @ qphi).T
|
|
831
|
+
return np.sum(LA.norm(hkl - hklnew,axis=1) * weights)
|
|
832
|
+
res = opt.minimize(Chi2,[0,0,0,1.,1.,1.])
|
|
833
|
+
#print(res)
|
|
834
|
+
U = util.x_rotation(res.x[0]) @ util.y_rotation(res.x[1]) @ util.z_rotation(res.x[2]) @ self._U
|
|
835
|
+
self.setU(U)
|
|
836
|
+
|
|
837
|
+
|
|
838
|
+
def refineU(self,hkl,angles,allowPhiChi_opt=False,rod=None,factor=100.):
|
|
839
|
+
qphi = []
|
|
840
|
+
for pos in angles:
|
|
841
|
+
qphi.append(calculate_q_phi(pos,self.getK()).T)
|
|
842
|
+
qphi = np.array(qphi).T
|
|
843
|
+
# p[0], p[1], p[2]: rotation angles
|
|
844
|
+
|
|
845
|
+
weights = np.ones(angles.shape[0])
|
|
846
|
+
|
|
847
|
+
if rod is not None:
|
|
848
|
+
weights[np.all(hkl[:,:2] == rod,axis=1)] = factor
|
|
849
|
+
|
|
850
|
+
if allowPhiChi_opt:
|
|
851
|
+
def Chi2(p):
|
|
852
|
+
qphi = []
|
|
853
|
+
UBnew = util.x_rotation(p[0]) @ util.y_rotation(p[1]) @ util.z_rotation(p[2]) @ self._U @ self.lattice.B_mat
|
|
854
|
+
#if input("Type exit") == "exit": raise Exception("bla")
|
|
855
|
+
for pos in angles:
|
|
856
|
+
pos[4] = p[3]
|
|
857
|
+
pos[5] = p[4]
|
|
858
|
+
qphi.append(calculate_q_phi(pos,self.getK()).T)
|
|
859
|
+
qphi = np.array(qphi).T
|
|
860
|
+
|
|
861
|
+
hklnew = (np.linalg.inv(UBnew) @ qphi).T
|
|
862
|
+
#print(np.sum(LA.norm(hkl - hklnew,axis=1)))
|
|
863
|
+
return np.sum(LA.norm(hkl - hklnew,axis=1) * weights )
|
|
864
|
+
res = opt.minimize(Chi2,[0,0,0,0,0])
|
|
865
|
+
else:
|
|
866
|
+
|
|
867
|
+
def Chi2(p):
|
|
868
|
+
UBnew = util.x_rotation(p[0]) @ util.y_rotation(p[1]) @ util.z_rotation(p[2]) @ self._U @ self.lattice.B_mat
|
|
869
|
+
hklnew = (np.linalg.inv(UBnew) @ qphi).T
|
|
870
|
+
return np.sum(LA.norm(hkl - hklnew,axis=1) * weights)
|
|
871
|
+
|
|
872
|
+
res = opt.minimize(Chi2,[0,0,0])
|
|
873
|
+
U = util.x_rotation(res.x[0]) @ util.y_rotation(res.x[1]) @ util.z_rotation(res.x[2]) @ self._U
|
|
874
|
+
self.setU(U)
|
|
875
|
+
|
|
876
|
+
def bruteForceU(self,hkl,angles):
|
|
877
|
+
qphi = []
|
|
878
|
+
for pos in angles:
|
|
879
|
+
qphi.append(calculate_q_phi(pos,self.getK()).T)
|
|
880
|
+
qphi = np.array(qphi).T
|
|
881
|
+
# p[0], p[1], p[2]: rotation angles
|
|
882
|
+
#I = np.matrix([[1.,0.,0.],[0.,1.,0.],[0.,0.,1.]])
|
|
883
|
+
def Chi2(p):
|
|
884
|
+
UBnew = util.x_rotation(p[0]) @ util.y_rotation(p[1]) @ util.z_rotation(p[2]) @ self.lattice.B_mat
|
|
885
|
+
hklnew = (np.linalg.inv(UBnew) * qphi).T
|
|
886
|
+
#print(hklnew)
|
|
887
|
+
#print(hkl)
|
|
888
|
+
#print(np.sum(LA.norm(hkl - hklnew,axis=1)))
|
|
889
|
+
return np.sum(LA.norm(hkl - hklnew,axis=1))
|
|
890
|
+
res = opt.minimize(Chi2,[0,0,0])
|
|
891
|
+
U = util.x_rotation(res.x[0]) @ util.y_rotation(res.x[1]) @ util.z_rotation(res.x[2])
|
|
892
|
+
self.setU(U)
|
|
893
|
+
|
|
894
|
+
def __str__(self):
|
|
895
|
+
pstr = 'E = ' + str(self.getEnergy()) + ' keV, lambda = ' +str(self.getLambda()) + "\n"
|
|
896
|
+
pstr += str(self.lattice)
|
|
897
|
+
if hasattr(self, "_primary"):
|
|
898
|
+
ppos, phkl = self._primary
|
|
899
|
+
pstr += '\nprimary reflection (or0): %s\n%s\n' % (phkl , strPos_prim(ppos) )
|
|
900
|
+
if hasattr(self, "_secondary"):
|
|
901
|
+
spos, shkl = self._secondary
|
|
902
|
+
pstr += 'secondary reflection (or1): %s\n%s\n' % (shkl , strPos_prim(spos) )
|
|
903
|
+
return pstr
|
|
904
|
+
|
|
905
|
+
def __repr__(self):
|
|
906
|
+
pstr = 'E = ' + str(self.getEnergy()) + ' keV, lambda = ' +str(self.getLambda()) + "\n"
|
|
907
|
+
pstr += str(self.lattice)
|
|
908
|
+
if hasattr(self, "_primary"):
|
|
909
|
+
ppos, phkl = self._primary
|
|
910
|
+
pstr += '\nprimary reflection (or0): %s\n%s\n' % (phkl , strPos_prim(ppos) )
|
|
911
|
+
if hasattr(self, "_secondary"):
|
|
912
|
+
spos, shkl = self._secondary
|
|
913
|
+
pstr += 'secondary reflection (or1): %s\n%s\n' % (shkl , strPos_prim(spos) )
|
|
914
|
+
return pstr
|
|
915
|
+
|
|
916
|
+
|
|
917
|
+
|
|
918
|
+
|
|
919
|
+
|
|
920
|
+
class VliegAngles():
|
|
921
|
+
def __init__(self,ubCalculator):
|
|
922
|
+
self._ubCalculator = ubCalculator
|
|
923
|
+
|
|
924
|
+
"""
|
|
925
|
+
Returns the hkl values of a aingle detector frame.
|
|
926
|
+
phi,chi,alpha,omega are fixed, gamma and delta are 1-d arrays
|
|
927
|
+
The calculation is optimized with numpy
|
|
928
|
+
Only kinematical calculation!
|
|
929
|
+
"""
|
|
930
|
+
def anglesToHklDetector(self,alpha,delta,gamma,omega,chi,phi):
|
|
931
|
+
[ALPHA, DELTA, GAMMA, OMEGA, CHI, PHI] = createVliegMatrices([alpha,delta,gamma,omega,chi,phi])
|
|
932
|
+
hkl = np.empty((gamma.size,delta.size,3))
|
|
933
|
+
K = self._ubCalculator.getK()
|
|
934
|
+
UBi = np.linalg.inv(self._ubCalculator.getUB())
|
|
935
|
+
ALPHAi = ALPHA.T
|
|
936
|
+
OMEGAi = OMEGA.T
|
|
937
|
+
CHIi = CHI.T
|
|
938
|
+
PHIi = PHI.T
|
|
939
|
+
for i in range(gamma.size):
|
|
940
|
+
#calculate ( DELTA * GAMMA - ALPHA**-1 ) * K_lab = Q_alpha
|
|
941
|
+
DEL_GAM_minALP = np.matmul((np.matmul(DELTA,GAMMA[i]) - ALPHAi), np.array([0.,K,0.]) ).T
|
|
942
|
+
#calculate UBi * PHIi * CHIi * OMEGAi * Q_alpha
|
|
943
|
+
hkl[i] = np.matmul(UBi,np.matmul(PHIi,np.matmul(CHIi,np.matmul(OMEGAi,DEL_GAM_minALP)))).T
|
|
944
|
+
return hkl[:,:,0], hkl[:,:,1], hkl[:,:,2] # h k l
|
|
945
|
+
|
|
946
|
+
def anglesToHklDetector_mesh(self,alpha,delta,gamma,omega,chi,phi):
|
|
947
|
+
warnings.warn("anglesToHklDetector_mesh is deprecated and will be removed in the future. Use anglesToHkl instead.", FutureWarning)
|
|
948
|
+
return self.anglesToHkl(alpha,delta,gamma,omega,chi,phi)
|
|
949
|
+
|
|
950
|
+
|
|
951
|
+
def anglesToHkl(self,alpha,delta,gamma,omega,chi,phi):
|
|
952
|
+
delta = np.asarray(delta)
|
|
953
|
+
gamma = np.asarray(gamma)
|
|
954
|
+
assert delta.shape == gamma.shape
|
|
955
|
+
|
|
956
|
+
[_, _, _, OMEGA, CHI, PHI] = createVliegMatrices([None,None,None,omega,chi,phi])
|
|
957
|
+
#hkl = np.empty((*shape,3))
|
|
958
|
+
K = self._ubCalculator.getK()
|
|
959
|
+
UBi = np.linalg.inv(self._ubCalculator.getUB())
|
|
960
|
+
|
|
961
|
+
OMEGAi = OMEGA.T
|
|
962
|
+
CHIi = CHI.T
|
|
963
|
+
PHIi = PHI.T
|
|
964
|
+
|
|
965
|
+
Qalp = self.QAlpha(alpha,delta,gamma)
|
|
966
|
+
shape = Qalp.shape
|
|
967
|
+
Qalp = np.ascontiguousarray(Qalp.reshape((-1,3)).T)
|
|
968
|
+
#calculate UBi * PHIi * CHIi * OMEGAi * Q_alpha
|
|
969
|
+
|
|
970
|
+
hkl = (UBi @ PHIi @ CHIi @ OMEGAi) @ Qalp
|
|
971
|
+
#hkl = np.matmul(UBi,np.matmul(PHIi,np.matmul(CHIi,np.matmul(OMEGAi,DEL_GAM_minALP.T)))).T.reshape((*shape,3))
|
|
972
|
+
hkl = hkl.T.reshape(shape)
|
|
973
|
+
return hkl[...,0], hkl[...,1], hkl[...,2] # h k l
|
|
974
|
+
|
|
975
|
+
# only for single points
|
|
976
|
+
def __anglesToHkl(self,pos):
|
|
977
|
+
"""
|
|
978
|
+
Returns hkl from pos object in radians.
|
|
979
|
+
Only kinematical calculation!
|
|
980
|
+
"""
|
|
981
|
+
pos = np.array(pos)
|
|
982
|
+
if len(pos.shape) == 1:
|
|
983
|
+
return np.linalg.inv(self._ubCalculator.getUB()) @ calculate_q_phi(pos,self._ubCalculator.getK())
|
|
984
|
+
else:
|
|
985
|
+
qphi = []
|
|
986
|
+
for p in pos:
|
|
987
|
+
qphi.append(calculate_q_phi(p,self._ubCalculator.getK()).T)
|
|
988
|
+
qphi = np.array(qphi).T
|
|
989
|
+
return (np.linalg.inv(self._ubCalculator.getUB()) @ qphi).T
|
|
990
|
+
|
|
991
|
+
|
|
992
|
+
def QAlpha(self,alpha,delta,gamma):
|
|
993
|
+
delta = np.asarray(delta)
|
|
994
|
+
gamma = np.asarray(gamma)
|
|
995
|
+
shape = delta.shape
|
|
996
|
+
assert delta.shape == gamma.shape
|
|
997
|
+
Qxyz = np.empty((*shape,3),dtype=np.float64)
|
|
998
|
+
|
|
999
|
+
cosgam = np.cos(gamma)
|
|
1000
|
+
Qxyz[...,0] = - np.sin(-delta)*cosgam
|
|
1001
|
+
Qxyz[...,1] = np.cos(-delta)*cosgam - np.cos(alpha)
|
|
1002
|
+
Qxyz[...,2] = np.sin(gamma) + np.sin(alpha)
|
|
1003
|
+
|
|
1004
|
+
Qxyz *= self._ubCalculator.getK()
|
|
1005
|
+
return Qxyz
|
|
1006
|
+
|
|
1007
|
+
|
|
1008
|
+
def QAlphaDetector(self,alpha,delta,gamma):
|
|
1009
|
+
[ALPHA, DELTA, GAMMA, OMEGA, CHI, PHI] = createVliegMatrices([alpha,delta,gamma,None,None,None])
|
|
1010
|
+
K = self._ubCalculator.getK()
|
|
1011
|
+
Qxyz = np.empty((gamma.size,delta.size,3))
|
|
1012
|
+
ALPHAi = ALPHA.T
|
|
1013
|
+
for i in range(gamma.size):
|
|
1014
|
+
#calculate ( DELTA * GAMMA - ALPHA**-1 ) * K_lab = Q_alpha
|
|
1015
|
+
Qxyz[i] = np.matmul((np.matmul(DELTA,GAMMA[i]) - ALPHAi), np.array([0.,K,0.]) )
|
|
1016
|
+
return Qxyz[:,:,0], Qxyz[:,:,1], Qxyz[:,:,2] # Qx Qy Qz
|
|
1017
|
+
|
|
1018
|
+
def anglesZmode(self,hkl,fixedangle,fixed='in',chi=0,phi=0,**keyargs):
|
|
1019
|
+
"""
|
|
1020
|
+
Calculates the diffractometer angles for the z-mode of the
|
|
1021
|
+
the 6-circle diffractometer as described in Lohmeier and Vlieg 1993.
|
|
1022
|
+
|
|
1023
|
+
Currently only fully functioning in forward scattering.
|
|
1024
|
+
|
|
1025
|
+
Parameters
|
|
1026
|
+
----------
|
|
1027
|
+
hkl: array of shape (3,n) or array_like of shape (3,)
|
|
1028
|
+
reciprocal space coordinates in reciprocal lattice units
|
|
1029
|
+
of the lattice specified in the given UBCalculator
|
|
1030
|
+
|
|
1031
|
+
fixedangle: float
|
|
1032
|
+
value of the fixed angle in rad,
|
|
1033
|
+
has no effect if equal angle constraint is given.
|
|
1034
|
+
|
|
1035
|
+
fixed: str (default: 'in')
|
|
1036
|
+
specifies one of three possible angle constraints:
|
|
1037
|
+
'in': angle of incidence (alpha) fixed
|
|
1038
|
+
'out': exit angle fixed (gamma)
|
|
1039
|
+
'eq': equal incident and exit angles.
|
|
1040
|
+
This typically is used to measure the
|
|
1041
|
+
specular reflectivity or the specular CTR
|
|
1042
|
+
The value of fixedangle has no effect, pass any number!
|
|
1043
|
+
|
|
1044
|
+
chi, phi: float (default: 0)
|
|
1045
|
+
values of the fixed inner sample circles chi and phi in rad
|
|
1046
|
+
|
|
1047
|
+
**keyargs:
|
|
1048
|
+
'mirrorx' : bool (default : False)
|
|
1049
|
+
indicates that the reflections with negative delta should be
|
|
1050
|
+
calculated.
|
|
1051
|
+
|
|
1052
|
+
Returns
|
|
1053
|
+
-------
|
|
1054
|
+
out: diffractometer angles
|
|
1055
|
+
alpha, delta, gamma, omega, chi, phi in rad
|
|
1056
|
+
if hkl was of shape (3,):
|
|
1057
|
+
6-tuple of the angles
|
|
1058
|
+
if hkl was of shape (3,n):
|
|
1059
|
+
ndarray of shape (n,6) with the angles
|
|
1060
|
+
"""
|
|
1061
|
+
[ALPHA, DELTA, GAMMA, OMEGA, CHI, PHI] = createVliegMatrices([None,None,None,None,chi,phi])
|
|
1062
|
+
hkl = np.array(hkl)
|
|
1063
|
+
K = self._ubCalculator.getK()
|
|
1064
|
+
Hphi = np.matmul(self._ubCalculator.getUB(),hkl)
|
|
1065
|
+
|
|
1066
|
+
Homega = CHI @ PHI @ Hphi
|
|
1067
|
+
if fixed == 'in':
|
|
1068
|
+
alpha = fixedangle
|
|
1069
|
+
gamma = np.arcsin(Homega[2]/K - np.sin(alpha) )
|
|
1070
|
+
elif fixed == 'out':
|
|
1071
|
+
gamma = fixedangle
|
|
1072
|
+
alpha = np.arcsin(Homega[2]/K - np.sin(gamma) )
|
|
1073
|
+
elif fixed == 'eq':
|
|
1074
|
+
gamma = alpha = np.arcsin(Homega[2]/(2*K))
|
|
1075
|
+
else:
|
|
1076
|
+
raise Exception("No valid angle constraint given. Should be one of 'in', 'out' or 'eq'")
|
|
1077
|
+
if len(hkl.shape) > 1:
|
|
1078
|
+
accos_arg = (1. - np.sum(Homega**2,axis=0) / (2*K**2) + np.sin(gamma)*np.sin(alpha)) * \
|
|
1079
|
+
(1 / (np.cos(gamma)*np.cos(alpha)))
|
|
1080
|
+
mask = np.logical_or(accos_arg > 0.99, accos_arg < -0.99) # handle numerical precision issue close to arccos(1).
|
|
1081
|
+
accos_arg[mask] = np.round(accos_arg[mask], np.finfo(np.float64).precision - 2)
|
|
1082
|
+
delta = np.arccos(accos_arg)
|
|
1083
|
+
|
|
1084
|
+
gamma = np.full_like(delta,gamma) if np.array(gamma).size < 2 else gamma
|
|
1085
|
+
alpha = np.full_like(delta,alpha) if np.array(alpha).size < 2 else alpha
|
|
1086
|
+
chi = np.full_like(delta,chi)
|
|
1087
|
+
phi = np.full_like(delta,phi)
|
|
1088
|
+
else:
|
|
1089
|
+
accos_arg = (1. - np.sum(Homega**2) / (2*K**2) + np.sin(gamma)*np.sin(alpha)) * \
|
|
1090
|
+
(1 / (np.cos(gamma)*np.cos(alpha)))
|
|
1091
|
+
if accos_arg > 0.99 or accos_arg < -0.99:
|
|
1092
|
+
accos_arg = np.round(accos_arg, np.finfo(np.float64).precision - 2)
|
|
1093
|
+
delta = np.arccos(accos_arg)
|
|
1094
|
+
if keyargs.get('mirrorx',False):
|
|
1095
|
+
delta = - delta # solution since delta is calculated using arccos above
|
|
1096
|
+
omega = np.arctan2((Homega[1]*np.sin(delta)*np.cos(gamma) - Homega[0]*(np.cos(delta)*np.cos(gamma) - np.cos(alpha))),
|
|
1097
|
+
(Homega[0]*np.sin(delta)*np.cos(gamma) + Homega[1]*(np.cos(delta)*np.cos(gamma) - np.cos(alpha))))
|
|
1098
|
+
|
|
1099
|
+
# omega now in range +-pi
|
|
1100
|
+
# convert to 0 - 2*pi, since most diffractometers work from 0 to 360 degrees
|
|
1101
|
+
#omega = (omega + np.pi) % np.pi
|
|
1102
|
+
if len(hkl.shape) > 1:
|
|
1103
|
+
return np.vstack((alpha, delta, gamma, omega, chi, phi)).T
|
|
1104
|
+
else:
|
|
1105
|
+
return alpha, delta, gamma, omega, chi, phi
|
|
1106
|
+
|
|
1107
|
+
def intersectLineEwald(self, H_0, H_1, alpha,omega,phi=0.,chi=0.,**keyargs):
|
|
1108
|
+
shape = H_0.shape
|
|
1109
|
+
assert len(H_0.shape) == 2 or len(H_0.shape) == 1
|
|
1110
|
+
#assert H_0.shape == H_1.shape
|
|
1111
|
+
|
|
1112
|
+
[ALPHA, DELTA, GAMMA, OMEGA, CHI, PHI] = createVliegMatrices([alpha, None, None, omega, chi, phi])
|
|
1113
|
+
#rotmatrices = [np.asarray(mat) for mat in rotmatrices]
|
|
1114
|
+
#[ALPHA, DELTA, GAMMA, OMEGA, CHI, PHI] = rotmatrices
|
|
1115
|
+
K = self._ubCalculator.getK()
|
|
1116
|
+
ub = np.asarray(self._ubCalculator.getUB())
|
|
1117
|
+
|
|
1118
|
+
Vmat = OMEGA @ (CHI @ (PHI @ ub)) # this can be super expensive!
|
|
1119
|
+
Vmat_H_0 = np.squeeze(Vmat @ H_0[...,np.newaxis])
|
|
1120
|
+
Vmat_H_1 = np.squeeze(Vmat @ H_1[...,np.newaxis])
|
|
1121
|
+
|
|
1122
|
+
# np.swapaxes(ALPHA,1,2) transposes matrices inside the stack, works also with a single matrix
|
|
1123
|
+
C_vec = (np.swapaxes(ALPHA,-1,-2)[...,1] * K) + Vmat_H_0
|
|
1124
|
+
|
|
1125
|
+
C_vec_sqr = np.sum(C_vec**2, axis=-1)
|
|
1126
|
+
Vmat_H_1_sqr = np.sum(Vmat_H_1**2, axis=-1)
|
|
1127
|
+
Vmat_H_1_C = np.sum(Vmat_H_1*C_vec, axis=-1)
|
|
1128
|
+
|
|
1129
|
+
term1 = Vmat_H_1_C / Vmat_H_1_sqr
|
|
1130
|
+
|
|
1131
|
+
with warnings.catch_warnings(): # will be nan if there is no solution!
|
|
1132
|
+
warnings.simplefilter("ignore")
|
|
1133
|
+
sqrtTerm = np.sqrt(term1**2 + (K**2 - C_vec_sqr) / Vmat_H_1_sqr)
|
|
1134
|
+
|
|
1135
|
+
|
|
1136
|
+
s1 = -(term1 + sqrtTerm)
|
|
1137
|
+
s2 = -(term1 - sqrtTerm)
|
|
1138
|
+
|
|
1139
|
+
#from IPython import embed; embed()
|
|
1140
|
+
|
|
1141
|
+
HKL_1 = H_1*s1[...,np.newaxis] + H_0
|
|
1142
|
+
HKL_2 = H_1*s2[...,np.newaxis] + H_0
|
|
1143
|
+
|
|
1144
|
+
if keyargs.get('Qalpha',False):
|
|
1145
|
+
Qalpha1 = np.squeeze(Vmat @ HKL_1[...,np.newaxis])
|
|
1146
|
+
Qalpha2 = np.squeeze(Vmat @ HKL_2[...,np.newaxis])
|
|
1147
|
+
return np.concatenate((HKL_1,Qalpha1,s1[...,np.newaxis]),axis=-1), np.concatenate((HKL_2,Qalpha2,s2[...,np.newaxis]),axis=-1)
|
|
1148
|
+
else:
|
|
1149
|
+
return HKL_1, HKL_2
|
|
1150
|
+
|
|
1151
|
+
def anglesIntersectLineEwald(self, H_0, H_1, alpha,omega,phi=0.,chi=0.,**keyargs):
|
|
1152
|
+
HKL_Q1, HKL_Q2 = self.intersectLineEwald(H_0, H_1, alpha,omega,phi,chi, Qalpha=True)
|
|
1153
|
+
K = self._ubCalculator.getK()
|
|
1154
|
+
HKL_1 = HKL_Q1[...,:3]
|
|
1155
|
+
HKL_2 = HKL_Q2[...,:3]
|
|
1156
|
+
|
|
1157
|
+
Qa_1 = HKL_Q1[...,3:-1] / K
|
|
1158
|
+
Qa_2 = HKL_Q2[...,3:-1] / K
|
|
1159
|
+
|
|
1160
|
+
sinalpha = np.sin(alpha)
|
|
1161
|
+
gamma_1 = np.arcsin(Qa_1[...,2] - sinalpha)
|
|
1162
|
+
gamma_2 = np.arcsin(Qa_2[...,2] - sinalpha)
|
|
1163
|
+
|
|
1164
|
+
delta_1 = np.arctan2(Qa_1[...,0], Qa_1[...,1] + np.cos(alpha))
|
|
1165
|
+
delta_2 = np.arctan2(Qa_2[...,0], Qa_2[...,1] + np.cos(alpha))
|
|
1166
|
+
|
|
1167
|
+
#gamma_1 = np.arctan2((Qa_1[...,2] - sinalpha) * np.sin(delta_1), Qa_1[...,0])
|
|
1168
|
+
#gamma_2 = np.arctan2((Qa_2[...,2] - sinalpha) * np.sin(delta_2), Qa_2[...,0])
|
|
1169
|
+
if keyargs.get('Qalpha',False):
|
|
1170
|
+
return np.concatenate((HKL_1,delta_1[...,np.newaxis], gamma_1[...,np.newaxis],HKL_Q1[...,-1][...,np.newaxis]),axis=-1),\
|
|
1171
|
+
np.concatenate((HKL_2,delta_2[...,np.newaxis], gamma_2[...,np.newaxis],HKL_Q2[...,-1][...,np.newaxis]),axis=-1),\
|
|
1172
|
+
Qa_1*K, Qa_2*K
|
|
1173
|
+
else:
|
|
1174
|
+
return np.concatenate((HKL_1,delta_1[...,np.newaxis], gamma_1[...,np.newaxis],HKL_Q1[...,-1][...,np.newaxis]),axis=-1),\
|
|
1175
|
+
np.concatenate((HKL_2,delta_2[...,np.newaxis], gamma_2[...,np.newaxis],HKL_Q2[...,-1][...,np.newaxis]),axis=-1)
|
|
1176
|
+
|
|
1177
|
+
def hkIntersect(self,rod,alpha,omega,phi=0.,chi=0.):
|
|
1178
|
+
hk = np.asarray(rod)
|
|
1179
|
+
if len(hk.shape) > 1:
|
|
1180
|
+
H_0 = np.concatenate((hk,np.zeros((hk.shape[0],1))),axis=-1)
|
|
1181
|
+
else:
|
|
1182
|
+
H_0 = np.array([*hk,0.])
|
|
1183
|
+
H_1 = np.array([0.,0.,1.])
|
|
1184
|
+
|
|
1185
|
+
hkl_del_gam_1, hkl_del_gam_2 = self.anglesIntersectLineEwald(H_0, H_1, alpha,omega,phi,chi)
|
|
1186
|
+
|
|
1187
|
+
L1 = hkl_del_gam_1[...,2]
|
|
1188
|
+
L2 = hkl_del_gam_2[...,2]
|
|
1189
|
+
delta1 = hkl_del_gam_1[...,3]
|
|
1190
|
+
delta2 = hkl_del_gam_2[...,3]
|
|
1191
|
+
gam1 = hkl_del_gam_1[...,4]
|
|
1192
|
+
gam2 = hkl_del_gam_2[...,4]
|
|
1193
|
+
|
|
1194
|
+
return (L1,gam1,delta1), (L2,gam2,delta2)
|
|
1195
|
+
|
|
1196
|
+
def coordinatesAlpha(self, xyz_rel, omega,phi=0.,chi=0.):
|
|
1197
|
+
"""Calculates the coordinates xyz_rel in the alpha frame (tilted sample).
|
|
1198
|
+
|
|
1199
|
+
:param xyz_rel: real space coordinates in lattice units
|
|
1200
|
+
:type xyz_rel: ndarray shape (n,3)
|
|
1201
|
+
|
|
1202
|
+
"""
|
|
1203
|
+
xyz_rel = np.atleast_2d(np.asarray(xyz_rel))
|
|
1204
|
+
shape = xyz_rel.shape
|
|
1205
|
+
|
|
1206
|
+
OMEGA = calcOMEGA(omega)
|
|
1207
|
+
PHI = calcPHI(phi)
|
|
1208
|
+
CHI = calcCHI(chi)
|
|
1209
|
+
U = self._ubCalculator.getU()
|
|
1210
|
+
R = self._ubCalculator.lattice.RealspaceMatrix
|
|
1211
|
+
xyz_alpha = (OMEGA @ CHI @ PHI @ U @ R) @ xyz_rel.T
|
|
1212
|
+
return (xyz_alpha.T).reshape(shape)
|
|
1213
|
+
|
|
1214
|
+
def coordinatesLab(self, xyz_rel,alpha, omega,phi=0.,chi=0.):
|
|
1215
|
+
"""Calculates the coordinates xyz_rel in the laboratory frame (y along beam).
|
|
1216
|
+
|
|
1217
|
+
:param xyz_rel: real space coordinates in lattice units
|
|
1218
|
+
:type xyz_rel: ndarray shape (n,3)
|
|
1219
|
+
|
|
1220
|
+
"""
|
|
1221
|
+
xyz_rel = np.atleast_2d(np.asarray(xyz_rel))
|
|
1222
|
+
shape = xyz_rel.shape
|
|
1223
|
+
|
|
1224
|
+
ALPHA = calcALPHA(alpha)
|
|
1225
|
+
xyz_alpha = self.coordinatesAlpha(xyz_rel, omega, phi, chi)
|
|
1226
|
+
return ((ALPHA @ xyz_alpha.T).T).reshape(shape)
|
|
1227
|
+
|
|
1228
|
+
def anglesOrientationAlpha(self, xyz_rel, xyz_direction):
|
|
1229
|
+
xyz_rel = np.atleast_2d(np.asarray(xyz_rel))
|
|
1230
|
+
xyz_direction = np.atleast_2d(np.asarray(xyz_direction))
|
|
1231
|
+
shape = xyz_rel.shape
|
|
1232
|
+
assert xyz_rel.shape == xyz_direction.shape
|
|
1233
|
+
|
|
1234
|
+
xyz_rel = xyz_rel.reshape((-1,3))
|
|
1235
|
+
xyz_direction = xyz_direction.reshape((-1,3))
|
|
1236
|
+
|
|
1237
|
+
om = np.empty(xyz_rel.shape[0])
|
|
1238
|
+
chi = np.empty(xyz_rel.shape[0])
|
|
1239
|
+
phi = np.empty(xyz_rel.shape[0])
|
|
1240
|
+
|
|
1241
|
+
U = np.array(self._ubCalculator.getU())
|
|
1242
|
+
R = np.array(self._ubCalculator.lattice.RealspaceMatrix)
|
|
1243
|
+
UR = U @ R
|
|
1244
|
+
|
|
1245
|
+
for i, (xyz_r, xyz_d) in enumerate(zip(xyz_rel, xyz_direction)):
|
|
1246
|
+
urx = UR @ xyz_r
|
|
1247
|
+
rotmat = util.rotation_matrix_from_vectors(urx, xyz_d)
|
|
1248
|
+
rot = Rotation.from_matrix(rotmat)
|
|
1249
|
+
rotangles = rot.as_euler('xyz')
|
|
1250
|
+
phi[i] = rotangles[0]
|
|
1251
|
+
chi[i] = rotangles[1]
|
|
1252
|
+
om[i] = -rotangles[2]
|
|
1253
|
+
|
|
1254
|
+
return om.reshape(shape[:-1]), chi.reshape(shape[:-1]), phi.reshape(shape[:-1])
|
|
1255
|
+
|
|
1256
|
+
|
|
1257
|
+
|
|
1258
|
+
|
|
1259
|
+
def getGeometryCorrection(self):
|
|
1260
|
+
return GeometryCorrection(self)
|
|
1261
|
+
|
|
1262
|
+
# only for phi/omega scans, partially zmode
|
|
1263
|
+
# deprecated! can be removed in release!
|
|
1264
|
+
class GeometryCorrection():
|
|
1265
|
+
def __init__(self,vliegangles):
|
|
1266
|
+
self._angles = vliegangles
|
|
1267
|
+
|
|
1268
|
+
def lorentzFactor(self,delta,beta_in,gamma):
|
|
1269
|
+
return 1./(np.sin(delta)*np.cos(beta_in)*np.cos(gamma))
|
|
1270
|
+
|
|
1271
|
+
def polarization(self,delta,gamma,alpha,fraction_horiz=1.):
|
|
1272
|
+
P_hor = 1. - (np.sin(alpha)*np.cos(delta)*np.cos(gamma) + np.cos(alpha)*np.sin(gamma))**2
|
|
1273
|
+
P_vert = 1. - (np.sin(delta)**2)*(np.cos(gamma)**2)
|
|
1274
|
+
return fraction_horiz*P_hor + (1.-fraction_horiz)*P_vert
|
|
1275
|
+
|
|
1276
|
+
# without footprint correction
|
|
1277
|
+
def activeSurfaceArea(self,delta,alpha,beta_in):
|
|
1278
|
+
return 1./(np.sin(delta)*np.cos(alpha-beta_in))
|
|
1279
|
+
|
|
1280
|
+
def correctionZmode(self,hkl,fixedangle,fixed='in',polarization_horiz=1.):
|
|
1281
|
+
alpha, delta, gamma, omega, chi, phi = self._angles.anglesZmode(hkl,fixedangle,fixed)
|
|
1282
|
+
P = self.polarization(delta,gamma,alpha,polarization_horiz)
|
|
1283
|
+
#L_phi = self.lorentzFactor(delta,alpha,gamma)
|
|
1284
|
+
Carea = self.activeSurfaceArea(delta,alpha,alpha)
|
|
1285
|
+
return P*Carea
|
|
1286
|
+
|
|
1287
|
+
def correctDatasetZmode(self,hkl,I,fixedangle,fixed='in',polarization_horiz=1.):
|
|
1288
|
+
corr = np.empty_like(I)
|
|
1289
|
+
for i in range(I.size):
|
|
1290
|
+
corr[i] = self.correctionZmode(hkl[i],fixedangle,fixed,polarization_horiz)
|
|
1291
|
+
corr /= np.mean(corr)
|
|
1292
|
+
return I/corr
|
|
1293
|
+
|
|
1294
|
+
def correctionFactorZmode(self,alpha,delta,gamma,polarization_horiz=1.):
|
|
1295
|
+
delta = np.abs(delta)
|
|
1296
|
+
corr = np.empty_like(delta)
|
|
1297
|
+
for i in range(delta.shape[0]):
|
|
1298
|
+
P = self.polarization(delta[i],gamma[i],alpha,polarization_horiz)
|
|
1299
|
+
Carea = self.activeSurfaceArea(delta[i],alpha,alpha)
|
|
1300
|
+
corr[i] = (P*Carea)
|
|
1301
|
+
return corr
|
|
1302
|
+
|
|
1303
|
+
def correctImageZmode(self,intensity,alpha,delta,gamma,polarization_horiz=1.):
|
|
1304
|
+
I = np.copy(intensity)
|
|
1305
|
+
delta = np.abs(delta)
|
|
1306
|
+
|
|
1307
|
+
for i in range(delta.shape[0]):
|
|
1308
|
+
#print(i)
|
|
1309
|
+
P = self.polarization(delta[i],gamma[i],alpha,polarization_horiz)
|
|
1310
|
+
Carea = self.activeSurfaceArea(delta[i],alpha,alpha)
|
|
1311
|
+
#print(I[i].shape)
|
|
1312
|
+
#print((P*Carea).shape)
|
|
1313
|
+
I[i] = I[i]/(P*Carea)
|
|
1314
|
+
return I
|
|
1315
|
+
|
|
1316
|
+
def applyImageZmode(self,intensity,alpha,delta,gamma,polarization_horiz=1.):
|
|
1317
|
+
I = np.copy(intensity)
|
|
1318
|
+
delta = np.abs(delta)
|
|
1319
|
+
|
|
1320
|
+
for i in range(delta.shape[0]):
|
|
1321
|
+
#print(i)
|
|
1322
|
+
P = self.polarization(delta[i],gamma[i],alpha,polarization_horiz)
|
|
1323
|
+
Carea = self.activeSurfaceArea(delta[i],alpha,alpha)
|
|
1324
|
+
#print(I[i].shape)
|
|
1325
|
+
#print((P*Carea).shape)
|
|
1326
|
+
I[i] = I[i]*(P*Carea)
|
|
1327
|
+
return I
|
|
1328
|
+
|
|
1329
|
+
|
|
1330
|
+
|
|
1331
|
+
#pos = [ALPHA, DELTA, GAMMA, OMEGA, CHI, PHI] (angles)
|
|
1332
|
+
|
|
1333
|
+
if __name__ == "__main__":
|
|
1334
|
+
|
|
1335
|
+
pt111 = Lattice([ 2.7748 , 2.7748 , 6.7969],[ 90. , 90. , 120.])
|
|
1336
|
+
"""
|
|
1337
|
+
basis = [[1.,0.,0.,0.],[1.,2./3.,1./3.,1./3.],[1.,1./3.,2./3.,2./3.]]
|
|
1338
|
+
h = 1.
|
|
1339
|
+
k = -1
|
|
1340
|
+
for l in range(10):
|
|
1341
|
+
print("[%s , %s, %s]: F = %s" % (h,k,l,np.absolute(pt111.F_hkl([h,k,l],basis))))
|
|
1342
|
+
"""
|
|
1343
|
+
ub = UBCalculator(pt111,69.971)
|
|
1344
|
+
ub.defaultU()
|
|
1345
|
+
|
|
1346
|
+
#ub.setPrimaryReflection(np.deg2rad([0.15,4.2168,1.354,0,0.,0.]),[1.,0.,1.])
|
|
1347
|
+
#ub.setSecondayReflection(np.deg2rad([0.15,4.211,2.839,+32.43 + 28.12,0.,0.]),[0.,1.,2.])
|
|
1348
|
+
#ub.calculateU()
|
|
1349
|
+
|
|
1350
|
+
|
|
1351
|
+
angles = VliegAngles(ub)
|
|
1352
|
+
|
|
1353
|
+
|
|
1354
|
+
|
|
1355
|
+
#delta = np.linspace(-np.pi/8,np.pi/8,1100)
|
|
1356
|
+
#gamma = np.linspace(-np.pi/8,np.pi/8,1600)
|
|
1357
|
+
h , k , l = angles.anglesToHklDetector(0.15,delta,gamma,0.1,0,0)
|
|
1358
|
+
|
|
1359
|
+
|
|
1360
|
+
|