pyTEMlib 0.2025.4.1__py3-none-any.whl → 0.2025.9.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.
Potentially problematic release.
This version of pyTEMlib might be problematic. Click here for more details.
- build/lib/pyTEMlib/__init__.py +33 -0
- build/lib/pyTEMlib/animation.py +640 -0
- build/lib/pyTEMlib/atom_tools.py +238 -0
- build/lib/pyTEMlib/config_dir.py +31 -0
- build/lib/pyTEMlib/crystal_tools.py +1219 -0
- build/lib/pyTEMlib/diffraction_plot.py +756 -0
- build/lib/pyTEMlib/dynamic_scattering.py +293 -0
- build/lib/pyTEMlib/eds_tools.py +826 -0
- build/lib/pyTEMlib/eds_xsections.py +432 -0
- build/lib/pyTEMlib/eels_tools/__init__.py +44 -0
- build/lib/pyTEMlib/eels_tools/core_loss_tools.py +751 -0
- build/lib/pyTEMlib/eels_tools/eels_database.py +134 -0
- build/lib/pyTEMlib/eels_tools/low_loss_tools.py +655 -0
- build/lib/pyTEMlib/eels_tools/peak_fit_tools.py +175 -0
- build/lib/pyTEMlib/eels_tools/zero_loss_tools.py +264 -0
- build/lib/pyTEMlib/file_reader.py +274 -0
- build/lib/pyTEMlib/file_tools.py +811 -0
- build/lib/pyTEMlib/get_bote_salvat.py +69 -0
- build/lib/pyTEMlib/graph_tools.py +1153 -0
- build/lib/pyTEMlib/graph_viz.py +599 -0
- build/lib/pyTEMlib/image/__init__.py +37 -0
- build/lib/pyTEMlib/image/image_atoms.py +270 -0
- build/lib/pyTEMlib/image/image_clean.py +197 -0
- build/lib/pyTEMlib/image/image_distortion.py +299 -0
- build/lib/pyTEMlib/image/image_fft.py +277 -0
- build/lib/pyTEMlib/image/image_graph.py +926 -0
- build/lib/pyTEMlib/image/image_registration.py +316 -0
- build/lib/pyTEMlib/image/image_utilities.py +309 -0
- build/lib/pyTEMlib/image/image_window.py +421 -0
- build/lib/pyTEMlib/image_tools.py +699 -0
- build/lib/pyTEMlib/interactive_image.py +1 -0
- build/lib/pyTEMlib/kinematic_scattering.py +1196 -0
- build/lib/pyTEMlib/microscope.py +61 -0
- build/lib/pyTEMlib/probe_tools.py +906 -0
- build/lib/pyTEMlib/sidpy_tools.py +153 -0
- build/lib/pyTEMlib/simulation_tools.py +104 -0
- build/lib/pyTEMlib/test.py +437 -0
- build/lib/pyTEMlib/utilities.py +314 -0
- build/lib/pyTEMlib/version.py +5 -0
- build/lib/pyTEMlib/xrpa_x_sections.py +20976 -0
- pyTEMlib/__init__.py +25 -3
- pyTEMlib/animation.py +31 -22
- pyTEMlib/atom_tools.py +29 -34
- pyTEMlib/config_dir.py +2 -28
- pyTEMlib/crystal_tools.py +129 -165
- pyTEMlib/eds_tools.py +559 -342
- pyTEMlib/eds_xsections.py +432 -0
- pyTEMlib/eels_tools/__init__.py +44 -0
- pyTEMlib/eels_tools/core_loss_tools.py +751 -0
- pyTEMlib/eels_tools/eels_database.py +134 -0
- pyTEMlib/eels_tools/low_loss_tools.py +655 -0
- pyTEMlib/eels_tools/peak_fit_tools.py +175 -0
- pyTEMlib/eels_tools/zero_loss_tools.py +264 -0
- pyTEMlib/file_reader.py +274 -0
- pyTEMlib/file_tools.py +260 -1130
- pyTEMlib/get_bote_salvat.py +69 -0
- pyTEMlib/graph_tools.py +101 -174
- pyTEMlib/graph_viz.py +150 -0
- pyTEMlib/image/__init__.py +37 -0
- pyTEMlib/image/image_atoms.py +270 -0
- pyTEMlib/image/image_clean.py +197 -0
- pyTEMlib/image/image_distortion.py +299 -0
- pyTEMlib/image/image_fft.py +277 -0
- pyTEMlib/image/image_graph.py +926 -0
- pyTEMlib/image/image_registration.py +316 -0
- pyTEMlib/image/image_utilities.py +309 -0
- pyTEMlib/image/image_window.py +421 -0
- pyTEMlib/image_tools.py +154 -915
- pyTEMlib/kinematic_scattering.py +1 -1
- pyTEMlib/probe_tools.py +1 -1
- pyTEMlib/test.py +437 -0
- pyTEMlib/utilities.py +314 -0
- pyTEMlib/version.py +2 -3
- pyTEMlib/xrpa_x_sections.py +14 -10
- {pytemlib-0.2025.4.1.dist-info → pytemlib-0.2025.9.1.dist-info}/METADATA +13 -16
- pytemlib-0.2025.9.1.dist-info/RECORD +86 -0
- {pytemlib-0.2025.4.1.dist-info → pytemlib-0.2025.9.1.dist-info}/WHEEL +1 -1
- pytemlib-0.2025.9.1.dist-info/top_level.txt +6 -0
- pyTEMlib/core_loss_widget.py +0 -721
- pyTEMlib/eels_dialog.py +0 -754
- pyTEMlib/eels_dialog_utilities.py +0 -1199
- pyTEMlib/eels_tools.py +0 -2359
- pyTEMlib/file_tools_qt.py +0 -193
- pyTEMlib/image_dialog.py +0 -158
- pyTEMlib/image_dlg.py +0 -146
- pyTEMlib/info_widget.py +0 -1086
- pyTEMlib/info_widget3.py +0 -1120
- pyTEMlib/low_loss_widget.py +0 -479
- pyTEMlib/peak_dialog.py +0 -1129
- pyTEMlib/peak_dlg.py +0 -286
- pytemlib-0.2025.4.1.dist-info/RECORD +0 -38
- pytemlib-0.2025.4.1.dist-info/top_level.txt +0 -1
- {pytemlib-0.2025.4.1.dist-info → pytemlib-0.2025.9.1.dist-info}/entry_points.txt +0 -0
- {pytemlib-0.2025.4.1.dist-info → pytemlib-0.2025.9.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"""utility functions for sidpy; will move to sidpy"""
|
|
2
|
+
import numpy as np
|
|
3
|
+
import sidpy
|
|
4
|
+
import h5py
|
|
5
|
+
import pyNSID
|
|
6
|
+
import os
|
|
7
|
+
import ipywidgets as widgets
|
|
8
|
+
from IPython.display import display
|
|
9
|
+
import json
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ChooseDataset(object):
|
|
13
|
+
"""Widget to select dataset object """
|
|
14
|
+
|
|
15
|
+
def __init__(self, input_object, show_dialog=True):
|
|
16
|
+
if isinstance(input_object, sidpy.Dataset):
|
|
17
|
+
if isinstance(input_object.h5_dataset, h5py.Dataset):
|
|
18
|
+
self.current_channel = input_object.h5_dataset.parent
|
|
19
|
+
elif isinstance(input_object, h5py.Group):
|
|
20
|
+
self.current_channel = input_object
|
|
21
|
+
elif isinstance(input_object, h5py.Dataset):
|
|
22
|
+
self.current_channel = input_object.parent
|
|
23
|
+
else:
|
|
24
|
+
raise ValueError('Need hdf5 group or sidpy Dataset to determine image choices')
|
|
25
|
+
self.dataset_names = []
|
|
26
|
+
self.dataset_list = []
|
|
27
|
+
self.dataset_type = None
|
|
28
|
+
self.dataset = None
|
|
29
|
+
self.reader = pyNSID.NSIDReader(self.current_channel.file.filename)
|
|
30
|
+
|
|
31
|
+
self.get_dataset_list()
|
|
32
|
+
self.select_image = widgets.Dropdown(options=self.dataset_names,
|
|
33
|
+
value=self.dataset_names[0],
|
|
34
|
+
description='select dataset:',
|
|
35
|
+
disabled=False,
|
|
36
|
+
button_style='')
|
|
37
|
+
if show_dialog:
|
|
38
|
+
display(self.select_image)
|
|
39
|
+
|
|
40
|
+
self.select_image.observe(self.set_dataset, names='value')
|
|
41
|
+
self.set_dataset(0)
|
|
42
|
+
self.select_image.index = (len(self.dataset_names) - 1)
|
|
43
|
+
|
|
44
|
+
def get_dataset_list(self):
|
|
45
|
+
""" Get by Log number sorted list of datasets"""
|
|
46
|
+
datasets = self.reader.read()
|
|
47
|
+
order = []
|
|
48
|
+
for dset in datasets:
|
|
49
|
+
if self.dataset_type is None or dset.data_type == self.data_type:
|
|
50
|
+
if 'Log' in dset.title:
|
|
51
|
+
position = dset.title.find('Log_') + 4
|
|
52
|
+
order.append(int(dset.title[position:position + 3])+1)
|
|
53
|
+
else:
|
|
54
|
+
order.append(0)
|
|
55
|
+
for index in np.argsort(order):
|
|
56
|
+
dset = datasets[index]
|
|
57
|
+
self.dataset_names.append('/'.join(dset.title.replace('-', '_').split('/')[-1:]))
|
|
58
|
+
self.dataset_list.append(dset)
|
|
59
|
+
|
|
60
|
+
def set_dataset(self, b):
|
|
61
|
+
index = self.select_image.index
|
|
62
|
+
self.dataset = self.dataset_list[index]
|
|
63
|
+
# Find
|
|
64
|
+
self.dataset.title = self.dataset.title.split('/')[-1]
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def get_dimensions_by_order(dims_in, dataset):
|
|
68
|
+
"""get dimension
|
|
69
|
+
|
|
70
|
+
Parameters
|
|
71
|
+
----------
|
|
72
|
+
dims_in: int or list of int
|
|
73
|
+
the dimensions by numerical order
|
|
74
|
+
dataset: sidpy.Dataset
|
|
75
|
+
|
|
76
|
+
Returns
|
|
77
|
+
-------
|
|
78
|
+
dims_out: list of dimensions
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
if isinstance(dims_in, int):
|
|
82
|
+
dims_in = [dims_in]
|
|
83
|
+
dims_out = []
|
|
84
|
+
for item in dims_in:
|
|
85
|
+
if isinstance(item, int):
|
|
86
|
+
if item in dataset._axes:
|
|
87
|
+
dims_out.append([item, dataset._axes[item]])
|
|
88
|
+
return dims_out
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def get_dimensions_by_type(dims_in, dataset):
|
|
92
|
+
""" get dimension by dimension_type name
|
|
93
|
+
|
|
94
|
+
Parameters
|
|
95
|
+
----------
|
|
96
|
+
dims_in: dimension_type or list of dimension_types
|
|
97
|
+
the dimensions by numerical order
|
|
98
|
+
dataset: sidpy.Dataset
|
|
99
|
+
|
|
100
|
+
Returns
|
|
101
|
+
-------
|
|
102
|
+
dims_out: list of dimensions
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
if isinstance(dims_in, (str, sidpy.DimensionType)):
|
|
106
|
+
dims_in = [dims_in]
|
|
107
|
+
for i in range(len(dims_in)):
|
|
108
|
+
if isinstance(dims_in[i], str):
|
|
109
|
+
dims_in[i] = sidpy.DimensionType[dims_in[i].upper()]
|
|
110
|
+
dims_out = []
|
|
111
|
+
for dim, axis in dataset._axes.items():
|
|
112
|
+
if axis.dimension_type in dims_in:
|
|
113
|
+
dims_out.append([dim, dataset._axes[dim]])
|
|
114
|
+
return dims_out
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def make_dummy_dataset(value_type):
|
|
118
|
+
"""Make a dummy sidpy.Dataset """
|
|
119
|
+
|
|
120
|
+
assert isinstance(value_type, sidpy.DataType)
|
|
121
|
+
if type == sidpy.DataType.SPECTRUM:
|
|
122
|
+
dataset = sidpy.Dataset.from_array(np.arange(100))
|
|
123
|
+
dataset.data_type = 'spectrum'
|
|
124
|
+
dataset.units = 'counts'
|
|
125
|
+
dataset.quantity = 'intensity'
|
|
126
|
+
|
|
127
|
+
dataset.set_dimension(0, sidpy.Dimension(np.arange(dataset.shape[0]) + 70, name='energy_scale'))
|
|
128
|
+
dataset.dim_0.dimension_type = 'spectral'
|
|
129
|
+
dataset.dim_0.units = 'eV'
|
|
130
|
+
dataset.dim_0.quantity = 'energy loss'
|
|
131
|
+
else:
|
|
132
|
+
raise NotImplementedError('not implemented')
|
|
133
|
+
return dataset
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def plot(dataset):
|
|
137
|
+
dataset.plot()
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def get_image_dims(dataset):
|
|
141
|
+
"""Get all spatial dimensions"""
|
|
142
|
+
|
|
143
|
+
image_dims = []
|
|
144
|
+
for dim, axis in dataset._axes.items():
|
|
145
|
+
if axis.dimension_type == sidpy.DimensionType.SPATIAL:
|
|
146
|
+
image_dims.append(dim)
|
|
147
|
+
return image_dims
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def get_extent(dataset):
|
|
151
|
+
"""get extent to plot with matplotlib"""
|
|
152
|
+
image_dims = get_image_dims(dataset)
|
|
153
|
+
return dataset.get_extent(image_dims)
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
""" dft simulations tools
|
|
2
|
+
|
|
3
|
+
Part of pyTEMlib
|
|
4
|
+
by Gerd Duscher
|
|
5
|
+
created 10/29/2020
|
|
6
|
+
|
|
7
|
+
Supports the conversion of DFT data to simulated EELS spectra
|
|
8
|
+
|
|
9
|
+
- exciting_get_spectra: importing dielectric function from the exciting program
|
|
10
|
+
- final_state_broadening: apply final state broadening to loss-spectra
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
from lxml import etree
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def exciting_get_spectra(file):
|
|
18
|
+
"""get EELS spectra from exciting calculation"""
|
|
19
|
+
|
|
20
|
+
tags = {'data': {}}
|
|
21
|
+
|
|
22
|
+
tree = etree.ElementTree(file=file)
|
|
23
|
+
root = tree.getroot()
|
|
24
|
+
|
|
25
|
+
data = tags['data']
|
|
26
|
+
|
|
27
|
+
if root.tag in ['loss', 'dielectric']:
|
|
28
|
+
print(' reading ', root.tag, ' function from file ', file)
|
|
29
|
+
# print(root[0].tag, root[0].text)
|
|
30
|
+
map_def = root[0]
|
|
31
|
+
i = 0
|
|
32
|
+
v = {}
|
|
33
|
+
for child_of_root in map_def:
|
|
34
|
+
data[child_of_root.tag] = child_of_root.attrib
|
|
35
|
+
v[child_of_root.tag] = []
|
|
36
|
+
i += 1
|
|
37
|
+
|
|
38
|
+
for elem in tree.iter(tag='map'):
|
|
39
|
+
m_dict = elem.attrib
|
|
40
|
+
for key in m_dict:
|
|
41
|
+
v[key].append(float(m_dict[key]))
|
|
42
|
+
|
|
43
|
+
for key in data:
|
|
44
|
+
data[key]['data'] = np.array(v[key])
|
|
45
|
+
data['type'] = root.tag+' function'
|
|
46
|
+
return tags
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def final_state_broadening(x, y, start, instrument):
|
|
50
|
+
"""Final state smearing of ELNES edges
|
|
51
|
+
|
|
52
|
+
Parameters
|
|
53
|
+
----------
|
|
54
|
+
x: numpy array
|
|
55
|
+
x or energy loss axis of density of states
|
|
56
|
+
y: numpy array
|
|
57
|
+
y or intensity axis of density of states
|
|
58
|
+
start: float
|
|
59
|
+
start energy of edge
|
|
60
|
+
instrument: float
|
|
61
|
+
instrument broadening
|
|
62
|
+
|
|
63
|
+
Return
|
|
64
|
+
------
|
|
65
|
+
out_data: numpy array
|
|
66
|
+
smeared intensity according to final state and instrument broadening
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
# Getting the smearing
|
|
70
|
+
a_i = 107.25*5
|
|
71
|
+
b_i = 0.04688*2.
|
|
72
|
+
x = np.array(x)-start
|
|
73
|
+
zero = int(-x[0]/(x[1]-x[0]))+1
|
|
74
|
+
smear_i = x*0.0
|
|
75
|
+
smear_i[zero:-1] = (a_i/x[zero:-1]**2)+b_i*np.sqrt(x[zero:-1])
|
|
76
|
+
h_bar = 6.58e-16 # h/2pi
|
|
77
|
+
pre = 1.0
|
|
78
|
+
m = 6.58e-31
|
|
79
|
+
smear = x*0.0
|
|
80
|
+
smear[zero:-1] = pre*(h_bar/(smear_i[zero:-1]*0.000000001))*np.sqrt((2*x[zero:-1]*1.6E-19)/m)
|
|
81
|
+
|
|
82
|
+
def lorentzian(xx, pp):
|
|
83
|
+
yy = ((0.5 * pp[1]/3.14)/((xx-pp[0])**2 + ((pp[1]/2)**2)))
|
|
84
|
+
return yy/sum(yy)
|
|
85
|
+
|
|
86
|
+
p = [0, instrument]
|
|
87
|
+
in_data = y.copy()
|
|
88
|
+
out_data = np.array(y)*0.0
|
|
89
|
+
for i in range(zero+5, len(x)):
|
|
90
|
+
p[0] = x[i]
|
|
91
|
+
p[1] = smear[i]/1.0
|
|
92
|
+
lor = lorentzian(x+1e-9, p)
|
|
93
|
+
out_data[i] = sum(in_data*lor)
|
|
94
|
+
if np.isnan(out_data[i]):
|
|
95
|
+
out_data[i] = 0.0
|
|
96
|
+
|
|
97
|
+
p[1] = instrument
|
|
98
|
+
in_data = out_data.copy()
|
|
99
|
+
for i in range(zero-5, len(x)):
|
|
100
|
+
p[0] = x[i]
|
|
101
|
+
lor = lorentzian(x+1e-9, p)
|
|
102
|
+
out_data[i] = sum(in_data*lor)
|
|
103
|
+
# print(out_data[i],in_data[i], lor[i],in_data[i-1], lor[i-1], )
|
|
104
|
+
return out_data
|
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
|
|
3
|
+
from matplotlib.pyplot import plot
|
|
4
|
+
sys.path.insert(0, './')
|
|
5
|
+
import pyTEMlib
|
|
6
|
+
print(pyTEMlib.__version__)
|
|
7
|
+
|
|
8
|
+
filename = "C:\\Users\\gduscher\\Desktop\\ESL-506-15kV.esl"
|
|
9
|
+
filename = "C:\\Users\\gduscher\\.pyTEMlib\\k-factors-Spectra300UTK200keV.csv"
|
|
10
|
+
# pyTEMlib.eds_tools.read_esl_k_factors("C:\\Users\\gduscher\\Desktop\\ESL-506-15kV.esl")
|
|
11
|
+
|
|
12
|
+
# print(pyTEMlib.eds_tools.read_csv_k_factors(filename))
|
|
13
|
+
# pp =pyTEMlib.eds_tools.convert_k_factor_file(filename)
|
|
14
|
+
# print('read bruker k-factors')
|
|
15
|
+
#
|
|
16
|
+
# print(pyTEMlib.eds_tools.read_k_factors('k_factors_Thermo_200keV.json'))
|
|
17
|
+
|
|
18
|
+
# print(pyTEMlib.eds_tools.get_k_factor_files())
|
|
19
|
+
import os
|
|
20
|
+
file = os.path.join(pyTEMlib.config_dir.config_path, 'Dirac_GOS.gosh')
|
|
21
|
+
import h5py
|
|
22
|
+
|
|
23
|
+
"""
|
|
24
|
+
element = 'Si'
|
|
25
|
+
edge = 'L3'
|
|
26
|
+
with h5py.File(file, 'r') as gos_file:
|
|
27
|
+
gos = gos_file[element][edge]['data'][:].squeeze().T
|
|
28
|
+
free_energies = gos_file[element][edge]['free_energies'][:][ :] # two dimensional array
|
|
29
|
+
q_axis = gos_file[element][edge]['q'][:] # in [1/m]
|
|
30
|
+
ionization_energy = gos_file[element][edge]['metadata'].attrs['ionization_energy']
|
|
31
|
+
print (ionization_energy)
|
|
32
|
+
|
|
33
|
+
import matplotlib.pylab as plt
|
|
34
|
+
import scipy
|
|
35
|
+
import numpy as np
|
|
36
|
+
|
|
37
|
+
x = free_energies
|
|
38
|
+
y = q_axis
|
|
39
|
+
y = y/min(y)
|
|
40
|
+
z = gos
|
|
41
|
+
print(x.shape, y.shape, z.shape)
|
|
42
|
+
X = np.linspace(min(x), max(x))
|
|
43
|
+
Y = np.linspace(min(y), max(y))
|
|
44
|
+
print(min(y), max(y))
|
|
45
|
+
grid_x, grid_y = np.meshgrid(X, Y)
|
|
46
|
+
|
|
47
|
+
interp = scipy.interpolate.griddata(zip(x, y), z, (grid_x, grid_y), method='linear', fill_value=0)
|
|
48
|
+
print(interp.shape, print())
|
|
49
|
+
|
|
50
|
+
plt.imshow(interp[:,:,0].T, extent=(min(x), max(x), min(y), max(y)), origin='lower')
|
|
51
|
+
|
|
52
|
+
plt.colorbar()
|
|
53
|
+
|
|
54
|
+
plt.show()
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
plt.figure()
|
|
58
|
+
plt.pcolormesh( q_axis/1e10,free_energies+ionization_energy, gos)
|
|
59
|
+
plt.xlabel('q [1/A]')
|
|
60
|
+
plt.ylabel('Energy above ionization [eV]')
|
|
61
|
+
plt.colorbar()
|
|
62
|
+
plt.title('GOS for ' + element + ' ' + edge)
|
|
63
|
+
plt.show()
|
|
64
|
+
"""
|
|
65
|
+
import matplotlib.pylab as plt
|
|
66
|
+
|
|
67
|
+
import scipy
|
|
68
|
+
import numpy as np
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def getinterpolatedgos(E, q, E_axis, q_axis, GOSmatrix):
|
|
76
|
+
"""
|
|
77
|
+
Gets the interpolated value of the GOS from the E and q value.
|
|
78
|
+
"""
|
|
79
|
+
index_q = np.searchsorted(q_axis, q, side='left')
|
|
80
|
+
index_E = np.searchsorted(E_axis, E, side='left')
|
|
81
|
+
|
|
82
|
+
if index_E == 0:
|
|
83
|
+
return 0
|
|
84
|
+
if index_E == E_axis.size:
|
|
85
|
+
return 0.
|
|
86
|
+
if index_q == 0:
|
|
87
|
+
return GOSmatrix[index_E, 0]
|
|
88
|
+
if index_q == q_axis.size:
|
|
89
|
+
return 0.0
|
|
90
|
+
|
|
91
|
+
dE = E_axis[index_E] - E_axis[index_E - 1]
|
|
92
|
+
dq = q_axis[index_q] - q_axis[index_q - 1]
|
|
93
|
+
|
|
94
|
+
distE = E - E_axis[index_E - 1]
|
|
95
|
+
distq = q - q_axis[index_q - 1]
|
|
96
|
+
|
|
97
|
+
r = GOSmatrix[index_E - 1, index_q - 1] * (1 / (dE * dq)) * (dE - distE) * (dq - distq)
|
|
98
|
+
r += GOSmatrix[index_E - 1, index_q] * (1 / (dE * dq)) * (dE - distE) * (distq)
|
|
99
|
+
r += GOSmatrix[index_E, index_q - 1] * (1 / (dE * dq)) * (distE) * (dq - distq)
|
|
100
|
+
r += GOSmatrix[index_E, index_q] * (1 / (dE * dq)) * (distE) * (distq)
|
|
101
|
+
return r
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def gaussian(E, integral, x0, sigma):
|
|
105
|
+
# A = integral / (np.sqrt(2 * np.pi) * sigma)
|
|
106
|
+
g = np.exp(-0.5 * (E - x0)**2 / sigma**2)
|
|
107
|
+
g = integral * g / g.sum()
|
|
108
|
+
return g
|
|
109
|
+
|
|
110
|
+
def getinterpolatedq(q, GOSarray, q_axis):
|
|
111
|
+
"""
|
|
112
|
+
Gets the interpolated value of the GOS array as a function of q.
|
|
113
|
+
Usefull for the bounded states
|
|
114
|
+
|
|
115
|
+
Parameters
|
|
116
|
+
----------
|
|
117
|
+
q: float
|
|
118
|
+
The q from the GOS should be interpolated
|
|
119
|
+
GOSarray:
|
|
120
|
+
dddd
|
|
121
|
+
q_axis: numpy array
|
|
122
|
+
The q axis on which the GOS is calculated
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
Returns
|
|
126
|
+
-------
|
|
127
|
+
interpolated GOS matrix
|
|
128
|
+
|
|
129
|
+
"""
|
|
130
|
+
index_q = np.searchsorted(q_axis, q, side='left')
|
|
131
|
+
|
|
132
|
+
if index_q == 0:
|
|
133
|
+
return GOSarray[0]
|
|
134
|
+
if index_q == q_axis.size:
|
|
135
|
+
return 0.0
|
|
136
|
+
|
|
137
|
+
dq = q_axis[index_q] - q_axis[index_q - 1]
|
|
138
|
+
|
|
139
|
+
distq = q - q_axis[index_q - 1]
|
|
140
|
+
|
|
141
|
+
r0 = GOSarray[index_q - 1] * (1/dq) * (dq-distq)
|
|
142
|
+
r1 = GOSarray[index_q] * (1/dq) * (distq)
|
|
143
|
+
|
|
144
|
+
return r0 + r1
|
|
145
|
+
|
|
146
|
+
def correction_factor_kohl(alpha, beta, theta, min_alpha=1e-6):
|
|
147
|
+
"""
|
|
148
|
+
STILL NEEDS TO BE VALIDATED
|
|
149
|
+
Calculates the correction factor when using a convergent
|
|
150
|
+
probe. For probes having is convergence angle smaller than
|
|
151
|
+
min_alpha no correction is applied.
|
|
152
|
+
Ultramicroscopy 16 (1985) 265-268:
|
|
153
|
+
https://doi.org/10.1016/0304-3991(85)90081-6
|
|
154
|
+
Parameters
|
|
155
|
+
----------
|
|
156
|
+
alpha : float
|
|
157
|
+
Convergence angle in radians
|
|
158
|
+
beta : float
|
|
159
|
+
Collection angle in radians
|
|
160
|
+
theta : float
|
|
161
|
+
The angle for which the correction factor should be calculated
|
|
162
|
+
min_alpha : float
|
|
163
|
+
Minimum convergence angle for which the correction is applied
|
|
164
|
+
|
|
165
|
+
Returns
|
|
166
|
+
-------
|
|
167
|
+
corr_factor : float
|
|
168
|
+
correction factor used in the integration
|
|
169
|
+
"""
|
|
170
|
+
if alpha < min_alpha:
|
|
171
|
+
corr_factor = 1.
|
|
172
|
+
elif theta <= np.abs(alpha - beta):
|
|
173
|
+
min_thetasq = min(alpha**2, beta**2)
|
|
174
|
+
corr_factor = min_thetasq / alpha**2
|
|
175
|
+
else:
|
|
176
|
+
x = (alpha**2 + theta**2 - beta**2) / (2. * alpha * theta)
|
|
177
|
+
y = (beta**2 + theta**2 - alpha**2) / (2. * beta * theta)
|
|
178
|
+
wortel = np.sqrt(4 * alpha**2 * beta**2 - (alpha**2 + beta**2 - theta**2)**2)
|
|
179
|
+
corr_factor = (1 / np.pi) * (np.arccos(x) + (beta**2 / alpha**2 * np.arccos(y)) - (
|
|
180
|
+
1 / (2 * alpha**2) * wortel))
|
|
181
|
+
return corr_factor
|
|
182
|
+
|
|
183
|
+
def get_dirac_X_section(element = 'Si', edge = 'L3', file = file,
|
|
184
|
+
q_steps=100, E0=200000, beta=0.100, alpha=0):
|
|
185
|
+
""" Calculates the cross section from the GOS matrix"""
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
with h5py.File(file, 'r') as gos_file:
|
|
189
|
+
GOSmatrix = gos_file[element][edge]['data'][:].squeeze().T
|
|
190
|
+
free_energies = gos_file[element][edge]['free_energies'][:][ :] # two dimensional array
|
|
191
|
+
q_axis = gos_file[element][edge]['q'][:] # in [1/m]
|
|
192
|
+
ek = gos_file[element][edge]['metadata'].attrs['ionization_energy']
|
|
193
|
+
|
|
194
|
+
energy_axis = np.linspace(50, 850, int(800/5))
|
|
195
|
+
shell_occupancy = 1
|
|
196
|
+
pref = 1e28 * shell_occupancy
|
|
197
|
+
|
|
198
|
+
e = scipy.constants.e
|
|
199
|
+
c = scipy.constants.c
|
|
200
|
+
m = scipy.constants.electron_mass
|
|
201
|
+
a_0 = scipy.constants.physical_constants['Bohr radius'][0]
|
|
202
|
+
gamma = 1 + e * E0 / (m * c ** 2)
|
|
203
|
+
|
|
204
|
+
effective_incident_energy = E0 * (1 + gamma) / (2 * gamma**2)
|
|
205
|
+
T = effective_incident_energy
|
|
206
|
+
R = scipy.constants.Rydberg
|
|
207
|
+
|
|
208
|
+
bool0 = free_energies < 0
|
|
209
|
+
Ebound = free_energies[bool0] + ek
|
|
210
|
+
|
|
211
|
+
dsigma_dE = np.zeros(energy_axis.shape)
|
|
212
|
+
dsigma_dE_bound = np.zeros(energy_axis.shape)
|
|
213
|
+
sigma = 2*(energy_axis[1] - energy_axis[0])
|
|
214
|
+
rel_energy_axis = free_energies + ek
|
|
215
|
+
|
|
216
|
+
for i in range(Ebound.size):
|
|
217
|
+
E = Ebound[i]
|
|
218
|
+
integral = 0
|
|
219
|
+
# the bounded states are differently interpolated
|
|
220
|
+
qa0sq_min = E ** 2 / (4 * R * T) + (E ** 3) / (8 * gamma ** 3 * R * T ** 2)
|
|
221
|
+
qa0sq_max = qa0sq_min + 4 * gamma ** 2 * (T / R) * (np.sin((beta + alpha) / 2)) ** 2
|
|
222
|
+
logqa0sq_axis = np.linspace(np.log(qa0sq_min), np.log(qa0sq_max),
|
|
223
|
+
q_steps)
|
|
224
|
+
lnqa0sqstep = (logqa0sq_axis[1] - logqa0sq_axis[0])
|
|
225
|
+
print(i, logqa0sq_axis, lnqa0sqstep)
|
|
226
|
+
for j in range(logqa0sq_axis.size):
|
|
227
|
+
q = np.sqrt(np.exp(logqa0sq_axis[j])) / scipy.constants.physical_constants['Bohr radius'][0]
|
|
228
|
+
theta = 2. * np.sqrt(np.abs( R * (np.exp(logqa0sq_axis[j]) - qa0sq_min) /
|
|
229
|
+
(4. * gamma**2 * T)))
|
|
230
|
+
GOSarray = GOSmatrix[i, :]
|
|
231
|
+
df_dE = getinterpolatedq(q, GOSarray, q_axis)
|
|
232
|
+
|
|
233
|
+
# integral+= df_dE*lnqa0sqstep
|
|
234
|
+
integral += df_dE * lnqa0sqstep * correction_factor_kohl(alpha, beta, theta)
|
|
235
|
+
|
|
236
|
+
sig = 4 * np.pi * a_0 ** 2 * (R / E) * (R / T) * integral
|
|
237
|
+
dsigma_dE_bound += gaussian(energy_axis, sig, E, sigma)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
# the for loop over the bound states
|
|
241
|
+
for i in range(energy_axis.size):
|
|
242
|
+
E = energy_axis[i]
|
|
243
|
+
integral = 0
|
|
244
|
+
if (E > ek) & (E <= rel_energy_axis[-1]):
|
|
245
|
+
qa0sq_min = E**2 / (4 * R * T) + (E**3) / (8 * gamma**3 * R * T**2)
|
|
246
|
+
qa0sq_max = qa0sq_min + 4 * gamma**2 * (T / R) * (np.sin((beta + alpha) / 2))**2
|
|
247
|
+
logqa0sq_axis = np.linspace(np.log(qa0sq_min), np.log(qa0sq_max), q_steps)
|
|
248
|
+
lnqa0sqstep = (logqa0sq_axis[1] - logqa0sq_axis[0])
|
|
249
|
+
for j in range(logqa0sq_axis.size):
|
|
250
|
+
q = np.sqrt(np.exp(logqa0sq_axis[j])) / a_0
|
|
251
|
+
theta = 2. * np.sqrt(np.abs(
|
|
252
|
+
R * (np.exp(logqa0sq_axis[j]) - qa0sq_min) / (
|
|
253
|
+
4. * gamma ** 2 * T)))
|
|
254
|
+
df_dE = getinterpolatedgos(E, q, rel_energy_axis, q_axis, GOSmatrix)
|
|
255
|
+
# integral+= df_dE*lnqa0sqstep
|
|
256
|
+
integral += df_dE * lnqa0sqstep * correction_factor_kohl(alpha, beta, theta)
|
|
257
|
+
# dsigma_dE[i] = 4*np.pi*pc.a0()**2*(R/E)*(R/T)*integral*dispersion
|
|
258
|
+
dsigma_dE[i] = 4 * np.pi * a_0 ** 2 * (R / E) * (R / T) * integral
|
|
259
|
+
else:
|
|
260
|
+
dsigma_dE[i] = 0
|
|
261
|
+
|
|
262
|
+
cross_section = dsigma_dE + dsigma_dE_bound * pref
|
|
263
|
+
|
|
264
|
+
return cross_section
|
|
265
|
+
|
|
266
|
+
def energy2wavelength(e0: float) -> float:
|
|
267
|
+
"""get deBroglie wavelength of electron accelerated by energy (in eV) e0"""
|
|
268
|
+
ev = scipy.constants.e * e0
|
|
269
|
+
m_e = scipy.constants.m_e
|
|
270
|
+
c = scipy.constants.c
|
|
271
|
+
h = scipy.constants.h
|
|
272
|
+
return h / np.sqrt(2 * m_e * ev * (1 + ev / (2 * m_e * c**2)))*1e10
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def ddscs_dE_dOmega(free_energies, ek, E0, q_axis, GOSmatrix):
|
|
276
|
+
"""scattering cross section as a function of energy loss and solid angle
|
|
277
|
+
|
|
278
|
+
Args:
|
|
279
|
+
free_energies: 1d numpy array
|
|
280
|
+
The energy axis on which the GOS table is calculated without the onset
|
|
281
|
+
energy [eV]
|
|
282
|
+
ek: float
|
|
283
|
+
The onset energy of the calculated edge [eV]
|
|
284
|
+
E0: float
|
|
285
|
+
The acceleration voltage of the incoming electrons [V]
|
|
286
|
+
q_axis: 1d numpy array
|
|
287
|
+
The momentum on which the GOS table are calculated. [kg m /s]?
|
|
288
|
+
GOSmatrix: 2d numpy array
|
|
289
|
+
The GOS
|
|
290
|
+
|
|
291
|
+
Returns:
|
|
292
|
+
np.array: scattering cross section as a function of energy loss and solid angle
|
|
293
|
+
"""
|
|
294
|
+
R = scipy.constants.Rydberg
|
|
295
|
+
e = scipy.constants.e
|
|
296
|
+
e = scipy.constants.e
|
|
297
|
+
c = scipy.constants.c
|
|
298
|
+
m = scipy.constants.electron_mass
|
|
299
|
+
a_0 = scipy.constants.physical_constants['Bohr radius'][0]
|
|
300
|
+
gamma = 1 + e * E0 / (m * c ** 2)
|
|
301
|
+
energy_losses = free_energies + ek
|
|
302
|
+
|
|
303
|
+
k0 = 2 * np.pi / energy2wavelength(E0)
|
|
304
|
+
|
|
305
|
+
scs_list = []
|
|
306
|
+
for idx, epsilon in enumerate(free_energies):
|
|
307
|
+
kn = 2 * np.pi / energy2wavelength(E0-energy_losses[idx])
|
|
308
|
+
scs = (
|
|
309
|
+
4
|
|
310
|
+
* gamma ** 2
|
|
311
|
+
/ q_axis**2
|
|
312
|
+
* kn
|
|
313
|
+
/ k0
|
|
314
|
+
* GOSmatrix[idx]
|
|
315
|
+
/ energy_losses[idx]
|
|
316
|
+
* R
|
|
317
|
+
)
|
|
318
|
+
scs_list.append(scs)
|
|
319
|
+
scs_list = np.array(scs_list).squeeze()
|
|
320
|
+
|
|
321
|
+
return scs_list
|
|
322
|
+
|
|
323
|
+
def plot_ddscs(element = 'Si', edge = 'L3', file = file,
|
|
324
|
+
q_steps=100, E0=200000, beta=0.100, alpha=0):
|
|
325
|
+
with h5py.File(file, 'r') as gos_file:
|
|
326
|
+
GOSmatrix = gos_file[element][edge]['data'][:].squeeze().T
|
|
327
|
+
free_energies = gos_file[element][edge]['free_energies'][:][ :] # two dimensional array
|
|
328
|
+
q_axis = gos_file[element][edge]['q'][:] # in [1/m]
|
|
329
|
+
ek = gos_file[element][edge]['metadata'].attrs['ionization_energy']
|
|
330
|
+
for k in gos_file[element][edge]['metadata'].attrs.keys():
|
|
331
|
+
print(f"{k} => {gos_file[element][edge]['metadata'].attrs[k]}")
|
|
332
|
+
occupancy = gos_file[element][edge]['metadata'].attrs['occupancy_ratio']
|
|
333
|
+
k0 = 2 * np.pi / energy2wavelength(E0)
|
|
334
|
+
ddscs = ddscs_dE_dOmega(free_energies, ek, E0, q_axis/1e10, GOSmatrix)
|
|
335
|
+
plt.subplots(1, 2, figsize=(12, 5))
|
|
336
|
+
plt.subplot(121)
|
|
337
|
+
plt.pcolormesh(q_axis/1e10, free_energies, ddscs)
|
|
338
|
+
plt.ylabel('Energy loss [eV]')
|
|
339
|
+
plt.xlabel('Scattering vector [1/A]')
|
|
340
|
+
plt.colorbar()
|
|
341
|
+
plt.tight_layout()
|
|
342
|
+
plt.title('Double differential scattering cross section')
|
|
343
|
+
plt.subplot(122)
|
|
344
|
+
theta = np.arctan(q_axis/1e10 /k0)*1e3 # this is approximation
|
|
345
|
+
plt.pcolormesh(theta, free_energies, ddscs)
|
|
346
|
+
plt.xlim([0, 50])
|
|
347
|
+
plt.xlabel('Scattering angle [mrad]')
|
|
348
|
+
plt.ylabel('Energy loss [eV]')
|
|
349
|
+
plt.title('Double differential scattering cross section')
|
|
350
|
+
plt.colorbar()
|
|
351
|
+
plt.tight_layout()
|
|
352
|
+
max_q = np.searchsorted(theta, 30)
|
|
353
|
+
max_e = np.searchsorted(free_energies, 1000)
|
|
354
|
+
max_q_value = np.tan(30e-3)*k0*1e10
|
|
355
|
+
print(f"min_q: {q_axis[0]}, end_q: {q_axis[-1]}, max_q_value: {max_q_value}")
|
|
356
|
+
print(q_axis[max_q])
|
|
357
|
+
y_sparse = theta[:max_q]
|
|
358
|
+
x_sparse = free_energies[:max_e]
|
|
359
|
+
z_sparse = ddscs[:max_e, :max_q]
|
|
360
|
+
xnew = np.linspace(0, 1000, 1000)
|
|
361
|
+
ynew = np.linspace(0, 30, 100)
|
|
362
|
+
|
|
363
|
+
Xnew, Ynew = np.meshgrid(xnew, ynew)
|
|
364
|
+
|
|
365
|
+
# Flatten the input data for griddata
|
|
366
|
+
X_sparse, Y_sparse = np.meshgrid(x_sparse, y_sparse, indexing='ij')
|
|
367
|
+
points = np.column_stack([X_sparse.ravel(), Y_sparse.ravel()])
|
|
368
|
+
values = z_sparse.ravel()
|
|
369
|
+
znew_reggrid = scipy.interpolate.griddata(points, values, (Xnew, Ynew), method='linear', fill_value=0)
|
|
370
|
+
plt.figure()
|
|
371
|
+
plt.imshow(znew_reggrid, extent=(0, 400, 0, 30), origin='lower', aspect='auto')
|
|
372
|
+
|
|
373
|
+
plt.colorbar()
|
|
374
|
+
|
|
375
|
+
print(ddscs.shape, q_axis.shape, free_energies.shape, znew_reggrid.shape)
|
|
376
|
+
|
|
377
|
+
plt.figure()
|
|
378
|
+
#plt.plot(free_energies, ddscs[:, :max_q].sum(axis=1),)
|
|
379
|
+
plt.plot(xnew, znew_reggrid.sum(axis=0),)
|
|
380
|
+
plt.show()
|
|
381
|
+
|
|
382
|
+
def plot_ddscs(element = 'Si', edge = 'L3', file = file,
|
|
383
|
+
energy_scale = np.linspace(50, 850, int(800*3)),
|
|
384
|
+
q_steps=100, E0=200000, beta=0.100, alpha=0):
|
|
385
|
+
with h5py.File(file, 'r') as gos_file:
|
|
386
|
+
GOSmatrix = gos_file[element][edge]['data'][:].squeeze().T
|
|
387
|
+
free_energies = gos_file[element][edge]['free_energies'][:][ :] # two dimensional array
|
|
388
|
+
q_axis = gos_file[element][edge]['q'][:] # in [1/m]
|
|
389
|
+
ek = gos_file[element][edge]['metadata'].attrs['ionization_energy']
|
|
390
|
+
for k in gos_file[element][edge]['metadata'].attrs.keys():
|
|
391
|
+
print(f"{k} => {gos_file[element][edge]['metadata'].attrs[k]}")
|
|
392
|
+
occupancy = gos_file[element][edge]['metadata'].attrs['occupancy_ratio']
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
k0 = 2 * np.pi / energy2wavelength(E0)
|
|
396
|
+
ddscs = ddscs_dE_dOmega(free_energies, ek, E0, q_axis/1e10, GOSmatrix)
|
|
397
|
+
theta = np.arctan(q_axis/1e10 /k0)*1e3 # this is an approximation
|
|
398
|
+
|
|
399
|
+
max_q = np.searchsorted(theta, beta*1e3)+1
|
|
400
|
+
max_e = np.searchsorted(free_energies+ek, energy_scale[-1])+1
|
|
401
|
+
max_q_value = np.tan(beta)*k0
|
|
402
|
+
print(f"min_q: {q_axis[0]/1e10}, end_q: {q_axis[-1]/1e10}, max_q_value: {max_q_value}")
|
|
403
|
+
print(q_axis[max_q])
|
|
404
|
+
y_sparse = q_axis[:max_q]/1e10
|
|
405
|
+
x_sparse = free_energies[:max_e]+ek
|
|
406
|
+
z_sparse = ddscs[:max_e, :max_q]
|
|
407
|
+
xnew = energy_scale
|
|
408
|
+
ynew = np.linspace(0, max_q_value, q_steps)
|
|
409
|
+
|
|
410
|
+
Xnew, Ynew = np.meshgrid(xnew, ynew)
|
|
411
|
+
|
|
412
|
+
# Flatten the input data for griddata
|
|
413
|
+
X_sparse, Y_sparse = np.meshgrid(x_sparse, y_sparse, indexing='ij')
|
|
414
|
+
points = np.column_stack([X_sparse.ravel(), Y_sparse.ravel()])
|
|
415
|
+
values = z_sparse.ravel()
|
|
416
|
+
znew_reggrid = scipy.interpolate.griddata(points, values, (Xnew, Ynew), method='linear', fill_value=0)
|
|
417
|
+
plt.figure()
|
|
418
|
+
plt.imshow(znew_reggrid, extent=(xnew[0], xnew[-1], 0, ynew[-1]), origin='lower', aspect='auto')
|
|
419
|
+
|
|
420
|
+
plt.colorbar()
|
|
421
|
+
delta_q = ynew[1]-ynew[0]
|
|
422
|
+
|
|
423
|
+
print(ddscs.shape, q_axis.shape, free_energies.shape, znew_reggrid.shape)
|
|
424
|
+
|
|
425
|
+
plt.figure()
|
|
426
|
+
#plt.plot(free_energies, ddscs[:, :max_q].sum(axis=1),)
|
|
427
|
+
plt.plot(xnew, znew_reggrid.sum(axis=0)*delta_q,)
|
|
428
|
+
plt.show()
|
|
429
|
+
|
|
430
|
+
plot_ddscs(element='Si', edge='L3', beta=0.030, alpha=0.0)
|
|
431
|
+
plt.show()
|
|
432
|
+
#xsec = get_dirac_X_section(element='Si', edge='M2', beta=0.0001, alpha=0.0)
|
|
433
|
+
#print('calculated cross section')
|
|
434
|
+
#plt.figure()
|
|
435
|
+
#plt.plot(np.linspace(50, 850, int(800/5)), xsec)
|
|
436
|
+
#plt.xlabel('Energy loss [eV]')
|
|
437
|
+
#plt.show()
|