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.
Files changed (83) hide show
  1. orGUI-1.0.1.dist-info/LICENSE +21 -0
  2. orGUI-1.0.1.dist-info/METADATA +147 -0
  3. orGUI-1.0.1.dist-info/RECORD +83 -0
  4. orGUI-1.0.1.dist-info/WHEEL +5 -0
  5. orGUI-1.0.1.dist-info/entry_points.txt +2 -0
  6. orGUI-1.0.1.dist-info/top_level.txt +1 -0
  7. orgui/__init__.py +36 -0
  8. orgui/app/ArrayTableDialog.py +433 -0
  9. orgui/app/QReflectionSelector.py +538 -0
  10. orgui/app/QScanSelector.py +692 -0
  11. orgui/app/QUBCalculator.py +1210 -0
  12. orgui/app/__init__.py +36 -0
  13. orgui/app/database.py +487 -0
  14. orgui/app/orGUI.py +2613 -0
  15. orgui/app/qutils.py +51 -0
  16. orgui/backend/__init__.py +32 -0
  17. orgui/backend/backends.py +157 -0
  18. orgui/backend/beamline/ID31DiffractLinTilt.py +77 -0
  19. orgui/backend/beamline/P212_tools.py +577 -0
  20. orgui/backend/beamline/__init__.py +36 -0
  21. orgui/backend/beamline/fio_reader.py +110 -0
  22. orgui/backend/beamline/id31_tools.py +651 -0
  23. orgui/backend/scans.py +95 -0
  24. orgui/backend/udefaults.py +163 -0
  25. orgui/backend/universalScanLoader.py +105 -0
  26. orgui/datautils/__init__.py +32 -0
  27. orgui/datautils/util.py +705 -0
  28. orgui/datautils/xrayutils/CTRcalc.py +3022 -0
  29. orgui/datautils/xrayutils/CTRopt.py +623 -0
  30. orgui/datautils/xrayutils/CTRplotutil.py +904 -0
  31. orgui/datautils/xrayutils/DetectorCalibration.py +685 -0
  32. orgui/datautils/xrayutils/HKLVlieg.py +1360 -0
  33. orgui/datautils/xrayutils/ReciprocalNavigation.py +401 -0
  34. orgui/datautils/xrayutils/_CTRcalc_accel.py +181 -0
  35. orgui/datautils/xrayutils/__init__.py +46 -0
  36. orgui/datautils/xrayutils/element_data.py +213 -0
  37. orgui/datautils/xrayutils/test/__init__.py +57 -0
  38. orgui/datautils/xrayutils/test/test_CTRcalc.py +152 -0
  39. orgui/datautils/xrayutils/test/test_DetectorCalibration.py +336 -0
  40. orgui/datautils/xrayutils/test/test_HKLcalc.py +88 -0
  41. orgui/datautils/xrayutils/unitcells/Fe3O4(100).bul +59 -0
  42. orgui/datautils/xrayutils/unitcells/Pt100.bul +7 -0
  43. orgui/datautils/xrayutils/unitcells/Pt100_small.bul +5 -0
  44. orgui/datautils/xrayutils/unitcells/Pt110.bul +5 -0
  45. orgui/datautils/xrayutils/unitcells/Pt111.bul +6 -0
  46. orgui/datautils/xrayutils/unitcells/Pt310.bul +13 -0
  47. orgui/datautils/xrayutils/unitcells/Pt3O4(100).bul +19 -0
  48. orgui/datautils/xrayutils/unitcells/PtO(001).bul +9 -0
  49. orgui/datautils/xrayutils/unitcells/PtO(010).bul +9 -0
  50. orgui/datautils/xrayutils/unitcells/PtO(100).bul +9 -0
  51. orgui/datautils/xrayutils/unitcells/__init__.py +67 -0
  52. orgui/datautils/xrayutils/unitcells/a-PtO2(0001).bul +6 -0
  53. orgui/main.py +101 -0
  54. orgui/resources/__init__.py +40 -0
  55. orgui/resources/icons/alpha.png +0 -0
  56. orgui/resources/icons/alpha.svg +67 -0
  57. orgui/resources/icons/diffractometer_v3.png +0 -0
  58. orgui/resources/icons/disable-image.png +0 -0
  59. orgui/resources/icons/disable-image.svg +68 -0
  60. orgui/resources/icons/document-nx-open.png +0 -0
  61. orgui/resources/icons/document-nx-open.svg +152 -0
  62. orgui/resources/icons/document-nx-save.png +0 -0
  63. orgui/resources/icons/document-nx-save.svg +73 -0
  64. orgui/resources/icons/logo.png +0 -0
  65. orgui/resources/icons/logo.svg +808 -0
  66. orgui/resources/icons/max_image.png +0 -0
  67. orgui/resources/icons/max_image.svg +77 -0
  68. orgui/resources/icons/max_image2.png +0 -0
  69. orgui/resources/icons/max_image2.svg +83 -0
  70. orgui/resources/icons/search-image.png +0 -0
  71. orgui/resources/icons/search-image.svg +94 -0
  72. orgui/resources/icons/search-reflection.png +0 -0
  73. orgui/resources/icons/search-reflection.svg +126 -0
  74. orgui/resources/icons/search.png +0 -0
  75. orgui/resources/icons/search.svg +91 -0
  76. orgui/resources/icons/select-image.png +0 -0
  77. orgui/resources/icons/select-image.svg +60 -0
  78. orgui/resources/icons/set-reflection.png +0 -0
  79. orgui/resources/icons/set-reflection.svg +91 -0
  80. orgui/resources/icons/sum_image.png +0 -0
  81. orgui/resources/icons/sum_image.svg +63 -0
  82. orgui/resources/icons/sum_image2.png +0 -0
  83. 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
+