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.
xarpes/distributions.py CHANGED
@@ -1,38 +1,361 @@
1
1
  # Copyright (C) 2024 xARPES Developers
2
- # This program is free software under the terms of the GNU GPLv2 license.
2
+ # This program is free software under the terms of the GNU GPLv3 license.
3
3
 
4
4
  """The distributions used throughout the code."""
5
5
 
6
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
+ """
7
15
  def __init__(self, name):
8
- self.name = name
16
+ self._name = name
9
17
 
10
- def dist(self):
11
- return self._dist
18
+ @property
19
+ def name(self):
20
+ r"""Returns the name of the class instance.
12
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
13
29
 
14
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
+ """
15
40
  def __init__(self, name):
16
41
  super().__init__(name)
17
42
  self._label = name
18
43
 
44
+ @property
19
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
+ """
20
54
  return self._label
21
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
22
211
 
23
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
+ """
24
238
  def __init__(self, temperature, hnuminphi, background=0,
25
239
  integrated_weight=1, name='fermi_dirac'):
26
-
27
240
  super().__init__(name)
28
241
  self.temperature = temperature
29
242
  self.hnuminphi = hnuminphi
30
243
  self.background = background
31
244
  self.integrated_weight = integrated_weight
32
- self.name = name
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
33
335
 
34
336
  def __call__(self, energy_range, hnuminphi, background, integrated_weight,
35
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
+ """
36
359
  from scipy.ndimage import gaussian_filter
37
360
  import numpy as np
38
361
  sigma_extend = 5 # Extend data range by "5 sigma"
@@ -48,17 +371,49 @@ class fermi_dirac(unique_distribution):
48
371
  len(energy_range) + 2 * enumb)
49
372
  result = (integrated_weight / (1 + np.exp((extend - hnuminphi) / k_BT))
50
373
  + background)
51
- return gaussian_filter(result, sigma=estep)[enumb:-enumb]
374
+ evalf = gaussian_filter(result, sigma=estep)[enumb:-enumb]
375
+ return evalf
52
376
 
53
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
+ """
54
391
  import numpy as np
55
392
  k_B = 8.617e-5 # Boltzmann constant [eV/K]
56
393
  k_BT = self.temperature * k_B
57
- return (self.integrated_weight
394
+ evalf = (self.integrated_weight
58
395
  / (1 + np.exp((energy_range - self.hnuminphi) / k_BT))
59
396
  + self.background)
397
+ return evalf
60
398
 
61
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
+ """
62
417
  import numpy as np
63
418
  from scipy.ndimage import gaussian_filter
64
419
  sigma_extend = 5 # Extend data range by "5 sigma"
@@ -70,35 +425,6 @@ class fermi_dirac(unique_distribution):
70
425
  extend = np.linspace(energy_range[0] - enumb * step_size,
71
426
  energy_range[-1] + enumb * step_size,
72
427
  len(energy_range) + 2 * enumb)
73
- return gaussian_filter(self.evaluate(extend), sigma=estep)[enumb:-enumb]
74
-
75
-
76
- class constant(unique_distribution):
77
- def __init__(self, offset):
78
- super().__init__(name='constant')
79
- self._offset = offset
80
-
81
- def offset(self):
82
- return self._offset
83
-
84
- def set_offset(self, x):
85
- self._offset = x
86
-
87
-
88
- class linear(unique_distribution):
89
- def __init__(self, slope, offset):
90
- super().__init__(name='linear')
91
- self._offset = offset
92
- self._slope = slope
93
-
94
- def offset(self):
95
- return self._offset
96
-
97
- def set_offset(self, x):
98
- self._offset = x
99
-
100
- def slope(self):
101
- return self._slope
102
-
103
- def set_slope(self, x):
104
- self._slope = x
428
+ evalf = gaussian_filter(self.evaluate(extend),
429
+ sigma=estep)[enumb:-enumb]
430
+ return evalf
xarpes/functions.py CHANGED
@@ -1,20 +1,115 @@
1
1
  # Copyright (C) 2024 xARPES Developers
2
- # This program is free software under the terms of the GNU GPLv2 license.
2
+ # This program is free software under the terms of the GNU GPLv3 license.
3
3
 
4
- """Separate functions used in conjunction with various classes."""
4
+ """Separate functions mostly used in conjunction with various classes."""
5
5
 
6
- import numpy as np
7
- from scipy.optimize import leastsq
6
+ def download_examples():
7
+ """Downloads the examples folder from the xARPES code only if it does not
8
+ already exist. Prints executed steps and a final cleanup/failure message.
9
+
10
+ Returns
11
+ -------
12
+ 0, 1 : int
13
+ Returns 0 if the execution succeeds, 1 if it fails.
14
+ """
15
+ import requests
16
+ import zipfile
17
+ import os
18
+ import shutil
19
+ import io
20
+
21
+ repo_url = 'https://github.com/xARPES/xARPES_examples'
22
+ output_dir = '.' # Directory from which the function is called
23
+
24
+ # Check if 'examples' directory already exists
25
+ final_examples_path = os.path.join(output_dir, 'examples')
26
+ if os.path.exists(final_examples_path):
27
+ print("Warning: 'examples' folder already exists. No download will be performed.")
28
+ return 1 # Exit the function if 'examples' directory exists
29
+
30
+ # Proceed with download if 'examples' directory does not exist
31
+ repo_parts = repo_url.replace("https://github.com/", "").rstrip('/')
32
+ zip_url = f"https://github.com/{repo_parts}/archive/refs/heads/main.zip"
33
+
34
+ # Make the HTTP request to download the zip file
35
+ print(f"Downloading {zip_url}")
36
+ response = requests.get(zip_url)
37
+ if response.status_code == 200:
38
+ zip_file_bytes = io.BytesIO(response.content)
39
+
40
+ with zipfile.ZipFile(zip_file_bytes, 'r') as zip_ref:
41
+ zip_ref.extractall(output_dir)
8
42
 
9
- def error_function(p, x, y, function, extra_args):
43
+ # Path to the extracted main folder
44
+ main_folder_path = os.path.join(output_dir, repo_parts.split('/')[-1] + '-main')
45
+ examples_path = os.path.join(main_folder_path, 'examples')
46
+
47
+ # Move the 'examples' directory to the target location if it was extracted
48
+ if os.path.exists(examples_path):
49
+ shutil.move(examples_path, final_examples_path)
50
+ print(f"'examples' subdirectory moved to {final_examples_path}")
51
+ else:
52
+ print("'examples' subdirectory not found in the repository.")
53
+
54
+ # Remove the rest of the extracted content
55
+ shutil.rmtree(main_folder_path)
56
+ print(f"Cleaned up temporary files in {main_folder_path}")
57
+ return 0
58
+ else:
59
+ print(f"Failed to download the repository. Status code: {response.status_code}")
60
+ return 1
61
+
62
+
63
+ def error_function(p, xdata, ydata, function, extra_args):
10
64
  r"""The error function used inside the fit_leastsq function.
65
+
66
+ Parameters
67
+ ----------
68
+ p : ndarray
69
+ Array of parameters during the optimization
70
+ xdata : ndarray
71
+ Array of abscissa values the function is evaluated on
72
+ ydata : ndarray
73
+ Outcomes on ordinate the evaluated function is compared to
74
+ function : function
75
+ Function or class with call method to be evaluated
76
+ extra_args :
77
+ Arguments provided to function that should not be optimized
78
+
79
+ Returns
80
+ -------
81
+ residual :
82
+ Residual between evaluated function and ydata
11
83
  """
12
- return function(x, *p, extra_args) - y
84
+ residual = function(xdata, *p, extra_args) - ydata
85
+ return residual
13
86
 
14
87
 
15
88
  def fit_leastsq(p0, xdata, ydata, function, extra_args):
16
89
  r"""Wrapper arround scipy.optimize.leastsq.
90
+
91
+ Parameters
92
+ ----------
93
+ p0 : ndarray
94
+ Initial guess for parameters to be optimized
95
+ xdata : ndarray
96
+ Array of abscissa values the function is evaluated on
97
+ ydata : ndarray
98
+ Outcomes on ordinate the evaluated function is compared to
99
+ function : function
100
+ Function or class with call method to be evaluated
101
+ extra_args :
102
+ Arguments provided to function that should not be optimized
103
+
104
+ Returns
105
+ -------
106
+ pfit_leastsq : ndarray
107
+ Array containing the optimized parameters
108
+ perr_leastsq : ndarray
109
+ Covariance matrix of the optimized parameters
17
110
  """
111
+ import numpy as np
112
+ from scipy.optimize import leastsq
18
113
  pfit, pcov, infodict, errmsg, success = leastsq(
19
114
  error_function, p0, args=(xdata, ydata, function, extra_args),
20
115
  full_output=1)
@@ -35,4 +130,4 @@ def fit_leastsq(p0, xdata, ydata, function, extra_args):
35
130
  pfit_leastsq = pfit
36
131
  perr_leastsq = np.array(error)
37
132
 
38
- return pfit_leastsq, perr_leastsq
133
+ return pfit_leastsq, perr_leastsq
xarpes/plotting.py CHANGED
@@ -1,5 +1,5 @@
1
1
  # Copyright (C) 2024 xARPES Developers
2
- # This program is free software under the terms of the GNU GPLv2 license.
2
+ # This program is free software under the terms of the GNU GPLv3 license.
3
3
 
4
4
  # get_ax_fig_plt and add_fig_kwargs originate from pymatgen/util/plotting.py.
5
5
  # Copyright (C) 2011-2024 Shyue Ping Ong and the pymatgen Development Team
@@ -15,7 +15,7 @@ from functools import wraps
15
15
  import matplotlib.pyplot as plt
16
16
  import matplotlib as mpl
17
17
 
18
- def my_plot_settings(name='default'):
18
+ def plot_settings(name='default'):
19
19
  mpl.rc('xtick', labelsize=10, direction='in')
20
20
  mpl.rc('ytick', labelsize=10, direction='in')
21
21
  lw = dict(default=2.0, large=4.0)[name]
@@ -26,20 +26,28 @@ def my_plot_settings(name='default'):
26
26
  mpl.rcParams['xtick.major.width'] = 0.8
27
27
  mpl.rcParams.update({'font.size': 16})
28
28
 
29
-
30
29
  def get_ax_fig_plt(ax=None, **kwargs):
31
- r"""Helper function used in plot functions supporting an optional Axes
32
- argument. If ax is None, we build the `matplotlib` figure and create the
33
- Axes else. We return the current active figure.
34
-
35
- Args:
36
- ax (Axes, optional): Axes object. Defaults to None.
37
- kwargs: keyword arguments are passed to plt.figure if ax is not None.
38
-
39
- Returns:
40
- ax: :class:`Axes` object
41
- figure: matplotlib figure
42
- plt: matplotlib pyplot module.
30
+ r"""Helper function used in plot functions supporting an optional `Axes`
31
+ argument.
32
+
33
+ If `ax` is `None`, we build the `matplotlib` figure and create the `Axes`.
34
+ Else we return the current active figure.
35
+
36
+ Parameters
37
+ ----------
38
+ ax : object
39
+ `Axes` object. Defaults to `None`.
40
+ **kwargs
41
+ Keyword arguments are passed to `plt.figure` if `ax` is not `None`.
42
+
43
+ Returns
44
+ -------
45
+ ax : object
46
+ `Axes` object.
47
+ figure : object
48
+ `matplotlib` figure.
49
+ plt : object
50
+ `matplotlib.pyplot` module.
43
51
  """
44
52
  if ax is None:
45
53
  fig = plt.figure(**kwargs)
@@ -49,7 +57,6 @@ def get_ax_fig_plt(ax=None, **kwargs):
49
57
 
50
58
  return ax, fig, plt
51
59
 
52
-
53
60
  def add_fig_kwargs(func):
54
61
  """Decorator that adds keyword arguments for functions returning matplotlib
55
62
  figures.
@@ -115,6 +122,10 @@ def add_fig_kwargs(func):
115
122
 
116
123
  # Add docstring to the decorated method.
117
124
  doc_str = """\n\n
125
+
126
+ notes
127
+ -----
128
+
118
129
  Keyword arguments controlling the display of the figure:
119
130
 
120
131
  ================ ====================================================
@@ -126,7 +137,8 @@ def add_fig_kwargs(func):
126
137
  size_kwargs Dictionary with options passed to fig.set_size_inches
127
138
  e.g. size_kwargs=dict(w=3, h=4)
128
139
  tight_layout True to call fig.tight_layout (default: False)
129
- ax_grid True (False) to add (remove) grid from all axes in fig.
140
+ ax_grid True (False) to add (remove) grid from all axes in
141
+ fig.
130
142
  Default: None i.e. fig is left unchanged.
131
143
  ax_annotate Add labels to subplots e.g. (a), (b).
132
144
  Default: False
@@ -142,4 +154,4 @@ def add_fig_kwargs(func):
142
154
  # Use s
143
155
  wrapper.__doc__ = doc_str
144
156
 
145
- return wrapper
157
+ return wrapper