pyTEMlib 0.2025.12.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. build/lib/pyTEMlib/__init__.py +36 -0
  2. build/lib/pyTEMlib/animation.py +667 -0
  3. build/lib/pyTEMlib/atom_tools.py +229 -0
  4. build/lib/pyTEMlib/config_dir.py +43 -0
  5. build/lib/pyTEMlib/crystal_tools.py +1219 -0
  6. build/lib/pyTEMlib/diffraction_plot.py +757 -0
  7. build/lib/pyTEMlib/dynamic_scattering.py +298 -0
  8. build/lib/pyTEMlib/eds_tools.py +901 -0
  9. build/lib/pyTEMlib/eds_xsections.py +268 -0
  10. build/lib/pyTEMlib/eels_tools/__init__.py +44 -0
  11. build/lib/pyTEMlib/eels_tools/core_loss_tools.py +751 -0
  12. build/lib/pyTEMlib/eels_tools/eels_database.py +134 -0
  13. build/lib/pyTEMlib/eels_tools/low_loss_tools.py +655 -0
  14. build/lib/pyTEMlib/eels_tools/peak_fit_tools.py +175 -0
  15. build/lib/pyTEMlib/eels_tools/zero_loss_tools.py +264 -0
  16. build/lib/pyTEMlib/file_reader.py +275 -0
  17. build/lib/pyTEMlib/file_tools.py +816 -0
  18. build/lib/pyTEMlib/get_bote_salvat.py +69 -0
  19. build/lib/pyTEMlib/graph_tools.py +1153 -0
  20. build/lib/pyTEMlib/graph_viz.py +599 -0
  21. build/lib/pyTEMlib/image/__init__.py +37 -0
  22. build/lib/pyTEMlib/image/image_atoms.py +270 -0
  23. build/lib/pyTEMlib/image/image_clean.py +198 -0
  24. build/lib/pyTEMlib/image/image_distortion.py +299 -0
  25. build/lib/pyTEMlib/image/image_fft.py +278 -0
  26. build/lib/pyTEMlib/image/image_graph.py +926 -0
  27. build/lib/pyTEMlib/image/image_registration.py +316 -0
  28. build/lib/pyTEMlib/image/image_utilities.py +308 -0
  29. build/lib/pyTEMlib/image/image_window.py +421 -0
  30. build/lib/pyTEMlib/image_tools.py +701 -0
  31. build/lib/pyTEMlib/interactive_image.py +1 -0
  32. build/lib/pyTEMlib/kinematic_scattering.py +1145 -0
  33. build/lib/pyTEMlib/microscope.py +62 -0
  34. build/lib/pyTEMlib/probe_tools.py +928 -0
  35. build/lib/pyTEMlib/sidpy_tools.py +153 -0
  36. build/lib/pyTEMlib/simulation_tools.py +104 -0
  37. build/lib/pyTEMlib/test.py +437 -0
  38. build/lib/pyTEMlib/utilities.py +431 -0
  39. build/lib/pyTEMlib/version.py +5 -0
  40. build/lib/pyTEMlib/xrpa_x_sections.py +20976 -0
  41. pyTEMlib/__init__.py +36 -0
  42. pyTEMlib/animation.py +667 -0
  43. pyTEMlib/atom_tools.py +229 -0
  44. pyTEMlib/config_dir.py +43 -0
  45. pyTEMlib/crystal_tools.py +1219 -0
  46. pyTEMlib/data/k_factors_Bruker_15keV.json +293 -0
  47. pyTEMlib/data/k_factors_Thermo_200keV.json +494 -0
  48. pyTEMlib/data/xrays_X_section_100kV.json +19334 -0
  49. pyTEMlib/data/xrays_X_section_200kV.json +18711 -0
  50. pyTEMlib/data/xrays_X_section_300kV.json +18347 -0
  51. pyTEMlib/data/xrays_X_section_60kV.json +20202 -0
  52. pyTEMlib/diffraction_plot.py +757 -0
  53. pyTEMlib/dynamic_scattering.py +298 -0
  54. pyTEMlib/eds_tools.py +901 -0
  55. pyTEMlib/eds_xsections.py +268 -0
  56. pyTEMlib/eels_tools/__init__.py +44 -0
  57. pyTEMlib/eels_tools/core_loss_tools.py +751 -0
  58. pyTEMlib/eels_tools/eels_database.py +134 -0
  59. pyTEMlib/eels_tools/low_loss_tools.py +655 -0
  60. pyTEMlib/eels_tools/peak_fit_tools.py +175 -0
  61. pyTEMlib/eels_tools/zero_loss_tools.py +264 -0
  62. pyTEMlib/file_reader.py +275 -0
  63. pyTEMlib/file_tools.py +816 -0
  64. pyTEMlib/get_bote_salvat.py +69 -0
  65. pyTEMlib/graph_tools.py +1153 -0
  66. pyTEMlib/graph_viz.py +599 -0
  67. pyTEMlib/image/__init__.py +37 -0
  68. pyTEMlib/image/image_atoms.py +270 -0
  69. pyTEMlib/image/image_clean.py +198 -0
  70. pyTEMlib/image/image_distortion.py +299 -0
  71. pyTEMlib/image/image_fft.py +278 -0
  72. pyTEMlib/image/image_graph.py +926 -0
  73. pyTEMlib/image/image_registration.py +316 -0
  74. pyTEMlib/image/image_utilities.py +308 -0
  75. pyTEMlib/image/image_window.py +421 -0
  76. pyTEMlib/image_tools.py +701 -0
  77. pyTEMlib/interactive_image.py +1 -0
  78. pyTEMlib/kinematic_scattering.py +1145 -0
  79. pyTEMlib/microscope.py +62 -0
  80. pyTEMlib/probe_tools.py +928 -0
  81. pyTEMlib/sidpy_tools.py +153 -0
  82. pyTEMlib/simulation_tools.py +104 -0
  83. pyTEMlib/test.py +437 -0
  84. pyTEMlib/utilities.py +431 -0
  85. pyTEMlib/version.py +5 -0
  86. pyTEMlib/xrpa_x_sections.py +20976 -0
  87. pytemlib-0.2025.12.0.dist-info/METADATA +100 -0
  88. pytemlib-0.2025.12.0.dist-info/RECORD +92 -0
  89. pytemlib-0.2025.12.0.dist-info/WHEEL +5 -0
  90. pytemlib-0.2025.12.0.dist-info/entry_points.txt +2 -0
  91. pytemlib-0.2025.12.0.dist-info/licenses/LICENSE +21 -0
  92. pytemlib-0.2025.12.0.dist-info/top_level.txt +5 -0
@@ -0,0 +1,1145 @@
1
+ """
2
+ kinematic_scattering
3
+ Copyright by Gerd Duscher
4
+
5
+ The University of Tennessee, Knoxville
6
+ Department of Materials Science & Engineering
7
+
8
+ Sources:
9
+ Scattering Theory:
10
+ Zuo and Spence, "Advanced TEM", 2017
11
+
12
+ Spence and Zuo, Electron Microdiffraction, Plenum 1992
13
+
14
+ Atomic Form Factor:
15
+ Kirkland: Advanced Computing in Electron Microscopy 2nd edition
16
+ Appendix C
17
+
18
+ Units:
19
+ everything is in SI units, except length which is given in Angstrom.
20
+
21
+ Usage:
22
+ See the notebooks for examples of these routines
23
+
24
+ All the input and output is done through a ase.Atoms object and the dictionary in the info attribute
25
+ """
26
+
27
+ import itertools
28
+
29
+ import numpy as np
30
+ import scipy
31
+
32
+ import ase
33
+ import ase.build
34
+
35
+ # plotting package used
36
+ import matplotlib.pylab as plt # basic plotting
37
+
38
+ import pyTEMlib
39
+ from pyTEMlib.crystal_tools import *
40
+ from pyTEMlib.diffraction_plot import *
41
+
42
+ inputKeys = ['acceleration_voltage_V', 'zone_hkl', 'Sg_max', 'hkl_max']
43
+ optional_input_keys = ['crystal', 'lattice_parameter_nm', 'convergence_angle_mrad',
44
+ 'mistilt', 'thickness', 'dynamic correction', 'dynamic correction K0']
45
+
46
+
47
+ def read_poscar(filename):
48
+ """ Deprecated - use pyTEMlib.file_tools.read_poscar"""
49
+ print('read_poscar and read_cif moved to file_tools, \n'
50
+ 'please use that library in the future!')
51
+ pyTEMlib.file_tools.read_poscar(filename)
52
+
53
+
54
+ def example(verbose=True):
55
+ """
56
+ same as zuo_fig_3_18
57
+ """
58
+ print('\n##########################')
59
+ print('# Start of Example Input #')
60
+ print('##########################\n')
61
+ print('Define only mandatory input: ', inputKeys)
62
+ print(' Kinematic diffraction routine will set optional input : ', optional_input_keys)
63
+
64
+ return zuo_fig_3_18(verbose=verbose)
65
+
66
+
67
+ def zuo_fig_3_18(verbose=True):
68
+ """
69
+ Input for Figure 3.18 in Zuo and Spence \"Advanced TEM\", 2017
70
+
71
+ This input acts as an example as well as a reference
72
+
73
+ Parameters:
74
+ -----------
75
+ verbose: boolean:
76
+ optional to see output
77
+ Returns:
78
+ -------
79
+ atoms: ase.Atoms
80
+ Silicon crystal structure
81
+ atoms.info['output'] = output: dictionary:
82
+ dictionary of all input and output parameter needed to reproduce that figure.
83
+ """
84
+
85
+ # INPUT
86
+ # Create Silicon structure (Could be produced with Silicon routine)
87
+ if verbose:
88
+ print('Sample Input for Figure 3.18 in Zuo and Spence \"Advanced TEM\", 2017')
89
+ a = 5.14 # A
90
+ atoms = ase.build.bulk('Si', 'diamond', a=a, cubic=True)
91
+
92
+ experiment = {'acceleration_voltage_V': 99.2 * 1000.0, # V
93
+ 'convergence_angle_mrad': 7.15, # mrad;
94
+ 'zone_hkl': np.array([-2, 2, 1]),
95
+ 'mistilt': np.array([0, 0, 0]), # mistilt in degrees
96
+ 'Sg_max': .03, # 1/A maximum allowed excitation error
97
+ 'hkl_max': 9 # Highest evaluated Miller indices
98
+ }
99
+ # Define Experimental Conditions
100
+ if verbose:
101
+ print('###########################')
102
+ print('# Experimental Conditions #')
103
+ print('###########################')
104
+
105
+ for key, value in experiment.items():
106
+ print(f'tags[\'{key}\'] =', value)
107
+
108
+ print('##################')
109
+ print('# Output Options #')
110
+ print('##################')
111
+
112
+ # Output options
113
+ output = {'background': 'black', # 'white' 'grey'
114
+ 'color_map': 'plasma',
115
+ 'plot_HOLZ': True,
116
+ 'plot_HOLZ_excess': True,
117
+ 'plot_Kikuchi': True,
118
+ 'plot_reflections': True,
119
+ 'label_HOLZ': False,
120
+ 'label_Kikuchi': False,
121
+ 'label_reflections': False,
122
+ 'label_color': 'black',
123
+ 'label_size': 10,
124
+ 'color_Laue_Zones': ['red', 'blue', 'green', 'blue', 'green'],
125
+ 'color_Kikuchi': 'green',
126
+ 'linewidth_HOLZ': -1, # -1: linewidth according to intensity (structure factor F^2)
127
+ 'linewidth_Kikuchi': -1, # linewidth according to intensity (structure factor F^2)
128
+ 'color_reflections': 'intensity', # 'Laue Zone'
129
+ 'color_zero': 'white', # 'None', 'white', 'blue'
130
+ 'color_ring_zero': 'None' # 'Red' #'white' #, 'None'
131
+ }
132
+
133
+ if verbose:
134
+ for key, value in output.items():
135
+ print(f'tags[\'{key}\'] =', value)
136
+ print('########################')
137
+ print('# End of Example Input #')
138
+ print('########################\n\n')
139
+
140
+ if atoms.info is None:
141
+ atoms.info = {}
142
+ atoms.info['experimental'] = experiment
143
+ atoms.info['output'] = output
144
+
145
+ return atoms
146
+
147
+ get_rotation_matrix = pyTEMlib.utilities.get_rotation_matrix
148
+
149
+ def zone_mistilt(zone, angles):
150
+ """ Rotation of zone axis by mistilt
151
+
152
+ Parameters
153
+ ----------
154
+ zone: list or numpy array of int
155
+ zone axis in Miller indices
156
+ angles: ist or numpy array of float
157
+ list of mistilt angles in degree
158
+
159
+ Returns
160
+ -------
161
+ new_zone_axis: np.ndarray (3)
162
+ new tilted zone axis
163
+ """
164
+ if not isinstance(zone, (np.ndarray, list)):
165
+ raise TypeError('Miller indices must be a list of int of length 3')
166
+
167
+ rotation_matrix = get_rotation_matrix(angles)
168
+ return np.dot(zone, rotation_matrix)
169
+
170
+
171
+ def get_metric_tensor(matrix):
172
+ """The metric tensor of the lattice."""
173
+ metric_tensor2 = np.dot(matrix, matrix.T)
174
+ return metric_tensor2
175
+
176
+
177
+ def vector_norm(g):
178
+ """ Length of vector
179
+
180
+ depreciated - use np.linalg.norm
181
+ """
182
+ g = np.array(g)
183
+ return np.sqrt(g[:, 0] ** 2 + g[:, 1] ** 2 + g[:, 2] ** 2)
184
+
185
+
186
+ def get_wavelength(acceleration_voltage) -> float:
187
+ """
188
+ Calculates the relativistic corrected de Broglie wavelength of an electron in Angstrom
189
+
190
+ Parameter:
191
+ ---------
192
+ acceleration_voltage: float or int
193
+ acceleration voltage in volt
194
+ Returns:
195
+ -------
196
+ wavelength: float
197
+ wave length in Angstrom (= meter *10**10)
198
+ """
199
+ return pyTEMlib.utilities.get_wavelength(acceleration_voltage) * 1e10
200
+
201
+
202
+ def get_all_miller_indices(hkl_max):
203
+ """ Get all Miller indices up to hkl_max"""
204
+ h = np.linspace(-hkl_max, hkl_max, 2 * hkl_max + 1) # all evaluated single Miller Indices
205
+ hkl = np.array(list(itertools.product(h, h, h))) # all evaluated Miller indices
206
+
207
+ # delete [0,0,0]
208
+ index_center = int(len(hkl) / 2)
209
+ hkl = np.delete(hkl, index_center, axis=0) # delete [0,0,0]
210
+ return hkl
211
+
212
+
213
+ def get_structure_factors(atoms, g_hkl):
214
+ """ Calculate structure factors for given reciprocal lattice points g_hkl"""
215
+ g_norm = np.linalg.norm(g_hkl, axis=1)
216
+ structure_factors = []
217
+ for j, g in enumerate(g_hkl):
218
+ structure_factor = 0
219
+ for atom in atoms:
220
+ # Atomic form factor for element and momentum change (g vector)
221
+ f = feq(atom.symbol, g_norm[j])
222
+ structure_factor += f * np.exp(-2*np.pi*1j*(g*atom.position).sum())
223
+ structure_factors.append(structure_factor)
224
+ return np.array(structure_factors)
225
+
226
+
227
+ def find_nearest_zone_axis(tags):
228
+ """Test all zone axis up to a maximum of hkl_max"""
229
+
230
+ hkl_max = 5
231
+ # Make all hkl indices
232
+ zones_hkl = get_all_miller_indices(hkl_max)
233
+
234
+ # make zone axis in reciprocal space
235
+ # all evaluated reciprocal_unit_cell points
236
+ zones_g = np.dot(zones_hkl, tags['reciprocal_unit_cell'])
237
+
238
+ # make zone axis in microscope coordinates of reciprocal space
239
+ zones_g = np.dot(zones_g, tags['rotation_matrix']) # rotate these reciprocal_unit_cell points
240
+
241
+ # calculate angles with z-axis
242
+ zones_g_norm = vector_norm(zones_g)
243
+ z_axis = np.array([0, 0, 1])
244
+
245
+ zones_angles = np.abs(np.arccos(np.dot((np.array(zones_g).T / zones_g_norm).T, z_axis)))
246
+
247
+ # get smallest angle
248
+ smallest = (zones_angles - zones_angles.min()) < 0.001
249
+ if smallest.sum() > 1: # multiples of Miller index of zone axis have same angle
250
+ zone = zones_hkl[smallest]
251
+ zone_index = abs(zone).sum(axis=1)
252
+ ind = zone_index.argmin()
253
+ zone_hkl = zone[ind]
254
+ else:
255
+ zone_hkl = zones_hkl[smallest][0]
256
+
257
+ tags['nearest_zone_axis'] = zone_hkl
258
+
259
+ # get other zone axes up to 5 degrees away
260
+ others = np.logical_not(smallest)
261
+ next_smallest = (zones_angles[others]) < np.deg2rad(5.)
262
+ ind = np.argsort((zones_angles[others])[next_smallest])
263
+
264
+ tags['next_nearest_zone_axes'] = ((zones_hkl[others])[next_smallest])[ind]
265
+
266
+ return zone_hkl
267
+
268
+
269
+ def find_angles(zone):
270
+ """Microscope stage coordinates of zone"""
271
+
272
+ # rotation around y-axis
273
+ r = np.sqrt(zone[1] ** 2 + zone[2] ** 2)
274
+ alpha = np.arctan(zone[0] / r)
275
+ if zone[2] < 0:
276
+ alpha = np.pi - alpha
277
+ # rotation around x-axis
278
+ if zone[2] == 0:
279
+ beta = np.pi / 2 * np.sign(zone[1])
280
+ else:
281
+ beta = np.arctan(zone[1] / zone[2])
282
+ return alpha, beta
283
+
284
+
285
+ def stage_rotation_matrix(alpha, beta):
286
+ """ Microscope stage coordinate system """
287
+
288
+ # FIRST we rotate beta about x-axis
289
+ angles = [beta, alpha, 0.]
290
+ return get_rotation_matrix(angles, in_radians=True)
291
+
292
+
293
+ # ##################
294
+ # Determine rotation matrix to tilt zone axis onto z-axis
295
+ # We determine spherical coordinates to do that
296
+ # ##################
297
+
298
+
299
+ def get_zone_rotation(tags):
300
+ """zone axis in global coordinate system"""
301
+ zone_hkl = tags['zone_hkl']
302
+ zone = np.dot(zone_hkl, tags['reciprocal_unit_cell'])
303
+
304
+ # angle of zone with Z around x,y:
305
+ alpha, beta = find_angles(zone)
306
+
307
+ alpha = alpha + tags['mistilt_alpha']
308
+ beta = beta + tags['mistilt_beta']
309
+
310
+ tags['y-axis rotation alpha'] = alpha
311
+ tags['x-axis rotation beta'] = beta
312
+
313
+ tags['rotation_matrix'] = rotation_matrix = stage_rotation_matrix(alpha, -beta)
314
+
315
+ # the rotation now makes z-axis coincide with plane normal
316
+
317
+ zone_nearest = find_nearest_zone_axis(tags)
318
+ tags['nearest_zone_axis'] = zone_nearest
319
+
320
+ # tilt angles of coordinates of nearest zone
321
+ zone_nearest = np.dot(zone_nearest, tags['reciprocal_unit_cell'])
322
+
323
+ alpha_nearest, beta_nearest = find_angles(zone_nearest)
324
+
325
+ # calculate mistilt of nearest zone axis
326
+ tags['mistilt_nearest_zone alpha'] = alpha - alpha_nearest
327
+ tags['mistilt_nearest_zone beta'] = beta - beta_nearest
328
+
329
+ tags['nearest_zone_axes'] = {}
330
+ tags['nearest_zone_axes']['0'] = {}
331
+ tags['nearest_zone_axes']['0']['hkl'] = tags['nearest_zone_axis']
332
+ tags['nearest_zone_axes']['0']['mistilt_alpha'] = alpha - alpha_nearest
333
+ tags['nearest_zone_axes']['0']['mistilt_beta'] = beta - beta_nearest
334
+
335
+ # find polar coordinates of next nearest zones
336
+ tags['nearest_zone_axes']['amount'] = len(tags['next_nearest_zone_axes']) + 1
337
+
338
+ for i in range(len(tags['next_nearest_zone_axes'])):
339
+ zone_n = tags['next_nearest_zone_axes'][i]
340
+ tags['nearest_zone_axes'][str(i + 1)] = {}
341
+ tags['nearest_zone_axes'][str(i + 1)]['hkl'] = zone_n
342
+
343
+ zone_near = np.dot(zone_n, tags['reciprocal_unit_cell'])
344
+ # zone_near_g = np.dot(zone_near,rotation_matrix)
345
+
346
+ tags['nearest_zone_axes'][str(i + 1)]['g'] = zone_near
347
+ alpha_nearest, beta_nearest = find_angles(zone_near)
348
+
349
+ tags['nearest_zone_axes'][str(i + 1)]['mistilt_alpha'] = alpha - alpha_nearest
350
+ tags['nearest_zone_axes'][str(i + 1)]['mistilt_beta'] = beta - beta_nearest
351
+ # print('other' , i, np.rad2deg([alpha, alpha_nearest, beta, beta_nearest]))
352
+
353
+ return rotation_matrix
354
+
355
+
356
+ def check_sanity(atoms, verbose_level=0):
357
+ """
358
+ Check sanity of input parameters
359
+ """
360
+ stop = False
361
+ output = atoms.info['output']
362
+ tags = atoms.info['experimental']
363
+ for key in ['acceleration_voltage_V']:
364
+ if key not in tags:
365
+ print(f'Necessary parameter {key} not defined')
366
+ stop = True
367
+ output.setdefault('SpotPattern', False)
368
+ if output['SpotPattern']:
369
+ if 'zone_hkl' not in tags:
370
+ print(' No zone_hkl defined')
371
+ stop = True
372
+ if 'Sg_max' not in tags:
373
+ print(' No Sg_max defined')
374
+ stop = True
375
+ if 'hkl_max' not in tags:
376
+ print(' No hkl_max defined')
377
+ stop = True
378
+
379
+ if stop:
380
+ print('Input is not complete, stopping')
381
+ print('Try \'example()\' for example input')
382
+ return False
383
+ ############################################
384
+ # Check optional input
385
+ ############################################
386
+
387
+ if output['SpotPattern']:
388
+ tags['mistilt_alpha'] = np.deg2rad(tags.get('mistilt_alpha degree', 0.0))
389
+ tags['mistilt_beta'] = np.deg2rad(tags.get('mistilt_beta degree', 0.0))
390
+ tags.setdefault('convergence_angle_mrad', 0.)
391
+ tags.setdefault('thickness', 0.)
392
+ tags.setdefault('dynamic correction', 0.)
393
+ tags.setdefault('dynamic correction K0', 0.)
394
+
395
+ if verbose_level > 0:
396
+ print('Input: ')
397
+ print(f"tags['mistilt_alpha'] = {tags['mistilt_alpha']*1000:.2f}mrad")
398
+ print(f"tags['mistilt_beta'] = {tags['mistilt_beta']*1000:.2f}mrad")
399
+ print(f"tags['convergence_angle_mrad'] = {tags['convergence_angle_mrad']}")
400
+ print(f"tags['thickness'] = {tags['thickness']}")
401
+ print(f"tags['dynamic correction'] = {tags['dynamic correction']}")
402
+ print(f"tags['dynamic correction K0'] = {tags['dynamic correction K0']}")
403
+ return not stop
404
+
405
+
406
+ def scattering_matrix(tags, verbose_level=1):
407
+ """ Scattering matrix"""
408
+ if not check_sanity(tags, verbose_level):
409
+ return
410
+ # ##
411
+ # Pair distribution Function
412
+ # ##
413
+ unit_cell = np.array(tags['unit_cell'])
414
+ base = tags['base']
415
+
416
+ n = 20
417
+ x = np.linspace(-n, n, 2 * n + 1) # all evaluated multiples of x
418
+ xyz = np.array(list(itertools.product(x, x, x))) # all evaluated multiples in all direction
419
+
420
+ mat = np.dot(xyz, unit_cell) # all evaluated unit_cells
421
+
422
+ atom = {}
423
+ all_distances = np.array([])
424
+ for i, coordinate in enumerate(np.dot(base, unit_cell)):
425
+ distances = np.linalg.norm(mat + coordinate, axis=1)
426
+ unique, counts = np.unique(distances, return_counts=True)
427
+
428
+ atom[str(i)] = dict(zip(unique, counts))
429
+ print(atom[str(i)])
430
+
431
+ all_distances = np.append(all_distances, distances)
432
+ unique, counts = np.unique(all_distances, return_counts=True)
433
+
434
+ plt.plot(unique, counts)
435
+ plt.show()
436
+
437
+
438
+ def gaussian(xx, pp):
439
+ """ Gaussian function"""
440
+ s1 = pp[2] / 2.3548
441
+ prefactor = 1.0 / np.sqrt(2 * np.pi * s1 ** 2)
442
+ y = (pp[1] * prefactor) * np.exp(-(xx - pp[0]) ** 2 / (2 * s1 ** 2))
443
+ return y
444
+
445
+ def get_allowed_reflections(atoms, verbose=False):
446
+ """ Calculate allowed reflections of a crystal structure"""
447
+ tags = atoms.info['experimental']
448
+ tags.setdefault('reciprocal_unit_cell', atoms.cell.reciprocal())
449
+
450
+ hkl = get_all_miller_indices(tags.get('hkl_max', 10))
451
+ # all evaluated reciprocal_unit_cell points
452
+
453
+ g_hkl = np.dot(hkl, tags['reciprocal_unit_cell'])
454
+ ##################################
455
+ # Calculate Structure Factors
456
+ #################################
457
+ structure_factors = get_structure_factors(atoms, g_hkl)
458
+
459
+ # Sort reflection in allowed and forbidden #
460
+ allowed = np.absolute(structure_factors) > 0.000001 # allowed within numerical error
461
+
462
+ if verbose:
463
+ print(f'Of the {hkl.shape[0]} possible reflection {allowed.sum()} are allowed.')
464
+
465
+ # information of allowed reflections
466
+ hkl_allowed = hkl[allowed][:]
467
+ g_allowed = np.array(g_hkl)[allowed, :]
468
+ f_allowed = structure_factors[allowed]
469
+ g_norm_allowed = vector_norm(g_allowed) # length of all vectors = 1/
470
+
471
+ ind = np.argsort(g_norm_allowed)
472
+ hkl_sorted = hkl_allowed[ind][:]
473
+ f_sorted = f_allowed[ind]
474
+ g_sorted = g_allowed[ind, :]
475
+ return hkl_sorted, g_sorted, f_sorted
476
+
477
+
478
+ def get_reflection_families(hkl_sorted, g_sorted, f_sorted, verbose=False):
479
+ """ Determine reflection families and multiplicity"""
480
+
481
+ g_norm_sorted = vector_norm(g_sorted) # length of all vectors = 1/A
482
+ g_norm, multiplicity = np.unique(np.around(g_norm_sorted, decimals=5),
483
+ return_counts=True)
484
+ if verbose:
485
+ print(f'Of the {len(g_norm_sorted)} allowed reflection {len(multiplicity)} ',
486
+ 'have unique distances.')
487
+
488
+ reflections_d = []
489
+ reflections_m = []
490
+ reflections_f = []
491
+ start = 0
492
+ for i in range(len(g_norm)):
493
+ end = start + multiplicity[i]
494
+ hkl_max = np.argmax(hkl_sorted[start:end].sum(axis=1))
495
+ reflections_d.append(g_norm_sorted[start])
496
+ reflections_m.append(hkl_sorted[start + hkl_max])
497
+ reflections_f.append(f_sorted[start])
498
+ start = end
499
+
500
+ if verbose:
501
+ print('\n\n [hkl] \t 1/d [1/nm] \t d [nm] \t F^2 ')
502
+ for i, spot in enumerate(g_norm):
503
+ print(f'{reflections_m[i]} \t {spot*10:.2f} \t '+
504
+ f'{1 / spot/10:.4f} \t {np.real(reflections_f[i])**2:.2f} ')
505
+ return reflections_m, reflections_f, g_norm, multiplicity
506
+
507
+ def ring_pattern_calculation(atoms, verbose=False):
508
+ """
509
+ Calculate the ring diffraction pattern of a crystal structure
510
+
511
+ Parameters
512
+ ----------
513
+ atoms: Crystal
514
+ crystal structure
515
+ verbose: verbose print-outs
516
+ set to False
517
+ Returns
518
+ -------
519
+ tags: dict
520
+ dictionary with diffraction information added
521
+ """
522
+ # Check sanity
523
+ if not check_sanity(atoms, verbose):
524
+ return
525
+
526
+ tags = atoms.info['experimental']
527
+ # wavelength
528
+ tags['wave_length'] = get_wavelength(tags['acceleration_voltage_V'])
529
+
530
+ tags['metric_tensor'] = get_metric_tensor(atoms.cell.array)
531
+ # converts hkl to g vectors and back
532
+
533
+ ##################################
534
+ # Calculate Structure Factors
535
+ #################################
536
+ hkl_sorted, g_sorted, f_sorted = get_allowed_reflections(atoms, verbose)
537
+
538
+ reflections_m, reflections_f, g_norm, multiplicity = get_reflection_families(hkl_sorted,
539
+ g_sorted,
540
+ f_sorted,
541
+ verbose)
542
+ atoms.info['Ring_Pattern'] = {}
543
+ atoms.info['Ring_Pattern']['allowed'] = {}
544
+ atoms.info['Ring_Pattern']['allowed']['hkl'] = reflections_m
545
+ atoms.info['Ring_Pattern']['allowed']['g norm'] = g_norm
546
+ atoms.info['Ring_Pattern']['allowed']['structure factor'] = reflections_f
547
+ atoms.info['Ring_Pattern']['allowed']['multiplicity'] = multiplicity
548
+
549
+ atoms.info['Ring_Pattern']['profile_x'] = np.linspace(0, g_norm.max(), 2048)
550
+ step_size = atoms.info['Ring_Pattern']['profile_x'][1]
551
+ intensity = np.zeros(2048)
552
+ x_index = [(g_norm / step_size + 0.5).astype(int)]
553
+ intensity[x_index] = np.array(np.real(reflections_f)) * np.array(np.real(reflections_f))
554
+ atoms.info['Ring_Pattern']['profile_y delta'] = intensity
555
+
556
+ if 'thickness' in tags:
557
+ if tags['thickness'] > 0:
558
+ x = np.linspace(-1024, 1023, 2048) * step_size
559
+ p = [0.0, 1, 2 / tags['thickness']]
560
+ gauss = gaussian(x, p)
561
+ intensity = np.convolve(np.array(intensity), np.array(gauss), mode='same')
562
+ atoms.info['Ring_Pattern']['profile_y'] = intensity
563
+
564
+ # Make pretty labels
565
+ hkl_allowed = reflections_m
566
+ hkl_label = make_pretty_labels(hkl_allowed)
567
+ atoms.info['Ring_Pattern']['allowed']['label'] = hkl_label
568
+
569
+
570
+ def get_dynamically_allowed(atoms, verbose=False):
571
+ """ Determine which forbidden reflections can be dynamically activated"""
572
+ if not isinstance(atoms, ase.Atoms):
573
+ print('we need an ase atoms object as input')
574
+ if 'diffraction' not in atoms.info:
575
+ print('Run the kinematic_scattering function first')
576
+
577
+ # Dynamically Allowed Reflection
578
+
579
+ dif = atoms.info['diffraction']
580
+ hkl_allowed = dif['allowed']['hkl']
581
+ hkl_forbidden = dif['forbidden']['hkl']
582
+ indices = range(len(hkl_allowed))
583
+ combinations = [list(x) for x in itertools.permutations(indices, 2)]
584
+ hkl_forbidden = hkl_forbidden.tolist()
585
+ dynamically_allowed = np.zeros(len(hkl_forbidden), dtype=bool)
586
+ for [i, j] in combinations:
587
+ possible = (hkl_allowed[i] + hkl_allowed[j]).tolist()
588
+ if possible in hkl_forbidden:
589
+ dynamically_allowed[hkl_forbidden.index(possible)] = True
590
+ dif['forbidden']['dynamically_allowed'] = dynamically_allowed
591
+
592
+ if verbose:
593
+ print(f"Of the {len(hkl_forbidden)} forbidden reflection {dynamically_allowed.sum()} "
594
+ f"can be dynamically activated.")
595
+ # print(dif['forbidden']['hkl'][dynamically_allowed])
596
+
597
+ def get_unit_cell(atoms, tags):
598
+ """ Unit cell and reciprocal unit cell"""
599
+ unit_cell = atoms.cell.array
600
+ tags['unit_cell'] = unit_cell
601
+ # converts hkl to g vectors and back
602
+ tags['metric_tensor'] = get_metric_tensor(unit_cell)
603
+
604
+ # We use the linear algebra package of numpy to invert the unit_cell "matrix"
605
+ tags['reciprocal_unit_cell'] = atoms.cell.reciprocal()
606
+
607
+
608
+ def get_incident_wave_vector(atoms, tags, verbose):
609
+ """ Incident wave vector K0 in vacuum and material"""
610
+ u0 = 0.0 # in (Ang)
611
+ # atom form factor of zero reflection angle is the
612
+ # inner potential in 1/A
613
+ for atom in atoms:
614
+ u0 += feq(atom.symbol, 0.0)
615
+
616
+ e = scipy.constants.elementary_charge
617
+ h = scipy.constants.Planck
618
+ m0 = scipy.constants.electron_mass
619
+
620
+ volume_unit_cell = atoms.cell.volume
621
+ scattering_factor_to_volts = (h**2) * (1e10**2) / (2 * np.pi * m0 * e) * volume_unit_cell
622
+ tags['inner_potential_V'] = u0 * scattering_factor_to_volts
623
+ if verbose:
624
+ print(f'The inner potential is {u0:.1f} V')
625
+
626
+ # Calculating incident wave vector magnitude 'k0' in material
627
+ wl = tags['wave_length']
628
+ tags['incident_wave_vector_vacuum'] = 1 / wl
629
+
630
+ tags['incident_wave_vector'] = np.sqrt(1 / wl**2 + u0/volume_unit_cell) # 1/Ang
631
+ return tags['incident_wave_vector']
632
+
633
+
634
+
635
+ def find_sorted_bragg_reflections(atoms, tags, verbose):
636
+ """ Find and sort all Bragg reflections within excitation error Sg_max"""
637
+ # #######################
638
+ # Find all Miller indices whose reciprocal point lies near the Ewald sphere with radius k_0
639
+ # within a maximum excitation error sg
640
+ # #######################
641
+ k_0 = tags['incident_wave_vector']
642
+ sg_max = tags['Sg_max'] # 1/Ang maximum allowed excitation error
643
+
644
+ hkl_all = get_all_miller_indices(tags['hkl_max'])
645
+ # all evaluated reciprocal_unit_cell points
646
+ g_non_rot = np.dot(hkl_all, tags['reciprocal_unit_cell'])
647
+ g_norm = np.linalg.norm(g_non_rot, axis=1) # length of all vectors
648
+ g = np.dot(g_non_rot, tags['rotation_matrix'])
649
+
650
+ # #######################
651
+ # Calculate excitation errors for all reciprocal_unit_cell points
652
+ # #######################
653
+
654
+ # Zuo and Spence, 'Adv TEM', 2017 -- Eq 3:14
655
+ #excitation error sg = (k0^2 - |g - k0|^2 ) / 2k0
656
+ excitation_error = (k_0**2-np.linalg.norm(g - tags['k0_vector'], axis=1)**2)/(2*k_0)
657
+
658
+ # #######################
659
+ # Determine reciprocal_unit_cell points with excitation error
660
+ # less than the maximum allowed one: Sg_max
661
+ # #######################
662
+
663
+ # This is now a boolean array with True for all possible reflections
664
+ reflections = abs(excitation_error) < sg_max
665
+
666
+ sg = excitation_error[reflections]
667
+ g_hkl = g[reflections]
668
+ g_hkl_non_rot = g_non_rot[reflections]
669
+ hkl = hkl_all[reflections]
670
+ g_norm = g_norm[reflections]
671
+
672
+ if verbose:
673
+ print(f'Of the {len(g)} tested reciprocal_unit_cell points,'+
674
+ f' {len(g_hkl)} have an excitation error less than {sg_max:.2f} 1/nm')
675
+
676
+ # #################################
677
+ # Calculate Structure Factors
678
+ # ################################
679
+ structure_factor = get_structure_factors(atoms, g_hkl_non_rot)
680
+
681
+ # ###########################################
682
+ # Sort reflection in allowed and forbidden #
683
+ # ###########################################
684
+ allowed = np.absolute(structure_factor) > 0.000001 # allowed within numerical erro
685
+ if verbose:
686
+ print(f'Of the {hkl.shape[0]} possible reflection {allowed.sum()} are allowed.')
687
+ # information of allowed reflections
688
+ f_allowed = structure_factor[allowed]
689
+
690
+ atoms.info['diffraction'] = {}
691
+ dif = atoms.info['diffraction']
692
+ dif['allowed'] = {}
693
+ dif['allowed']['sg'] = sg[allowed]
694
+ dif['allowed']['hkl'] = hkl[allowed][:]
695
+ dif['allowed']['g'] = g_hkl[allowed][:]
696
+ dif['allowed']['g_non_rot'] = g_non_rot
697
+
698
+ dif['allowed']['structure factor'] = f_allowed
699
+
700
+ # Calculate Extinction Distance Reimer 7.23
701
+ # - makes only sense for non-zero structure_factor
702
+ xi_g = np.real(np.pi * atoms.cell.volume * k_0 / f_allowed)
703
+
704
+ # ###########################
705
+ # Calculate Intensities (of allowed reflections)
706
+ # ###########################
707
+
708
+ # Calculate Intensity of beams Reimer 7.25
709
+ if 'thickness' not in tags:
710
+ tags['thickness'] = 0.
711
+ thickness = tags['thickness']
712
+ if thickness > 0.1:
713
+ i_g = np.real(np.pi ** 2 / xi_g**2 * np.sin(np.pi * thickness * sg[allowed])**2
714
+ / (np.pi * sg[allowed])**2)
715
+ dif['allowed']['Ig'] = i_g
716
+
717
+ dif['allowed']['intensities'] = np.real(f_allowed)**2
718
+
719
+ # information of forbidden reflections
720
+ forbidden = np.logical_not(allowed)
721
+ dif['forbidden'] = {}
722
+ dif['forbidden']['sg'] = sg[forbidden]
723
+ dif['forbidden']['hkl'] = hkl[forbidden]
724
+ dif['forbidden']['g'] = g_hkl[forbidden]
725
+
726
+ def center_of_laue_circle(atoms, tags):
727
+ """ Center of Laue circle in microscope coordinates"""
728
+ k_0 = tags['incident_wave_vector']
729
+ laue_circle = np.dot(tags['nearest_zone_axis'], tags['reciprocal_unit_cell'])
730
+ laue_circle = np.dot(laue_circle, tags['rotation_matrix'])
731
+ laue_circle = laue_circle / np.linalg.norm(laue_circle) * k_0
732
+ laue_circle[2] = 0
733
+
734
+ atoms.info.setdefault('diffraction', {})['Laue_circle'] = laue_circle
735
+
736
+ def output_verbose(atoms, tags):
737
+ """ Verbose output of experimental parameters"""
738
+ print('Experimental Parameters:')
739
+ print(f"Acceleration Voltage: {tags['acceleration_voltage_V']*1000:.1f} kV")
740
+ print(f"Convergence Angle: {tags['convergence_angle_mrad']:.2f} mrad",
741
+ f" = {tags['convergence_angle_A-1']:.2f} 1/Ang")
742
+ print(f"Wavelength: {tags['wave_length']*1000:.3f} pm")
743
+ print(f"Incident Wave Vector: {tags['incident_wave_vector']*10} 1/nm in material ",
744
+ f"; in vacumm: {1/tags['wave_length']:.4f} 1/nm")
745
+ print("\n Rotation to tilt zone axis onto z-axis:")
746
+ print(f"Rotation alpha {np.rad2deg(tags['y-axis rotation alpha']):.1f} degree, "
747
+ f" beta {np.rad2deg(tags['x-axis rotation beta']):.1f} degree")
748
+ print(f"from zone axis {tags['zone_hkl']}")
749
+ print(f"Tilting {1} by {np.rad2deg(tags['mistilt_alpha']):.2f} "
750
+ f" in alpha and {np.rad2deg(tags['mistilt_beta']):.2f}" +
751
+ " in beta direction results in :")
752
+ nearest = tags['nearest_zone_axes']
753
+ print('Next nearest zone axes are:')
754
+ for i in range(1, nearest['amount']):
755
+ print(f"{nearest[str(i)]['hkl']}: mistilt:",
756
+ f" {np.rad2deg(nearest[str(i)]['mistilt_alpha']):6.2f}, "
757
+ f"{np.rad2deg(nearest[str(i)]['mistilt_beta']):6.2f}")
758
+ print('Center of Ewald sphere ', tags['k0_vector'])
759
+ dif = atoms.info['diffraction']
760
+ print('Center or Laue circle', dif['Laue_circle'])
761
+
762
+ def calculate_laue_zones(atoms, tags, verbose):
763
+ """ Calculate Laue Zones (of allowed reflections)
764
+
765
+ ###########################
766
+ Below is the expression given in most books.
767
+ However, that would only work for orthogonal crystal systems
768
+ Laue_Zone = abs(np.dot(hkl_allowed,tags['zone_hkl']))
769
+ works only for orthogonal systems
770
+
771
+ Below expression works for all crystal systems
772
+ Remember we have already tilted, and so the dot product is trivial
773
+ and gives only the z-component.
774
+ """
775
+ dif = atoms.info['diffraction']
776
+ length_zone_axis = np.linalg.norm(np.dot(tags['zone_hkl'], tags['unit_cell']))
777
+ g_norm_allowed = vector_norm(dif['allowed']['g'])
778
+ laue_zone = abs(np.dot(dif['allowed']['hkl'], tags['nearest_zone_axis']))
779
+ dif['allowed']['Laue_Zone'] = laue_zone
780
+ zolz_forbidden = abs(np.floor(dif['forbidden']['g'][:, 2]
781
+ * length_zone_axis+0.5)) == 0
782
+ dif['forbidden']['Laue_Zone'] = zolz_forbidden
783
+ dif['allowed']['ZOLZ'] = laue_zone == 0
784
+ dif['allowed']['FOLZ'] = laue_zone == 1
785
+ dif['allowed']['SOLZ'] = laue_zone == 2
786
+ dif['allowed']['HOLZ'] = laue_zone > 0
787
+ dif['allowed']['HOLZ_plus'] = dif['allowed']['HHOLZ'] = laue_zone > 2
788
+
789
+ if verbose:
790
+ print(f' There are {(laue_zone == 0).sum()} allowed reflections in the ',
791
+ "zero order Laue Zone")
792
+ print(f' There are {(laue_zone == 1).sum()} allowed reflections in the ',
793
+ "first order Laue Zone")
794
+ print(f' There are {(laue_zone == 2).sum()} allowed reflections in the ',
795
+ "second order Laue Zone")
796
+ print(f' There are {(laue_zone > 2).sum()} allowed reflections in the ',
797
+ "other higher order Laue Zones")
798
+ print(f'Length of zone axis vector in real space {length_zone_axis:.3f} nm')
799
+
800
+ if verbose == 2:
801
+ print(' hkl \t Laue zone \t Intensity (*1 and \t log) \t length \n')
802
+ for i, hkl in enumerate(dif['allowed']['hkl']):
803
+ print(f" {hkl} \t {laue_zone[i]} \t {dif['allowed']['intensities'][i]:.3f} \t",
804
+ f" {np.log(dif['allowed']['intensities'][i]+1):.3f} ",
805
+ f"\t {g_norm_allowed[i]:.3f} ")
806
+
807
+
808
+ def get_dynamical_allowed(atoms, verbose):
809
+ """ Determine which forbidden reflections can be dynamically activated"""
810
+ dif = atoms.info['diffraction']
811
+ hkl_allowed = dif['allowed']['hkl']
812
+ hkl_forbidden = dif['forbidden']['hkl']
813
+ zolz = dif['allowed']['ZOLZ']
814
+ zolz_forbidden = dif['forbidden']['Laue_Zone']
815
+ double_diffraction = (np.sum(np.array(list(itertools.combinations(
816
+ hkl_allowed[zolz], 2))), axis=1))
817
+
818
+ dynamical_allowed = []
819
+ still_forbidden = []
820
+ for i, hkl in enumerate(hkl_forbidden):
821
+ if zolz_forbidden[i]:
822
+ if hkl.tolist() in double_diffraction.tolist():
823
+ dynamical_allowed.append(i)
824
+ else:
825
+ still_forbidden.append(i)
826
+ dif['forbidden']['dynamically_activated'] = dynamical_allowed
827
+ dif['forbidden']['forbidden'] = dynamical_allowed
828
+ if verbose:
829
+ print(f'There are {len(dynamical_allowed)} forbidden but',
830
+ " dynamical activated diffraction spots:")
831
+ # print(tags['forbidden']['hkl'][dynamical_allowed])
832
+
833
+
834
+ def calculate_holz(atoms, tags):
835
+ """ Calculate HOLZ lines (of allowed reflections)"""
836
+ dif = atoms.info['diffraction']
837
+ intensities = dif['allowed']['intensities']
838
+ k_0 = tags['incident_wave_vector']
839
+ g_norm_allowed = vector_norm(dif['allowed']['g'])
840
+
841
+ # Dynamic Correction
842
+ # Equation Spence+Zuo 3.86a
843
+ gamma_1 = - 1./(2.*k_0) * (intensities / (2.*k_0*dif['allowed']['sg'])).sum()
844
+
845
+ # Equation Spence+Zuo 3.84
846
+ kg = k_0 - k_0*gamma_1/(dif['allowed']['g'][:, 2]+1e-15)
847
+ kg[dif['allowed']['ZOLZ']] = k_0
848
+
849
+ # Calculate angle between K0 and deficient cone vector
850
+ # For dynamic calculations K0 is replaced by kg
851
+ kg[:] = k_0
852
+ d_theta = np.arcsin(g_norm_allowed/kg/2.) - (np.arcsin(np.abs(dif['allowed']['g'][:, 2])
853
+ /g_norm_allowed))
854
+
855
+ # calculate length of distance of deficient cone to K0 in ZOLZ plane
856
+ gd_length = 2*np.sin(d_theta/2) * k_0
857
+
858
+ # Calculate nearest point of HOLZ and Kikuchi lines
859
+ g_closest = dif['allowed']['g'].copy()
860
+ g_closest = g_closest*(gd_length/np.linalg.norm(g_closest, axis=1))[:, np.newaxis]
861
+
862
+ g_closest[:, 2] = 0.
863
+
864
+ # calculate and save line in Hough space coordinates (distance and theta)
865
+ slope = g_closest[:, 0]/(g_closest[:, 1]+1e-10)
866
+ distance = gd_length
867
+ theta = np.arctan2(dif['allowed']['g'][:, 0], dif['allowed']['g'][:, 1])
868
+
869
+ dif['HOLZ'] = {}
870
+ dif['HOLZ']['slope'] = slope
871
+ # a line is now given by
872
+
873
+ dif['HOLZ']['distance'] = distance
874
+ dif['HOLZ']['theta'] = theta
875
+ dif['HOLZ']['g_deficient'] = g_closest
876
+ dif['HOLZ']['g_excess'] = g_closest + dif['allowed']['g']
877
+ dif['HOLZ']['ZOLZ'] = dif['allowed']['ZOLZ']
878
+ dif['HOLZ']['HOLZ'] = dif['allowed']['HOLZ']
879
+ dif['HOLZ']['FOLZ'] = dif['allowed']['FOLZ']
880
+ dif['HOLZ']['SOLZ'] = dif['allowed']['SOLZ']
881
+ dif['HOLZ']['HHOLZ'] = dif['allowed']['HHOLZ'] # even higher HOLZ
882
+ dif['HOLZ']['hkl'] = dif['allowed']['hkl']
883
+ dif['HOLZ']['intensities'] = intensities
884
+
885
+ ####################################
886
+ # Calculate HOLZ and Kikuchi Lines #
887
+ ####################################
888
+ def calculate_kikuchi(atoms, tags, verbose):
889
+ """ Calculate Kikuchi lines (of allowed reflections)"""
890
+ tags_kikuchi = tags.copy()
891
+ tags_kikuchi['mistilt_alpha'] = 0
892
+ tags_kikuchi['mistilt_beta'] = 0
893
+ dif = atoms.info['diffraction']
894
+ k_0 = tags['incident_wave_vector']
895
+ k0_vector = tags['k0_vector']
896
+
897
+ for i in range(1): # tags['nearest_zone_axes']['amount']):
898
+ zone_tags = tags['nearest_zone_axes'][str(i)]
899
+ tags_kikuchi['zone_hkl'] = zone_tags['hkl']
900
+ if verbose:
901
+ print('Calculating Kikuchi lines for zone: ', zone_tags['hkl'])
902
+ tags_kikuchi['Laue_circle'] = dif['Laue_circle']
903
+ # Rotate to nearest zone axis
904
+ rotation_matrix = get_zone_rotation(tags_kikuchi)
905
+
906
+ g_kikuchi_all = np.dot(dif['allowed']['g_non_rot'], rotation_matrix)
907
+
908
+ zolz = abs(np.array(g_kikuchi_all)[:, 2]) < .1
909
+
910
+ g_kikuchi = g_kikuchi_all[zolz]
911
+ excitation_error = (k_0**2-np.linalg.norm(g_kikuchi - k0_vector, axis=1)**2)/(2*k_0)
912
+ # This is now a boolean array with True for all possible reflections
913
+ reflections = abs(excitation_error) < .01
914
+ g_kikuchi = g_kikuchi[reflections]
915
+ hkl_all = get_all_miller_indices(tags['hkl_max'])
916
+ hkl_kikuchi = hkl_all[zolz][reflections]
917
+ structure_factors = get_structure_factors(atoms, g_kikuchi)
918
+ allowed_kikuchi = np.absolute(structure_factors) > 0.000001
919
+
920
+ g_kikuchi = g_kikuchi[allowed_kikuchi]
921
+ hkl_kikuchi = hkl_kikuchi[allowed_kikuchi]
922
+
923
+ gd2 = g_kikuchi / 2.
924
+ gd2[:, 2] = 0.
925
+
926
+ # calculate and save line in Hough space coordinates (distance and theta)
927
+ slope2 = gd2[:, 0] / (gd2[:, 1] + 1e-20)
928
+ distance2 = np.sqrt(gd2[:, 0] * gd2[:, 0] + gd2[:, 1] * gd2[:, 1])
929
+ theta2 = np.arctan(slope2)
930
+
931
+ dif['Kikuchi'] = {}
932
+ dif['Kikuchi']['slope'] = slope2
933
+ dif['Kikuchi']['distance'] = distance2
934
+ dif['Kikuchi']['theta'] = theta2
935
+ dif['Kikuchi']['hkl'] = hkl_kikuchi
936
+ dif['Kikuchi']['g_hkl'] = g_kikuchi
937
+ dif['Kikuchi']['g_deficient'] = gd2
938
+ dif['Kikuchi']['min_dist'] = gd2 + dif['Laue_circle']
939
+
940
+ def kinematic_scattering(atoms, verbose=False):
941
+ """
942
+ All kinematic scattering calculation
943
+
944
+ Calculates Bragg spots, Kikuchi lines, excess, and deficient HOLZ lines
945
+
946
+ Parameters
947
+ ----------
948
+ atoms: ase.Atoms
949
+ object with crystal structure:
950
+ and with experimental parameters in info attribute:
951
+ 'acceleration_voltage_V', 'zone_hkl', 'Sg_max', 'hkl_max'
952
+ Optional parameters are:
953
+ 'mistilt', convergence_angle_mrad', and 'crystal_name'
954
+ verbose = True will give extended output of the calculation
955
+ verbose: boolean
956
+ default is False
957
+
958
+ Returns
959
+ -------
960
+ atoms:
961
+ There are three sub_dictionaries in info attribute:
962
+ ['allowed'], ['forbidden'], and ['HOLZ']
963
+ ['allowed'] and ['forbidden'] dictionaries contain:
964
+ ['sg'], ['hkl'], ['g'], ['structure factor'], ['intensities'],
965
+ ['ZOLZ'], ['FOLZ'], ['SOLZ'], ['HOLZ'], ['HHOLZ'], ['label'], and ['Laue_zone']
966
+ the ['HOLZ'] dictionary contains:
967
+ ['slope'], ['distance'], ['theta'], ['g_deficient'], ['g_excess'],
968
+ ['hkl'], ['intensities'],
969
+ ['ZOLZ'], ['FOLZ'], ['SOLZ'], ['HOLZ'], and ['HHOLZ']
970
+ Please note that the Kikuchi lines are the HOLZ lines of ZOLZ
971
+
972
+ There are also a few parameters stored in the main dictionary:
973
+ ['wave_length_nm'], ['reciprocal_unit_cell'], ['inner_potential_V'],
974
+ ['incident_wave_vector'],['volume'], ['theta'], ['phi'],
975
+ and ['incident_wave_vector_vacuum']
976
+ """
977
+
978
+ # Check sanity
979
+
980
+ atoms.info.setdefault('output', {})
981
+ atoms.info.setdefault('experimental', {})
982
+ output = atoms.info['output']
983
+ output['SpotPattern'] = True
984
+
985
+ if not check_sanity(atoms):
986
+ print('Input is not complete, stopping')
987
+ print('Try \'example()\' for example input')
988
+ return
989
+
990
+ tags = atoms.info['experimental']
991
+
992
+ tags['wave_length'] = get_wavelength(tags['acceleration_voltage_V'])
993
+
994
+ # ###########################################
995
+ # reciprocal_unit_cell
996
+ # #########################################
997
+ get_unit_cell(atoms, tags)
998
+
999
+ # ##############################################
1000
+ # Incident wave vector k0 in vacuum and material
1001
+ # ##############################################
1002
+
1003
+ k_0 = get_incident_wave_vector(atoms, tags, verbose)
1004
+ # Incident wave vector K0 in vacuum and material
1005
+ tags['convergence_angle_A-1'] = k_0*np.sin(tags['convergence_angle_mrad']/1000.)
1006
+
1007
+ # ############
1008
+ # Rotate
1009
+ # ############
1010
+
1011
+ # get rotation matrix to rotate zone axis onto z-axis
1012
+ get_zone_rotation(tags)
1013
+
1014
+ # rotate incident wave vector
1015
+ k0_unit_vector = np.array([0, 0, 1]) # incident unit wave vector
1016
+ k0_vector = k0_unit_vector * k_0 # incident wave vector
1017
+ tags['k0_vector'] = k0_vector
1018
+ find_sorted_bragg_reflections(atoms, tags, verbose)
1019
+
1020
+ # ##########################
1021
+ # Make pretty labels
1022
+ # ##########################
1023
+ dif = atoms.info['diffraction']
1024
+ dif['allowed']['label'] = make_pretty_labels(dif['allowed']['hkl'])
1025
+ dif['forbidden']['label'] = make_pretty_labels(dif['forbidden']['hkl'])
1026
+
1027
+ # Center of Laue Circle
1028
+ center_of_laue_circle(atoms, tags)
1029
+
1030
+ if verbose:
1031
+ output_verbose(atoms, tags)
1032
+
1033
+ # ###########################
1034
+ # Calculate Laue Zones (of allowed reflections)
1035
+ # ###########################
1036
+ calculate_laue_zones(atoms, tags, verbose)
1037
+
1038
+ # ##########################
1039
+ # Dynamically Activated forbidden reflections
1040
+ # ##########################
1041
+ get_dynamical_allowed(atoms, verbose)
1042
+
1043
+ # ###################################
1044
+ # Calculate HOLZ and Kikuchi Lines #
1045
+ # ###################################
1046
+ calculate_holz(atoms, tags)
1047
+
1048
+ ####################################
1049
+ # Calculate HOLZ and Kikuchi Lines #
1050
+ ####################################
1051
+ calculate_kikuchi(atoms, tags, verbose)
1052
+
1053
+ if verbose:
1054
+ print('pyTEMlib\'s \"kinematic_scattering\" finished')
1055
+
1056
+
1057
+ def make_pretty_labels(hkls, hex_label=False):
1058
+ """Make pretty labels
1059
+
1060
+ Parameters
1061
+ ----------
1062
+ hkls: np.ndarray
1063
+ a numpy array with all the Miller indices to be labeled
1064
+ hex_label: boolean - optional
1065
+ if True this will make for Miller indices.
1066
+
1067
+ Returns
1068
+ -------
1069
+ hkl_label: list
1070
+ list of labels in Latex format
1071
+ """
1072
+ hkl_label = []
1073
+ for i in range(len(hkls)):
1074
+ h, k, l = np.array(hkls)[i]
1075
+ if h < 0:
1076
+ h_string = r'[$\bar {' + str(int(-h)) + '},'
1077
+ else:
1078
+ h_string = r'[$\bar {' + str(int(h)) + '},'
1079
+ if k < 0:
1080
+ k_string = r'\bar {' + str(int(-k)) + '},'
1081
+ else:
1082
+ k_string = str(int(k)) + ','
1083
+ if hex_label:
1084
+ ii = -(h + k)
1085
+ if ii < 0:
1086
+ k_string = k_string + r'\bar {' + str(int(-ii)) + '},'
1087
+ else:
1088
+ k_string = k_string + str(int(ii)) + ','
1089
+ if l < 0:
1090
+ l_string = r'\bar {' + str(int(-l)) + '} $]'
1091
+ else:
1092
+ l_string = str(int(l)) + '} $]'
1093
+ label = h_string + k_string + l_string
1094
+ hkl_label.append(label)
1095
+ return hkl_label
1096
+
1097
+
1098
+ def feq(element, q):
1099
+ """Atomic form factor parametrized in 1/Angstrom but converted to 1/Angstrom
1100
+
1101
+ The atomic form factor is from Kirkland: Advanced Computing in
1102
+ Electron Microscopy 2nd edition, Appendix C.
1103
+ From Appendix C of Kirkland, "Advanced Computing in Electron Microscopy", 3rd ed.
1104
+ Calculation of electron form factor for specific q:
1105
+ Using equation Kirkland C.15
1106
+
1107
+ Parameters
1108
+ ----------
1109
+ element: string
1110
+ element name
1111
+ q: float
1112
+ magnitude of scattering vector in 1/Angstrom -- (=> exp(-i*g.r),
1113
+ physics negative convention)
1114
+
1115
+ Returns
1116
+ -------
1117
+ fL+fG: float
1118
+ atomic scattering vector
1119
+ """
1120
+
1121
+ if not isinstance(element, str):
1122
+ raise TypeError('Element has to be a string')
1123
+ if element not in electronFF:
1124
+ if len(element) > 2:
1125
+ raise TypeError('Please use standard convention for element ',
1126
+ 'abbreviation with not more than two letters')
1127
+ raise TypeError('Element {element} not known to electron diffraction should')
1128
+ if not isinstance(q, (float, int)):
1129
+ raise TypeError('Magnitude of scattering vector has to be a number of type float')
1130
+
1131
+ # q is in magnitude of scattering vector in 1/A -- (=> exp(-i*g.r),
1132
+ # physics negative convention)
1133
+ param = electronFF[element]
1134
+ f_lorentzian = 0
1135
+ f_gauss = 0
1136
+ for i in range(3):
1137
+ f_lorentzian += param['fa'][i]/(q**2 + param['fb'][i])
1138
+ f_gauss += param['fc'][i]*np.exp(-q**2 * param['fd'][i])
1139
+
1140
+ # Conversion factor from scattering factors to volts. h^2/(2pi*m0*e),
1141
+ # see e.g. Kirkland eqn. C.5
1142
+ # !NB RVolume is already in A unlike RPlanckConstant
1143
+ # scattering_factor_to_volts=(PlanckConstant**2)*(AngstromConversion**2)
1144
+ # /(2*np.pi*ElectronMass*ElectronCharge)
1145
+ return f_lorentzian+f_gauss # * scattering_factor_to_volts