xarpes 0.1.0__py3-none-any.whl → 0.2.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.
@@ -0,0 +1,13 @@
1
+ __version__ = '0.1.0'
2
+
3
+ from . import plotting
4
+
5
+ from .band_map import *
6
+ from .distributions import *
7
+ from .functions import *
8
+ from .plotting import *
9
+
10
+ # from . import plotting
11
+ # from xarpes.plotting import *
12
+ # from xarpes.distributions import *
13
+ # from xarpes.distributions import fermi_dirac
@@ -0,0 +1,205 @@
1
+ # Copyright (C) 2024 xARPES Developers
2
+ # This program is free software under the terms of the GNU GPLv3 license.
3
+
4
+ # get_ax_fig_plt and add_fig_kwargs originate from pymatgen/util/plotting.py.
5
+ # Copyright (C) 2011-2024 Shyue Ping Ong and the pymatgen Development Team
6
+ # Pymatgen is released under the MIT License.
7
+
8
+ # See also abipy/tools/plotting.py.
9
+ # Copyright (C) 2021 Matteo Giantomassi and the AbiPy Group
10
+ # AbiPy is free software under the terms of the GNU GPLv2 license.
11
+
12
+ """The band map class and allowed operations on it."""
13
+
14
+ import numpy as np
15
+ from .plotting import get_ax_fig_plt, add_fig_kwargs
16
+ from .distributions import fermi_dirac
17
+
18
+ class band_map():
19
+ r"""Class for the band map from the ARPES experiment.
20
+
21
+ Parameters
22
+ ----------
23
+ intensities : ndarray
24
+ 2D array of counts for given (E,k) or (E,angle) pairs [counts]
25
+ angles : ndarray
26
+ 1D array of angular values for the abscissa [degrees]
27
+ ekin : ndarray
28
+ 1D array of kinetic energy values for the ordinate [eV]
29
+ energy_resolution : float
30
+ Energy resolution of the detector [eV]
31
+ temperature : float, None
32
+ Temperature of the sample [K]
33
+ hnuminphi : float, None
34
+ Kinetic energy minus the work function [eV]
35
+ hnuminphi_std : float, None
36
+ Standard deviation of kinetic energy minus work function [eV]
37
+ """
38
+ def __init__(self, intensities, angles, ekin, energy_resolution=None,
39
+ temperature=None, hnuminphi=None, hnuminphi_std=None):
40
+ self.intensities = intensities
41
+ self.angles = angles
42
+ self.ekin = ekin
43
+ self.energy_resolution = energy_resolution
44
+ self.temperature = temperature
45
+ self.hnuminphi = hnuminphi
46
+ self.hnuminphi_std = hnuminphi_std
47
+
48
+ @property
49
+ def hnuminphi(self):
50
+ r"""Returns the photon energy minus the work function in eV if it has
51
+ been set, either during instantiation, with the setter, or by fitting
52
+ the Fermi-Dirac distribution to the integrated weight.
53
+
54
+ Returns
55
+ -------
56
+ hnuminphi : float, None
57
+ Kinetic energy minus the work function [eV]
58
+ """
59
+ return self._hnuminphi
60
+
61
+ @hnuminphi.setter
62
+ def hnuminphi(self, hnuminphi):
63
+ r"""Manually sets the photon energy minus the work function in eV if it
64
+ has been set; otherwise returns None.
65
+
66
+ Parameters
67
+ ----------
68
+ hnuminphi : float, None
69
+ Kinetic energy minus the work function [eV]
70
+ """
71
+ self._hnuminphi = hnuminphi
72
+
73
+ @property
74
+ def hnuminphi_std(self):
75
+ r"""Returns standard deviation of the photon energy minus the work
76
+ function in eV.
77
+
78
+ Returns
79
+ -------
80
+ hnuminphi_std : float
81
+ Standard deviation of energy minus the work function [eV]
82
+ """
83
+ return self._hnuminphi_std
84
+
85
+ @hnuminphi_std.setter
86
+ def hnuminphi_std(self, hnuminphi_std):
87
+ r"""Manually sets the standard deviation of photon energy minus the
88
+ work function in eV.
89
+
90
+ Parameters
91
+ ----------
92
+ hnuminphi_std : float
93
+ Standard deviation of energy minus the work function [eV]
94
+ """
95
+ self._hnuminphi_std = hnuminphi_std
96
+
97
+ def shift_angles(self, shift):
98
+ r"""
99
+ Shifts the angles by the specified amount in degrees. Used to shift
100
+ from the detector angle to the material angle.
101
+
102
+ Parameters
103
+ ----------
104
+ shift : float
105
+ Angular shift [degrees]
106
+ """
107
+ self.angles = self.angles + shift
108
+
109
+ @add_fig_kwargs
110
+ def fit_fermi_edge(self, hnuminphi_guess, background_guess=0.0,
111
+ integrated_weight_guess=1.0, angle_min=-np.infty,
112
+ angle_max=np.infty, ekin_min=-np.infty,
113
+ ekin_max=np.infty, ax=None, **kwargs):
114
+ r"""
115
+ Fits the Fermi edge of the band map and plots the result.
116
+ Also sets hnuminphi, the kinetic energy minus the work function in eV.
117
+ The fitting includes an energy convolution with an abscissa range
118
+ expanded by 5 times the energy resolution standard deviation.
119
+
120
+ Parameters
121
+ ----------
122
+ hnuminphi_guess : float
123
+ Initial guess for kinetic energy minus the work function [eV]
124
+ background_guess : float
125
+ Initial guess for background intensity [counts]
126
+ integrated_weight_guess : float
127
+ Initial guess for integrated spectral intensity [counts]
128
+ angle_min : float
129
+ Minimum angle of integration interval [degrees]
130
+ angle_max : float
131
+ Maximum angle of integration interval [degrees]
132
+ ekin_min : float
133
+ Minimum kinetic energy of integration interval [eV]
134
+ ekin_max : float
135
+ Maximum kinetic energy of integration interval [eV]
136
+ ax : Matplotlib-Axes / NoneType
137
+ Axis for plotting the Fermi edge on. Created if not provided by
138
+ the user.
139
+
140
+ Other parameters
141
+ ----------------
142
+ **kwargs : dict, optional
143
+ Additional arguments passed on to add_fig_kwargs. See the keyword
144
+ table below.
145
+
146
+ Returns
147
+ -------
148
+ fig : Matplotlib-Figure
149
+ Figure containing the Fermi edge fit
150
+ """
151
+ from xarpes.functions import fit_leastsq
152
+
153
+ ax, fig, plt = get_ax_fig_plt(ax=ax)
154
+
155
+ min_angle_index = np.argmin(np.abs(self.angles - angle_min))
156
+ max_angle_index = np.argmin(np.abs(self.angles - angle_max))
157
+
158
+ min_ekin_index = np.argmin(np.abs(self.ekin - ekin_min))
159
+ max_ekin_index = np.argmin(np.abs(self.ekin - ekin_max))
160
+
161
+ energy_range = self.ekin[min_ekin_index:max_ekin_index]
162
+
163
+ integrated_intensity = np.trapz(
164
+ self.intensities[min_ekin_index:max_ekin_index,
165
+ min_angle_index:max_angle_index], axis=1)
166
+
167
+ fdir_initial = fermi_dirac(temperature=self.temperature,
168
+ hnuminphi=hnuminphi_guess,
169
+ background=background_guess,
170
+ integrated_weight=integrated_weight_guess,
171
+ name='Initial guess')
172
+
173
+ parameters = np.array(
174
+ [hnuminphi_guess, background_guess, integrated_weight_guess])
175
+
176
+ extra_args = (self.energy_resolution)
177
+
178
+ popt, pcov = fit_leastsq(parameters, energy_range, integrated_intensity,
179
+ fdir_initial, extra_args)
180
+
181
+ fdir_final = fermi_dirac(temperature=self.temperature,
182
+ hnuminphi=popt[0], background=popt[1],
183
+ integrated_weight=popt[2],
184
+ name='Fitted result')
185
+
186
+ self.hnuminphi = popt[0]
187
+ self.hnuminphi_std = np.sqrt(np.diag(pcov))[0][0]
188
+
189
+ ax.set_xlabel(r'$E_{\mathrm{kin}}$ (-)')
190
+ ax.set_ylabel('Counts (-)')
191
+ ax.set_xlim([ekin_min, ekin_max])
192
+
193
+ ax.plot(energy_range, integrated_intensity, label='Data')
194
+
195
+ ax.plot(energy_range, fdir_initial.convolve(energy_range,
196
+ energy_resolution=self.energy_resolution),
197
+ label=fdir_initial.name)
198
+
199
+ ax.plot(energy_range, fdir_final.convolve(energy_range,
200
+ energy_resolution=self.energy_resolution),
201
+ label=fdir_final.name)
202
+
203
+ ax.legend()
204
+
205
+ return fig
@@ -0,0 +1,430 @@
1
+ # Copyright (C) 2024 xARPES Developers
2
+ # This program is free software under the terms of the GNU GPLv3 license.
3
+
4
+ """The distributions used throughout the code."""
5
+
6
+ class distribution:
7
+ r"""Parent class for distributions. The class cannot be used on its own,
8
+ but is used to instantiate unique and non-unique distributions.
9
+
10
+ Parameters
11
+ ----------
12
+ name : str
13
+ Non-unique name for instances, not to be modified after instantiation.
14
+ """
15
+ def __init__(self, name):
16
+ self._name = name
17
+
18
+ @property
19
+ def name(self):
20
+ r"""Returns the name of the class instance.
21
+
22
+ Returns
23
+ -------
24
+ name : str
25
+ Non-unique name for instances, not to be modified after
26
+ instantiation.
27
+ """
28
+ return self._name
29
+
30
+ class unique_distribution(distribution):
31
+ r"""Parent class for unique distributions, to be used one at a time, e.g.,
32
+ during the background of an MDC fit or the Fermi-Dirac distribution.
33
+
34
+ Parameters
35
+ ----------
36
+ label : str
37
+ Unique label for instances, identical to the name for unique
38
+ distributions. Not to be modified after instantiation.
39
+ """
40
+ def __init__(self, name):
41
+ super().__init__(name)
42
+ self._label = name
43
+
44
+ @property
45
+ def label(self):
46
+ r"""Returns the unique class label.
47
+
48
+ Returns
49
+ -------
50
+ label : str
51
+ Unique label for instances, identical to the name for unique
52
+ distributions. Not to be modified after instantiation.
53
+ """
54
+ return self._label
55
+
56
+ class constant(unique_distribution):
57
+ r"""Child class for constant distributions, used e.g., during MDC fitting.
58
+ The constant class is unique, only one instance should be used per task.
59
+
60
+ Parameters
61
+ ----------
62
+ offset : float
63
+ The value of the distribution for the abscissa equal to 0.
64
+ """
65
+ def __init__(self, offset):
66
+ super().__init__(name='constant')
67
+ self._offset = offset
68
+
69
+ @property
70
+ def offset(self):
71
+ r"""Returns the offset of the constant distribution.
72
+
73
+ Returns
74
+ -------
75
+ offset : float
76
+ The value of the distribution for the abscissa equal to 0.
77
+ """
78
+ return self._offset
79
+
80
+ @offset.setter
81
+ def set_offset(self, x):
82
+ r"""Sets the offset of the constant distribution.
83
+
84
+ Parameters
85
+ ----------
86
+ offset : float
87
+ The value of the distribution for the abscissa equal to 0.
88
+ """
89
+ self._offset = x
90
+
91
+ class linear(unique_distribution):
92
+ r"""Child cass for for linear distributions, used e.g., during MDC fitting.
93
+ The constant class is unique, only one instance should be used per task.
94
+
95
+ Parameters
96
+ ----------
97
+ offset : float
98
+ The value of the distribution for the abscissa equal to 0.
99
+ slope : float
100
+ The linear slope of the distribution w.r.t. the abscissa.
101
+ """
102
+ def __init__(self, slope, offset):
103
+ super().__init__(name='linear')
104
+ self._offset = offset
105
+ self._slope = slope
106
+
107
+ @property
108
+ def offset(self):
109
+ r"""Returns the offset of the linear distribution.
110
+
111
+ Returns
112
+ -------
113
+ offset : float
114
+ The value of the distribution for the abscissa equal to 0.
115
+ """
116
+ return self._offset
117
+
118
+ @offset.setter
119
+ def set_offset(self, x):
120
+ r"""Sets the offset of the linear distribution.
121
+
122
+ Parameters
123
+ ----------
124
+ offset : float
125
+ The value of the distribution for the abscissa equal to 0.
126
+ """
127
+ self._offset = x
128
+
129
+ @property
130
+ def slope(self):
131
+ r"""Returns the slope of the linear distribution.
132
+
133
+ Returns
134
+ -------
135
+ slope : float
136
+ The linear slope of the distribution w.r.t. the abscissa.
137
+ """
138
+ return self._slope
139
+
140
+ @slope.setter
141
+ def set_slope(self, x):
142
+ r"""Sets the slope of the linear distribution.
143
+
144
+ Parameters
145
+ ----------
146
+ slope : float
147
+ The linear slope of the distribution w.r.t. the abscissa.
148
+ """
149
+ self._slope = x
150
+
151
+ class linear(unique_distribution):
152
+ r"""Child cass for for linear distributions, used e.g., during MDC fitting.
153
+ The constant class is unique, only one instance should be used per task.
154
+
155
+ Parameters
156
+ ----------
157
+ offset : float
158
+ The value of the distribution for the abscissa equal to 0.
159
+ slope : float
160
+ The linear slope of the distribution w.r.t. the abscissa.
161
+ """
162
+ def __init__(self, slope, offset):
163
+
164
+ super().__init__(name='linear')
165
+ self._offset = offset
166
+ self._slope = slope
167
+
168
+ @property
169
+ def offset(self):
170
+ r"""Returns the offset of the linear distribution.
171
+
172
+ Returns
173
+ -------
174
+ offset : float
175
+ The value of the distribution for the abscissa equal to 0.
176
+ """
177
+ return self._offset
178
+
179
+ @offset.setter
180
+ def set_offset(self, x):
181
+ r"""Sets the offset of the linear distribution.
182
+
183
+ Parameters
184
+ ----------
185
+ offset : float
186
+ The value of the distribution for the abscissa equal to 0.
187
+ """
188
+ self._offset = x
189
+
190
+ @property
191
+ def slope(self):
192
+ r"""Returns the slope of the linear distribution.
193
+
194
+ Returns
195
+ -------
196
+ slope : float
197
+ The linear slope of the distribution w.r.t. the abscissa.
198
+ """
199
+ return self._slope
200
+
201
+ @slope.setter
202
+ def set_slope(self, x):
203
+ r"""Sets the slope of the linear distribution.
204
+
205
+ Parameters
206
+ ----------
207
+ slope : float
208
+ The linear slope of the distribution w.r.t. the abscissa.
209
+ """
210
+ self._slope = x
211
+
212
+ class fermi_dirac(unique_distribution):
213
+ r"""Child class for Fermi-Dirac (FD) distributions, used e.g., during Fermi
214
+ edge fitting. The FD class is unique, only one instance should be used
215
+ per task.
216
+
217
+ The Fermi-Dirac distribution is described by the following formula:
218
+
219
+ .. math::
220
+
221
+ \frac{A}{\rm{e}^{\beta(E_{\rm{kin}}-(h\nu-\Phi))}+1} + B
222
+
223
+ with :math:`A` as :attr:`integrated_weight`, :math:`B` as
224
+ :attr:`background`, :math:`h\nu-\Phi` as :attr:`hnuminphi`, and
225
+ :math:`\beta=1/(k_{\rm{B}}T)` with :math:`T` as :attr:`temperature`.
226
+
227
+ Parameters
228
+ ----------
229
+ temperature : float
230
+ Temperature of the sample [K]
231
+ hnuminphi : float
232
+ Kinetic energy minus the work function [eV]
233
+ background : float
234
+ Background spectral weight [counts]
235
+ integrated_weight : float
236
+ Integrated weight on top of the background [counts]
237
+ """
238
+ def __init__(self, temperature, hnuminphi, background=0,
239
+ integrated_weight=1, name='fermi_dirac'):
240
+ super().__init__(name)
241
+ self.temperature = temperature
242
+ self.hnuminphi = hnuminphi
243
+ self.background = background
244
+ self.integrated_weight = integrated_weight
245
+
246
+ @property
247
+ def temperature(self):
248
+ r"""Returns the temperature of the sample.
249
+
250
+ Returns
251
+ -------
252
+ temperature : float
253
+ Temperature of the sample [K]
254
+ """
255
+ return self._temperature
256
+
257
+ @temperature.setter
258
+ def set_temperature(self, x):
259
+ r"""Sets the temperature of the FD distribution.
260
+
261
+ Parameters
262
+ ----------
263
+ temperature : float
264
+ Temperature of the sample [K]
265
+ """
266
+ self._temperature = x
267
+
268
+ @property
269
+ def hnuminphi(self):
270
+ r"""Returns the photon energy minus the work function of the FD
271
+ distribution.
272
+
273
+ Returns
274
+ -------
275
+ hnuminphi: float
276
+ Kinetic energy minus the work function [eV]
277
+ """
278
+ return self._hnuminphi
279
+
280
+ @hnuminphi.setter
281
+ def set_hnuminphi(self, x):
282
+ r"""Sets the photon energy minus the work function of the FD
283
+ distribution.
284
+
285
+ Parameters
286
+ ----------
287
+ hnuminphi : float
288
+ Kinetic energy minus the work function [eV]
289
+ """
290
+ self._hnuminphi = x
291
+
292
+ @property
293
+ def background(self):
294
+ r"""Returns the background intensity of the FD distribution.
295
+
296
+ Returns
297
+ -------
298
+ background : float
299
+ Background spectral weight [counts]
300
+ """
301
+ return self._background
302
+
303
+ @background.setter
304
+ def set_background(self, x):
305
+ r"""Sets the background intensity of the FD distribution.
306
+
307
+ Parameters
308
+ ----------
309
+ background : float
310
+ Background spectral weight [counts]
311
+ """
312
+ self._background = x
313
+
314
+ @property
315
+ def integrated_weight(self):
316
+ r"""Returns the integrated weight of the FD distribution.
317
+
318
+ Returns
319
+ -------
320
+ integrated_weight: float
321
+ Integrated weight on top of the background [counts]
322
+ """
323
+ return self._integrated_weight
324
+
325
+ @integrated_weight.setter
326
+ def set_integrated_weight(self, x):
327
+ r"""Sets the integrated weight of the FD distribution.
328
+
329
+ Parameters
330
+ ----------
331
+ integrated_weight : float
332
+ Integrated weight on top of the background [counts]
333
+ """
334
+ self._integrated_weight = x
335
+
336
+ def __call__(self, energy_range, hnuminphi, background, integrated_weight,
337
+ energy_resolution):
338
+ """Call method to directly evaluate a FD distribution without having to
339
+ instantiate a class instance.
340
+
341
+ Parameters
342
+ ----------
343
+ energy_range : ndarray
344
+ 1D array on which to evaluate the FD distribution [eV]
345
+ hnuminphi : float
346
+ Kinetic energy minus the work function [eV]
347
+ background : float
348
+ Background spectral weight [counts]
349
+ integrated_weight : float
350
+ Integrated weight on top of the background [counts]
351
+ energy_resolution : float
352
+ Energy resolution of the detector for the convolution [eV]
353
+
354
+ Returns
355
+ -------
356
+ evalf : ndarray
357
+ 1D array of the energy-convolved FD distribution [counts]
358
+ """
359
+ from scipy.ndimage import gaussian_filter
360
+ import numpy as np
361
+ sigma_extend = 5 # Extend data range by "5 sigma"
362
+ # Conversion from FWHM to standard deviation [-]
363
+ fwhm_to_std = np.sqrt(8 * np.log(2))
364
+ k_B = 8.617e-5 # Boltzmann constant [eV/K]
365
+ k_BT = self.temperature * k_B
366
+ step_size = np.abs(energy_range[1] - energy_range[0])
367
+ estep = energy_resolution / (step_size * fwhm_to_std)
368
+ enumb = int(sigma_extend * estep)
369
+ extend = np.linspace(energy_range[0] - enumb * step_size,
370
+ energy_range[-1] + enumb * step_size,
371
+ len(energy_range) + 2 * enumb)
372
+ result = (integrated_weight / (1 + np.exp((extend - hnuminphi) / k_BT))
373
+ + background)
374
+ evalf = gaussian_filter(result, sigma=estep)[enumb:-enumb]
375
+ return evalf
376
+
377
+ def evaluate(self, energy_range):
378
+ r"""Evaluates the FD distribution for a given class instance.
379
+ No energy convolution is performed with evaluate.
380
+
381
+ Parameters
382
+ ----------
383
+ energy_range : ndarray
384
+ 1D array on which to evaluate the FD distribution [eV]
385
+
386
+ Returns
387
+ -------
388
+ evalf : ndarray
389
+ 1D array of the evaluated FD distribution [counts]
390
+ """
391
+ import numpy as np
392
+ k_B = 8.617e-5 # Boltzmann constant [eV/K]
393
+ k_BT = self.temperature * k_B
394
+ evalf = (self.integrated_weight
395
+ / (1 + np.exp((energy_range - self.hnuminphi) / k_BT))
396
+ + self.background)
397
+ return evalf
398
+
399
+ def convolve(self, energy_range, energy_resolution):
400
+ r"""Evaluates the FD distribution for a given class instance and
401
+ performs the energy convolution with the given resolution. The
402
+ convolution is performed with an expanded abscissa range of 5
403
+ times the standard deviation.
404
+
405
+ Parameters
406
+ ----------
407
+ energy_range : ndarray
408
+ 1D array on which to evaluate and convolve FD distribution [eV]
409
+ energy_resolution : float
410
+ Energy resolution of the detector for the convolution [eV]
411
+
412
+ Returns
413
+ -------
414
+ evalf : ndarray
415
+ 1D array of the energy-convolved FD distribution [counts]
416
+ """
417
+ import numpy as np
418
+ from scipy.ndimage import gaussian_filter
419
+ sigma_extend = 5 # Extend data range by "5 sigma"
420
+ # Conversion from FWHM to standard deviation [-]
421
+ fwhm_to_std = np.sqrt(8 * np.log(2))
422
+ step_size = np.abs(energy_range[1] - energy_range[0])
423
+ estep = energy_resolution / (step_size * fwhm_to_std)
424
+ enumb = int(sigma_extend * estep)
425
+ extend = np.linspace(energy_range[0] - enumb * step_size,
426
+ energy_range[-1] + enumb * step_size,
427
+ len(energy_range) + 2 * enumb)
428
+ evalf = gaussian_filter(self.evaluate(extend),
429
+ sigma=estep)[enumb:-enumb]
430
+ return evalf