pyTEMlib 0.2023.8.0__py2.py3-none-any.whl → 0.2024.2.0__py2.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.

Potentially problematic release.


This version of pyTEMlib might be problematic. Click here for more details.

@@ -26,9 +26,12 @@ All the input and output is done through a ase.Atoms object and the dictionary i
26
26
 
27
27
  # numerical packages used
28
28
  import numpy as np
29
- import scipy.constants as const
29
+ import scipy
30
30
  import itertools
31
31
 
32
+ import ase
33
+ import ase.build
34
+
32
35
  # plotting package used
33
36
  import matplotlib.pylab as plt # basic plotting
34
37
 
@@ -86,8 +89,6 @@ def Zuo_fig_3_18(verbose=True):
86
89
  # Create Silicon structure (Could be produced with Silicon routine)
87
90
  if verbose:
88
91
  print('Sample Input for Figure 3.18 in Zuo and Spence \"Advanced TEM\", 2017')
89
- import ase
90
- import ase.build
91
92
  a = 5.14 # A
92
93
  atoms = ase.build.bulk('Si', 'diamond', a=a, cubic=True)
93
94
 
@@ -147,31 +148,31 @@ def Zuo_fig_3_18(verbose=True):
147
148
  return atoms
148
149
 
149
150
 
150
- def zone_mistilt(zone, angles):
151
+ def get_rotation_matrix(angles, in_radians=False):
151
152
  """ Rotation of zone axis by mistilt
152
153
 
153
- Parameters
154
- ----------
155
- zone: list or numpy array of int
156
- zone axis in Miller indices
157
- angles: ist or numpy array of float
158
- list of mistilt angles in degree
154
+ Parameters
155
+ ----------
156
+ angles: ist or numpy array of float
157
+ list of mistilt angles (default in degrees)
158
+ in_radians: boolean default False
159
+ default is angles in degrees
159
160
 
160
- Returns
161
- -------
162
- new_zone_axis: np.ndarray (3)
163
- new tilted zone axis
164
- """
161
+ Returns
162
+ -------
163
+ rotation_matrix: np.ndarray (3x3)
164
+ rotation matrix in 3d
165
+ """
165
166
 
166
167
  if not isinstance(angles, (np.ndarray, list)):
167
168
  raise TypeError('angles must be a list of float of length 3')
168
169
  if len(angles) != 3:
169
170
  raise TypeError('angles must be a list of float of length 3')
170
- if not isinstance(zone, (np.ndarray, list)):
171
- raise TypeError('Miller indices must be a list of int of length 3')
172
-
173
- alpha, beta, gamma = np.radians(angles)
174
171
 
172
+ if in_radians:
173
+ alpha, beta, gamma = angles
174
+ else:
175
+ alpha, beta, gamma = np.radians(angles)
175
176
  # first we rotate alpha about x-axis
176
177
  c, s = np.cos(alpha), np.sin(alpha)
177
178
  rot_x = np.array([[1, 0, 0], [0, c, -s], [0, s, c]])
@@ -183,8 +184,29 @@ def zone_mistilt(zone, angles):
183
184
  # third we rotate gamma about z-axis
184
185
  c, s = np.cos(gamma), np.sin(gamma)
185
186
  rot_z = np.array([[c, -s, 0], [s, c, 0], [0, 0, 1]])
187
+ return np.dot(np.dot(rot_x, rot_y), rot_z)
188
+
189
+
190
+ def zone_mistilt(zone, angles):
191
+ """ Rotation of zone axis by mistilt
192
+
193
+ Parameters
194
+ ----------
195
+ zone: list or numpy array of int
196
+ zone axis in Miller indices
197
+ angles: ist or numpy array of float
198
+ list of mistilt angles in degree
199
+
200
+ Returns
201
+ -------
202
+ new_zone_axis: np.ndarray (3)
203
+ new tilted zone axis
204
+ """
205
+ if not isinstance(zone, (np.ndarray, list)):
206
+ raise TypeError('Miller indices must be a list of int of length 3')
186
207
 
187
- return np.dot(np.dot(np.dot(zone, rot_x), rot_y), rot_z)
208
+ rotation_matrix = get_rotation_matrix(angles)
209
+ return np.dot(zone, rotation_matrix)
188
210
 
189
211
 
190
212
  def get_metric_tensor(matrix):
@@ -213,12 +235,27 @@ def get_wavelength(acceleration_voltage):
213
235
  Returns:
214
236
  -------
215
237
  wavelength: float
216
- wave length in Angstrom
238
+ wave length in Angstrom (= meter *10**10)
217
239
  """
218
240
  if not isinstance(acceleration_voltage, (int, float)):
219
241
  raise TypeError('Acceleration voltage has to be a real number')
220
- eU = const.e * acceleration_voltage
221
- return const.h/np.sqrt(2*const.m_e*eU*(1+eU/(2*const.m_e*const.c**2)))*10**10
242
+
243
+ E = acceleration_voltage * scipy.constants.elementary_charge
244
+ h = scipy.constants.Planck
245
+ m0 = scipy.constants.electron_mass
246
+ c = scipy.constants.speed_of_light
247
+ wavelength = h / np.sqrt(2 * m0 * E * (1 + (E / (2 * m0 * c ** 2))))
248
+ return wavelength * 10**10
249
+
250
+
251
+ def get_all_miller_indices(hkl_max):
252
+ h = np.linspace(-hkl_max, hkl_max, 2 * hkl_max + 1) # all evaluated single Miller Indices
253
+ hkl = np.array(list(itertools.product(h, h, h))) # all evaluated Miller indices
254
+
255
+ # delete [0,0,0]
256
+ index_center = int(len(hkl) / 2)
257
+ hkl = np.delete(hkl, index_center, axis=0) # delete [0,0,0]
258
+ return hkl
222
259
 
223
260
 
224
261
  def find_nearest_zone_axis(tags):
@@ -226,12 +263,7 @@ def find_nearest_zone_axis(tags):
226
263
 
227
264
  hkl_max = 5
228
265
  # Make all hkl indices
229
- h = np.linspace(-hkl_max, hkl_max, 2 * hkl_max + 1) # all evaluated single Miller Indices
230
- hkl = np.array(list(itertools.product(h, h, h))) # all evaluated Miller indices
231
-
232
- # delete [0,0,0]
233
- index = int(len(hkl) / 2)
234
- zones_hkl = np.delete(hkl, index, axis=0) # delete [0,0,0]
266
+ zones_hkl = get_all_miller_indices(hkl_max)
235
267
 
236
268
  # make zone axis in reciprocal space
237
269
  zones_g = np.dot(zones_hkl, tags['reciprocal_unit_cell']) # all evaluated reciprocal_unit_cell points
@@ -285,15 +317,10 @@ def find_angles(zone):
285
317
 
286
318
  def stage_rotation_matrix(alpha, beta):
287
319
  """ Microscope stage coordinate system """
288
-
289
- # FIRST we rotate beta about x-axis
290
- c, s = np.cos(beta), np.sin(beta)
291
- rot_x = np.array([[1, 0, 0], [0, c, -s], [0, s, c]])
292
- # second we rotate alpha about y-axis
293
- c, s = np.cos(alpha), np.sin(alpha)
294
- rot_y = np.array([[c, 0, s], [0, 1, 0], [-s, 0, c]])
295
320
 
296
- return np.dot(rot_x, rot_y)
321
+ # FIRST we rotate beta about x-axis
322
+ angles = [beta, alpha, 0.]
323
+ return get_rotation_matrix(angles, in_radians=True)
297
324
 
298
325
 
299
326
  # ##################
@@ -301,7 +328,8 @@ def stage_rotation_matrix(alpha, beta):
301
328
  # We determine spherical coordinates to do that
302
329
  # ##################
303
330
 
304
- def get_rotation_matrix(tags):
331
+
332
+ def get_zone_rotation(tags):
305
333
  """zone axis in global coordinate system"""
306
334
 
307
335
  zone_hkl = tags['zone_hkl']
@@ -504,13 +532,7 @@ def ring_pattern_calculation(atoms, verbose=False):
504
532
  # inverse_metric_tensor = get_metric_tensor(reciprocal_unit_cell)
505
533
 
506
534
  hkl_max = tags['hkl_max']
507
-
508
- h = np.linspace(-hkl_max, hkl_max, 2 * hkl_max + 1) # all evaluated single Miller Indices
509
- hkl = np.array(list(itertools.product(h, h, h))) # all evaluated Miller indices
510
-
511
- # delete [0,0,0]
512
- index_center = int(len(hkl) / 2)
513
- hkl = np.delete(hkl, index_center, axis=0) # delete [0,0,0]
535
+ hkl = get_all_miller_indices(hkl_max)
514
536
 
515
537
  g_hkl = np.dot(hkl, reciprocal_unit_cell) # all evaluated reciprocal_unit_cell points
516
538
 
@@ -527,7 +549,7 @@ def ring_pattern_calculation(atoms, verbose=False):
527
549
 
528
550
  structure_factors.append(F)
529
551
 
530
- F = structure_factors = np.array(structure_factors)
552
+ F = np.array(structure_factors) # structure factors
531
553
 
532
554
  # Sort reflection in allowed and forbidden #
533
555
 
@@ -673,7 +695,7 @@ def kinematic_scattering(atoms, verbose=False):
673
695
  # Check sanity
674
696
  if atoms.info is None:
675
697
  atoms.info = {'output': {}, 'experimental': {}}
676
- elif 'output' in atoms.info:
698
+ if 'output' in atoms.info:
677
699
  output = atoms.info['output']
678
700
  else:
679
701
  output = atoms.info['output'] = {}
@@ -719,7 +741,11 @@ def kinematic_scattering(atoms, verbose=False):
719
741
  angstrom_conversion = 1.0e10 # So [1A (in m)] * angstrom_conversion = 1
720
742
  # NanometerConversion = 1.0e9
721
743
 
722
- scattering_factor_to_volts = (const.h ** 2) * (1e10 ** 2) / (2 * np.pi * const.m_e * const.e) * volume_unit_cell
744
+ e = scipy.constants.elementary_charge
745
+ h = scipy.constants.Planck
746
+ m0 = scipy.constants.electron_mass
747
+
748
+ scattering_factor_to_volts = (h**2) * (1e10**2) / (2 * np.pi * m0 * e) * volume_unit_cell
723
749
  tags['inner_potential_V'] = u0 * scattering_factor_to_volts
724
750
  if verbose:
725
751
  print(f'The inner potential is {u0:.1f} V')
@@ -746,7 +772,7 @@ def kinematic_scattering(atoms, verbose=False):
746
772
  # ############
747
773
 
748
774
  # get rotation matrix to rotate zone axis onto z-axis
749
- rotation_matrix = get_rotation_matrix(tags)
775
+ rotation_matrix = get_zone_rotation(tags)
750
776
 
751
777
  if verbose:
752
778
  print(f"Rotation alpha {np.rad2deg(tags['y-axis rotation alpha']):.1f} degree, "
@@ -854,22 +880,7 @@ def kinematic_scattering(atoms, verbose=False):
854
880
  dif['allowed']['hkl'] = hkl_allowed
855
881
  dif['allowed']['g'] = g_allowed
856
882
  dif['allowed']['structure factor'] = F_allowed
857
-
858
- # Calculate Extinction Distance Reimer 7.23
859
- # - makes only sense for non-zero F
860
-
861
- xi_g = np.real(np.pi * volume_unit_cell * k_0 / F_allowed)
862
883
 
863
- # Calculate Intensity of beams Reimer 7.25
864
- if 'thickness' not in tags:
865
- tags['thickness'] = 0.
866
- thickness = tags['thickness']
867
- if thickness > 0.1:
868
- I_g = np.real(np.pi ** 2 / xi_g ** 2 * np.sin(np.pi * thickness * s_g_allowed) ** 2 / (np.pi * s_g_allowed)**2)
869
- dif['allowed']['Ig'] = I_g
870
-
871
- dif['allowed']['intensities'] = intensities = np.real(F_allowed) ** 2
872
-
873
884
  # Calculate Extinction Distance Reimer 7.23
874
885
  # - makes only sense for non-zero F
875
886
 
@@ -894,7 +905,7 @@ def kinematic_scattering(atoms, verbose=False):
894
905
  Sg_forbidden = Sg[forbidden]
895
906
  hkl_forbidden = hkl[forbidden]
896
907
  g_forbidden = g_hkl[forbidden]
897
- F_forbidden = F[forbidden]
908
+ # F_forbidden = F[forbidden]
898
909
 
899
910
  dif['forbidden'] = {}
900
911
  dif['forbidden']['Sg'] = Sg_forbidden
@@ -1050,7 +1061,7 @@ def kinematic_scattering(atoms, verbose=False):
1050
1061
 
1051
1062
  tags_kikuchi['Laue_circle'] = laue_circle
1052
1063
  # Rotate to nearest zone axis
1053
- rotation_matrix = get_rotation_matrix(tags_kikuchi)
1064
+ rotation_matrix = get_zone_rotation(tags_kikuchi)
1054
1065
 
1055
1066
  g_kikuchi_all = np.dot(g_non_rot, rotation_matrix)
1056
1067
 
@@ -1098,452 +1109,6 @@ def kinematic_scattering(atoms, verbose=False):
1098
1109
  print('pyTEMlib\'s \"kinematic_scattering\" finished')
1099
1110
 
1100
1111
 
1101
- def kinematic_scattering2(atoms, verbose=False):
1102
- """
1103
- All kinematic scattering calculation
1104
-
1105
- Calculates Bragg spots, Kikuchi lines, excess, and deficient HOLZ lines
1106
-
1107
- Parameters
1108
- ----------
1109
- atoms: ase.Atoms
1110
- object with crystal structure:
1111
- and with experimental parameters in info attribute:
1112
- 'acceleration_voltage_V', 'zone_hkl', 'Sg_max', 'hkl_max'
1113
- Optional parameters are:
1114
- 'mistilt', convergence_angle_mrad', and 'crystal_name'
1115
- verbose = True will give extended output of the calculation
1116
- verbose: boolean
1117
- default is False
1118
-
1119
- Returns
1120
- -------
1121
- ato,s:
1122
- There are three sub_dictionaries in info attribute:
1123
- ['allowed'], ['forbidden'], and ['HOLZ']
1124
- ['allowed'] and ['forbidden'] dictionaries contain:
1125
- ['Sg'], ['hkl'], ['g'], ['structure factor'], ['intensities'],
1126
- ['ZOLZ'], ['FOLZ'], ['SOLZ'], ['HOLZ'], ['HHOLZ'], ['label'], and ['Laue_zone']
1127
- the ['HOLZ'] dictionary contains:
1128
- ['slope'], ['distance'], ['theta'], ['g_deficient'], ['g_excess'], ['hkl'], ['intensities'],
1129
- ['ZOLZ'], ['FOLZ'], ['SOLZ'], ['HOLZ'], and ['HHOLZ']
1130
- Please note that the Kikuchi lines are the HOLZ lines of ZOLZ
1131
-
1132
- There are also a few parameters stored in the main dictionary:
1133
- ['wave_length_nm'], ['reciprocal_unit_cell'], ['inner_potential_V'], ['incident_wave_vector'],
1134
- ['volume'], ['theta'], ['phi'], and ['incident_wave_vector_vacuum']
1135
- """
1136
-
1137
- # Check sanity
1138
- if atoms.info is None:
1139
- atoms.info = {'output': {}, 'experimental': {}}
1140
- elif 'output' in atoms.info:
1141
- output = atoms.info['output']
1142
- else:
1143
- output = atoms.info['output'] = {}
1144
-
1145
- output['SpotPattern'] = True
1146
-
1147
- if 'experimental' not in atoms.info:
1148
- tags = atoms.info['experimental'] = {}
1149
-
1150
- if not check_sanity(atoms):
1151
- print('Input is not complete, stopping')
1152
- print('Try \'example()\' for example input')
1153
- return
1154
-
1155
- tags = atoms.info['experimental']
1156
-
1157
- # wavelength
1158
- tags['wave_length'] = get_wavelength(tags['acceleration_voltage_V'])
1159
-
1160
- # volume of unit_cell
1161
- unit_cell = atoms.cell.array
1162
- metric_tensor = get_metric_tensor(unit_cell) # converts hkl to g vectors and back
1163
- tags['metric_tensor'] = metric_tensor
1164
- volume_unit_cell = atoms.cell.volume
1165
-
1166
- # reciprocal_unit_cell
1167
-
1168
- # We use the linear algebra package of numpy to invert the unit_cell "matrix"
1169
- reciprocal_unit_cell = atoms.cell.reciprocal() # np.linalg.inv(unit_cell).T # transposed of inverted unit_cell
1170
- tags['reciprocal_unit_cell'] = reciprocal_unit_cell
1171
- inverse_metric_tensor = get_metric_tensor(reciprocal_unit_cell)
1172
-
1173
- if verbose:
1174
- print('reciprocal_unit_cell')
1175
- print(np.round(reciprocal_unit_cell, 3))
1176
-
1177
- ############################################
1178
- # Incident wave vector k0 in vacuum and material
1179
- ############################################
1180
-
1181
- u0 = 0.0 # in (Ang)
1182
- # atom form factor of zero reflection angle is the inner potential in 1/A
1183
- for i in range(len(atoms)):
1184
- u0 += feq(atoms[i].symbol, 0.0)
1185
-
1186
- scattering_factor_to_volts = (const.h ** 2) * (1e10 ** 2) / (2 * np.pi * const.m_e * const.e) * volume_unit_cell
1187
-
1188
- tags['inner_potential_V'] = u0 * scattering_factor_to_volts
1189
- if verbose:
1190
- print(f'The inner potential is {u0:.1f} V')
1191
-
1192
- # Calculating incident wave vector magnitude 'k0' in material
1193
- wl = tags['wave_length']
1194
- tags['incident_wave_vector_vacuum'] = 1 / wl
1195
-
1196
- k0 = tags['incident_wave_vector'] = np.sqrt(1 / wl**2 + u0) # 1/Ang
1197
-
1198
- tags['convergence_angle_A-1'] = k0 * np.sin(tags['convergence_angle_mrad'] / 1000.)
1199
- if verbose:
1200
- print(f"Using an acceleration voltage of {tags['acceleration_voltage_V']/1000:.1f}kV")
1201
- print(f'Magnitude of incident wave vector in material: {k0:.1f} 1/Ang and in vacuum: {1/wl:.1f} 1/Ang')
1202
- print(f"Which is an wave length of {1 / k0 * 100.:.3f} pm in the material and {wl * 100.:.3f} pm "
1203
- f"in the vacuum")
1204
- print(f"The convergence angle of {tags['convergence_angle_mrad']:.1f}mrad "
1205
- f"= {tags['convergence_angle_A-1']:.2f} 1/A")
1206
- print(f"Magnitude of incident wave vector in material: {k0:.1f} 1/A which is a wavelength {100/k0:.3f} pm")
1207
-
1208
- # ############
1209
- # Rotate
1210
- # ############
1211
-
1212
- # get rotation matrix to rotate zone axis onto z-axis
1213
- rotation_matrix = get_rotation_matrix(tags)
1214
-
1215
- if verbose:
1216
- print(f"Rotation alpha {np.rad2deg(tags['y-axis rotation alpha']):.1f} degree, "
1217
- f" beta {np.rad2deg(tags['x-axis rotation beta']):.1f} degree")
1218
- print(f"from zone axis {tags['zone_hkl']}")
1219
- print(f"Tilting {1} by {np.rad2deg(tags['mistilt_alpha']):.2f} "
1220
- f" in alpha and {np.rad2deg(tags['mistilt_beta']):.2f} in beta direction results in :")
1221
- # list(tags['zone_hkl'])
1222
- #
1223
- # print(f"zone axis {list(tags['nearest_zone_axis'])} with a mistilt of "
1224
- # f"{np.rad2deg(tags['mistilt_nearest_zone alpha']):.2f} in alpha "
1225
- # f"and {np.rad2deg(tags['mistilt_nearest_zone beta']):.2f} in beta direction")
1226
- nearest = tags['nearest_zone_axes']
1227
- print('Next nearest zone axes are:')
1228
- for i in range(1, nearest['amount']):
1229
- print(f"{nearest[str(i)]['hkl']}: mistilt: {np.rad2deg(nearest[str(i)]['mistilt_alpha']):6.2f}, "
1230
- f"{np.rad2deg(nearest[str(i)]['mistilt_beta']):6.2f}")
1231
- k0_unit_vector = np.array([0, 0, 1]) # incident unit wave vector
1232
- k0_vector = k0_unit_vector * k0 # incident wave vector
1233
- cent = k0_vector # center of Ewald sphere
1234
-
1235
- if verbose:
1236
- print('Center of Ewald sphere ', cent)
1237
-
1238
- # Find all Miller indices whose reciprocal point lays near the Ewald sphere with radius k0
1239
- # within a maximum excitation error Sg
1240
- hkl_max = tags['hkl_max']
1241
- Sg_max = tags['Sg_max'] # 1/A maximum allowed excitation error
1242
-
1243
- h = np.linspace(-hkl_max, hkl_max, 2 * hkl_max + 1) # all evaluated single Miller indices
1244
- hkl = np.array(list(itertools.product(h, h, h))) # all evaluated Miller indices
1245
- g_non_rot = np.dot(hkl, reciprocal_unit_cell) # all evaluated reciprocal_unit_cell points
1246
-
1247
- g = np.dot(g_non_rot, rotation_matrix) # rotate these reciprocal_unit_cell points
1248
- g_norm = vector_norm(g) # length of all vectors
1249
- not_zero = g_norm > 0
1250
- g = g[not_zero] # zero reflection will make problems further on, so we exclude it.
1251
- g_non_rot = g_non_rot[not_zero]
1252
- g_norm = g_norm[not_zero]
1253
- hkl = hkl[not_zero]
1254
-
1255
- # Calculate excitation errors for all reciprocal_unit_cell points
1256
- # Zuo and Spence, 'Adv TEM', 2017 -- Eq 3:14
1257
- S = (k0 ** 2 - vector_norm(g - cent) ** 2) / (2 * k0)
1258
- g_mz = g - k0_vector
1259
- in_sqrt = g_mz[:, 2]**2 + np.linalg.norm(g_mz, axis=1)**2 - k0**2
1260
- in_sqrt[in_sqrt < 0] = 0.
1261
- S2 = -g_mz[:, 2] - np.sqrt(in_sqrt)
1262
-
1263
- # Determine reciprocal_unit_cell points with excitation error less than the maximum allowed one: Sg_max
1264
-
1265
- reflections = abs(S) < Sg_max # This is now a boolean array with True for all possible reflections
1266
- hkl_all = hkl.copy()
1267
- s_g = S[reflections]
1268
- g_hkl = g[reflections]
1269
-
1270
- hkl = hkl[reflections]
1271
- g_hkl_non_rot = g_non_rot[reflections]
1272
- g_norm = g_norm[reflections]
1273
-
1274
- if verbose:
1275
- print(f"Of the {len(g)} tested reciprocal_unit_cell points, {len(g_hkl)} "
1276
- f"have an excitation error less than {Sg_max:.2f} 1/Angstrom")
1277
-
1278
- # Calculate Structure Factors
1279
- base = atoms.positions
1280
- structure_factors = []
1281
- for j in range(len(g_hkl)):
1282
- F = 0
1283
- for b in range(len(atoms)):
1284
- f = feq(atoms[b].symbol, np.linalg.norm(g_hkl[j]))
1285
- F += f * np.exp(-2 * np.pi * 1j * (g_hkl_non_rot[j] * atoms.positions[b]).sum())
1286
-
1287
- structure_factors.append(F)
1288
-
1289
- F = structure_factors = np.array(structure_factors)
1290
-
1291
- # Sort reflection in allowed and forbidden #
1292
- allowed = np.absolute(F) > 0.000001 # allowed within numerical error
1293
-
1294
- if verbose:
1295
- print(f"Of the {hkl.shape[0]} possible reflection {allowed.sum()} are allowed.")
1296
-
1297
- # information of allowed reflections
1298
- s_g_allowed = s_g[allowed]
1299
- hkl_allowed = hkl[allowed][:]
1300
- g_allowed = g_hkl[allowed, :]
1301
- F_allowed = F[allowed]
1302
- g_norm_allowed = g_norm[allowed]
1303
-
1304
- atoms.info['diffraction'] = {}
1305
- dif = atoms.info['diffraction']
1306
- dif['allowed'] = {}
1307
- dif['allowed']['Sg'] = s_g_allowed
1308
- dif['allowed']['hkl'] = hkl_allowed
1309
- dif['allowed']['g'] = g_allowed
1310
- dif['allowed']['structure factor'] = F_allowed
1311
-
1312
- # Calculate Extinction Distance Reimer 7.23
1313
- # - makes only sense for non-zero F
1314
-
1315
- xi_g = np.real(np.pi * volume_unit_cell * k0 / F_allowed)
1316
-
1317
- # Calculate Intensity of beams Reimer 7.25
1318
- if 'thickness' not in tags:
1319
- tags['thickness'] = 0.
1320
- thickness = tags['thickness']
1321
- if thickness > 0.1:
1322
- I_g = np.real(np.pi ** 2 / xi_g ** 2 * np.sin(np.pi * thickness * s_g_allowed) ** 2 / (np.pi * s_g_allowed)**2)
1323
- dif['allowed']['Ig'] = I_g
1324
-
1325
- dif['allowed']['intensities'] = intensities = np.real(F_allowed) ** 2
1326
-
1327
- # information of forbidden reflections
1328
- forbidden = np.logical_not(allowed)
1329
- s_g_forbidden = s_g[forbidden]
1330
- hkl_forbidden = hkl[forbidden]
1331
- g_forbidden = g_hkl[forbidden]
1332
- F_forbidden = F[forbidden]
1333
-
1334
- dif['forbidden'] = {}
1335
- dif['forbidden']['Sg'] = s_g_forbidden
1336
- dif['forbidden']['hkl'] = hkl_forbidden.copy()
1337
- dif['forbidden']['g'] = g_forbidden
1338
-
1339
- # Make pretty labels
1340
- hkl_label = make_pretty_labels(hkl_allowed)
1341
- dif['allowed']['label'] = hkl_label
1342
- hkl_label = make_pretty_labels(hkl_forbidden)
1343
- dif['forbidden']['label'] = hkl_label
1344
-
1345
- # Dynamically Allowed Reflection
1346
- """
1347
- indices = range(len(hkl_allowed))
1348
- combinations = [list(x) for x in itertools.permutations(indices, 2)]
1349
- hkl_forbidden = hkl_forbidden.tolist()
1350
- dynamically_allowed = np.zeros(len(hkl_forbidden), dtype=bool)
1351
- for [i, j] in combinations:
1352
- possible = (hkl_allowed[i] + hkl_allowed[j]).tolist()
1353
- if possible in hkl_forbidden:
1354
- dynamically
1355
- _allowed[hkl_forbidden.index(possible)] = True
1356
- dif['forbidden']['dynamically_allowed'] = dynamically_allowed
1357
-
1358
- if verbose:
1359
- print(f"Of the {g_forbidden.shape[0]} forbidden reflection {dif['dynamically_allowed']['g'].shape[0]} "
1360
- f"can be dynamically activated.")
1361
- # print(dif['forbidden']['hkl'][dynamically_allowed])
1362
- """
1363
-
1364
- # Center of Laue Circle
1365
- laue_circle = np.dot(tags['nearest_zone_axis'], tags['reciprocal_unit_cell'])
1366
- laue_circle = np.dot(laue_circle, rotation_matrix)
1367
- laue_circle = laue_circle / np.linalg.norm(laue_circle) * k0
1368
- laue_circle[2] = 0
1369
-
1370
- dif['Laue_circle'] = laue_circle
1371
- if verbose:
1372
- print('Laue_circle', laue_circle)
1373
-
1374
- # ###########################
1375
- # Calculate Laue Zones (of allowed reflections)
1376
- # ###########################
1377
- # Below is the expression given in most books.
1378
- # However, that would only work for orthogonal crystal systems
1379
- # Laue_Zone = abs(np.dot(hkl_allowed,tags['zone_hkl'])) # works only for orthogonal systems
1380
-
1381
- # This expression works for all crystal systems
1382
- # Remember we have already tilted, and so the dot product is trivial and gives only the z-component.
1383
-
1384
- Laue_Zone = abs(np.dot(hkl_allowed, tags['nearest_zone_axis']))
1385
- dif['allowed']['Laue_Zone'] = Laue_Zone
1386
-
1387
- ZOLZ = Laue_Zone == 0
1388
- FOLZ = Laue_Zone == 1
1389
- SOLZ = Laue_Zone == 2
1390
- HOLZ = Laue_Zone > 2
1391
-
1392
- dif['allowed']['ZOLZ'] = ZOLZ
1393
- dif['allowed']['FOLZ'] = FOLZ
1394
- dif['allowed']['SOLZ'] = SOLZ
1395
- dif['allowed']['HOLZ'] = HOLZ
1396
-
1397
- if verbose:
1398
- print(' There are {0} allowed reflections in the zero order Laue Zone'.format(ZOLZ.sum()))
1399
- print(' There are {0} allowed reflections in the first order Laue Zone'.format((Laue_Zone == 1).sum()))
1400
- print(' There are {0} allowed reflections in the second order Laue Zone'.format((Laue_Zone == 2).sum()))
1401
- print(' There are {0} allowed reflections in the higher order Laue Zone'.format((Laue_Zone > 2).sum()))
1402
-
1403
- if verbose:
1404
- print(' hkl \t Laue zone \t Intensity (*1 and \t log) \t length \n')
1405
- for i in range(len(hkl_allowed)):
1406
- print(' {0} \t {1} \t {2:.3f} \t {3:.3f} \t {4:.3f} '.format(hkl_allowed[i],
1407
- g_allowed[i], intensities[i],
1408
- np.log(intensities[i] + 1),
1409
- g_norm_allowed[i]))
1410
-
1411
- ####################################
1412
- # Calculate HOLZ and Kikuchi Lines #
1413
- ####################################
1414
-
1415
- tags_new_zone = tags.copy()
1416
- tags_new_zone['mistilt_alpha'] = 0
1417
- tags_new_zone['mistilt_beta'] = 0
1418
-
1419
- for i in range(1): # tags['nearest_zone_axes']['amount']):
1420
-
1421
- zone_tags = tags['nearest_zone_axes'][str(i)]
1422
-
1423
- if verbose:
1424
- print('Calculating Kikuchi lines for zone: ', zone_tags['hkl'])
1425
-
1426
- laue_circle = np.dot(zone_tags['hkl'], tags['reciprocal_unit_cell'])
1427
- laue_circle = np.dot(laue_circle, rotation_matrix)
1428
- laue_circle = laue_circle / np.linalg.norm(laue_circle) * k0
1429
- laue_circle[2] = 0
1430
-
1431
- zone_tags['Laue_circle'] = laue_circle
1432
- # Rotate to nearest zone axis
1433
-
1434
- tags_new_zone['zone_hkl']
1435
-
1436
- theta = -(zone_tags['mistilt_alpha'])
1437
- phi = -(zone_tags['mistilt_beta'])
1438
-
1439
- # first we rotate phi about z-axis
1440
- c, s = np.cos(phi), np.sin(phi)
1441
- rot_z = np.array([[1, 0, 0], [0, c, -s], [0, s, c]])
1442
-
1443
- # second we rotate theta about y-axis
1444
- c, s = np.cos(theta), np.sin(theta)
1445
- rot_y = np.array([[c, 0, s], [0, 1, 0], [-s, 0, c]])
1446
-
1447
- # the rotation now makes z-axis coincide with plane normal
1448
-
1449
- rotation_matrix2 = np.dot(rot_z, rot_y)
1450
-
1451
- g_kikuchi_all = np.dot(g, rotation_matrix2)
1452
- ZOLZ = abs(g_kikuchi_all[:, 2]) < 1
1453
-
1454
- g_kikuchi = g_kikuchi_all[ZOLZ]
1455
- S = (k0 ** 2 - vector_norm(g_kikuchi - np.array([0, 0, k0])) ** 2) / (2 * k0)
1456
- reflections = abs(S) < 0.1 # This is now a boolean array with True for all possible reflections
1457
- g_hkl_kikuchi2 = g_kikuchi[reflections]
1458
- hkl_kikuchi2 = (hkl_all[ZOLZ])[reflections]
1459
-
1460
- structure_factors = []
1461
- for j in range(len(g_hkl_kikuchi2)):
1462
- F = 0
1463
- for b in range(len(atoms)):
1464
- f = feq(atoms[b].symbol, np.linalg.norm(g_hkl_kikuchi2[j]))
1465
- F += f * np.exp(-2 * np.pi * 1j * (g_hkl_kikuchi2[j] * atoms.positions[b]).sum())
1466
-
1467
- structure_factors.append(F)
1468
-
1469
- F = np.array(structure_factors)
1470
-
1471
- allowed_kikuchi = np.absolute(F) > 0.000001
1472
-
1473
- g_hkl_kikuchi = g_hkl_kikuchi2[allowed_kikuchi]
1474
- hkl_kikuchi = hkl_kikuchi2[allowed_kikuchi]
1475
-
1476
- gd2 = g_hkl_kikuchi / 2.
1477
- gd2[:, 2] = 0.
1478
-
1479
- # calculate and save line in Hough space coordinates (distance and theta)
1480
- slope2 = gd2[:, 0] / (gd2[:, 1] + 1e-20)
1481
- distance2 = np.sqrt(gd2[:, 0] * gd2[:, 0] + gd2[:, 1] * gd2[:, 1])
1482
- theta2 = np.arctan(slope2)
1483
-
1484
- dif['Kikuchi'] = {}
1485
- dif['Kikuchi']['slope'] = slope2
1486
- dif['Kikuchi']['distance'] = distance2
1487
- dif['Kikuchi']['theta'] = theta2
1488
- dif['Kikuchi']['hkl'] = hkl_kikuchi
1489
- dif['Kikuchi']['g_hkl'] = g_hkl_kikuchi
1490
- dif['Kikuchi']['g_deficient'] = gd2
1491
- dif['Kikuchi']['min dist'] = gd2 + laue_circle
1492
-
1493
- k_g = k0
1494
-
1495
- # Dynamic Correction
1496
- # Does not correct ZOLZ lines !!!!
1497
- # Equation Spence+Zuo 3.86a
1498
- if 'dynamic correction' in tags:
1499
- if tags['dynamic correction']:
1500
- gamma_1 = - 1. / (2. * k0) * (intensities / (2. * k0 * s_g_allowed)).sum()
1501
- if verbose:
1502
- print('Dynamic correction gamma_1: ', gamma_1)
1503
-
1504
- # Equation Spence+Zuo 3.84
1505
- k_g = k0 - k0 * gamma_1 / g_allowed[:, 2]
1506
-
1507
- # k_g = np.dot( [0,0,k0], rotation_matrix)
1508
- # Calculate angle between k0 and deficient cone vector
1509
- # For dynamic calculations k0 is replaced by k_g
1510
- d_theta = np.arcsin(g_norm_allowed / k_g / 2.) - np.arcsin(np.abs(g_allowed[:, 2]) / g_norm_allowed)
1511
-
1512
- # calculate length of distance of deficient cone to k0 in ZOLZ plane
1513
- gd_length = 2 * np.sin(d_theta / 2) * k0
1514
-
1515
- # Calculate nearest point of HOLZ and Kikuchi lines
1516
- gd = g_allowed.copy()
1517
- gd[:, 0] = -gd[:, 0] * gd_length / g_norm_allowed
1518
- gd[:, 1] = -gd[:, 1] * gd_length / g_norm_allowed
1519
- gd[:, 2] = 0.
1520
-
1521
- # calculate and save line in Hough space coordinates (distance and theta)
1522
- slope = gd[:, 0] / (gd[:, 1] + 1e-20)
1523
- distance = gd_length
1524
- theta = np.arctan(slope)
1525
-
1526
- dif['HOLZ'] = {}
1527
- dif['HOLZ']['slope'] = slope
1528
- # a line is now given by
1529
- dif['HOLZ']['distance'] = distance
1530
- dif['HOLZ']['theta'] = theta
1531
- dif['HOLZ']['g_deficient'] = gd
1532
- dif['HOLZ']['g_excess'] = gd + g_allowed
1533
- dif['HOLZ']['g_allowed'] = g_allowed.copy()
1534
-
1535
- dif['HOLZ']['ZOLZ'] = ZOLZ
1536
- dif['HOLZ']['HOLZ'] = np.logical_not(ZOLZ)
1537
- dif['HOLZ']['FOLZ'] = FOLZ
1538
- dif['HOLZ']['SOLZ'] = SOLZ
1539
- dif['HOLZ']['HHOLZ'] = HOLZ # even higher HOLZ
1540
-
1541
- dif['HOLZ']['hkl'] = dif['allowed']['hkl']
1542
- dif['HOLZ']['intensities'] = intensities
1543
-
1544
- print('done')
1545
-
1546
-
1547
1112
  def make_pretty_labels(hkls, hex_label=False):
1548
1113
  """Make pretty labels
1549
1114