xarpes 0.2.3__py3-none-any.whl → 0.3.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/__init__.py +3 -5
- xarpes/constants.py +12 -0
- xarpes/distributions.py +500 -239
- xarpes/functions.py +263 -61
- xarpes/plotting.py +46 -29
- xarpes/spectral.py +2067 -0
- xarpes-0.3.0.dist-info/METADATA +160 -0
- xarpes-0.3.0.dist-info/RECORD +11 -0
- {xarpes-0.2.3.dist-info → xarpes-0.3.0.dist-info}/WHEEL +1 -1
- xarpes-0.3.0.dist-info/entry_points.txt +3 -0
- {xarpes-0.2.3.dist-info → xarpes-0.3.0.dist-info/licenses}/LICENSE +0 -0
- xarpes/.ipynb_checkpoints/__init__-checkpoint.py +0 -8
- xarpes/band_map.py +0 -306
- xarpes-0.2.3.dist-info/METADATA +0 -121
- xarpes-0.2.3.dist-info/RECORD +0 -10
xarpes/distributions.py
CHANGED
|
@@ -1,11 +1,151 @@
|
|
|
1
|
-
# Copyright (C)
|
|
1
|
+
# Copyright (C) 2025 xARPES Developers
|
|
2
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
|
import numpy as np
|
|
7
|
+
from .functions import extend_function
|
|
8
|
+
from .plotting import get_ax_fig_plt, add_fig_kwargs
|
|
9
|
+
from .constants import k_B, pref, dtor
|
|
10
|
+
|
|
11
|
+
class CreateDistributions:
|
|
12
|
+
r"""
|
|
13
|
+
Thin container for distributions with leaf-aware utilities.
|
|
14
|
+
If a distribution implements `components()` -> iterable of leaf
|
|
15
|
+
distributions, we use it to count/flatten; otherwise it's treated as a leaf.
|
|
16
|
+
"""
|
|
17
|
+
def __init__(self, distributions):
|
|
18
|
+
# Adds a call method to the distribution list
|
|
19
|
+
self.distributions = distributions
|
|
20
|
+
|
|
21
|
+
def __call__(self):
|
|
22
|
+
r"""
|
|
23
|
+
"""
|
|
24
|
+
return self.distributions
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def distributions(self):
|
|
28
|
+
r"""
|
|
29
|
+
"""
|
|
30
|
+
return self._distributions
|
|
31
|
+
|
|
32
|
+
@distributions.setter
|
|
33
|
+
def distributions(self, x):
|
|
34
|
+
r"""
|
|
35
|
+
"""
|
|
36
|
+
self._distributions = x
|
|
37
|
+
|
|
38
|
+
def __iter__(self):
|
|
39
|
+
r"""
|
|
40
|
+
"""
|
|
41
|
+
return iter(self.distributions)
|
|
42
|
+
|
|
43
|
+
def __getitem__(self, index):
|
|
44
|
+
r"""
|
|
45
|
+
"""
|
|
46
|
+
return self.distributions[index]
|
|
47
|
+
|
|
48
|
+
def __setitem__(self, index, value):
|
|
49
|
+
r"""
|
|
50
|
+
"""
|
|
51
|
+
self.distributions[index] = value
|
|
52
|
+
|
|
53
|
+
def __len__(self):
|
|
54
|
+
r"""
|
|
55
|
+
"""
|
|
56
|
+
# Fast path: flat list length (may be different from n_individuals
|
|
57
|
+
# if composites exist)
|
|
58
|
+
return len(self.distributions)
|
|
59
|
+
|
|
60
|
+
def __deepcopy__(self, memo):
|
|
61
|
+
r"""
|
|
62
|
+
"""
|
|
63
|
+
import copy
|
|
64
|
+
return type(self)(copy.deepcopy(self.distributions, memo))
|
|
65
|
+
|
|
66
|
+
# -------- Leaf-aware helpers --------
|
|
67
|
+
def _iter_leaves(self):
|
|
68
|
+
"""
|
|
69
|
+
Yield leaf distributions. If an item has .components(), flatten it;
|
|
70
|
+
else treat the item itself as a leaf.
|
|
71
|
+
"""
|
|
72
|
+
for d in self.distributions:
|
|
73
|
+
comps = getattr(d, "components", None)
|
|
74
|
+
if callable(comps):
|
|
75
|
+
for leaf in comps():
|
|
76
|
+
yield leaf
|
|
77
|
+
else:
|
|
78
|
+
yield d
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def n_individuals(self) -> int:
|
|
82
|
+
"""
|
|
83
|
+
Number of leaf components (i.e., individual curves to plot).
|
|
84
|
+
"""
|
|
85
|
+
# If items define __len__ as num_leaves you can use that;
|
|
86
|
+
# but components() is the most explicit/robust.
|
|
87
|
+
return sum(1 for _ in self._iter_leaves())
|
|
88
|
+
|
|
89
|
+
def flatten(self):
|
|
90
|
+
"""
|
|
91
|
+
Return a flat list of leaf distributions (useful for plotting/storage).
|
|
92
|
+
"""
|
|
93
|
+
return list(self._iter_leaves())
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@add_fig_kwargs
|
|
97
|
+
def plot(self, angle_range, angle_resolution, kinetic_energy=None,
|
|
98
|
+
hnuminphi=None, matrix_element=None, matrix_args=None, ax=None,
|
|
99
|
+
**kwargs):
|
|
100
|
+
r"""
|
|
101
|
+
"""
|
|
102
|
+
if angle_resolution < 0:
|
|
103
|
+
raise ValueError('Distributions cannot be plotted with negative '
|
|
104
|
+
+ 'resolution.')
|
|
7
105
|
|
|
8
|
-
|
|
106
|
+
from scipy.ndimage import gaussian_filter
|
|
107
|
+
|
|
108
|
+
ax, fig, plt = get_ax_fig_plt(ax=ax)
|
|
109
|
+
|
|
110
|
+
ax.set_xlabel('Angle ($\degree$)')
|
|
111
|
+
ax.set_ylabel('Counts (-)')
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
extend, step, numb = extend_function(angle_range, angle_resolution)
|
|
115
|
+
|
|
116
|
+
total_result = np.zeros(np.shape(extend))
|
|
117
|
+
|
|
118
|
+
for dist in self.distributions:
|
|
119
|
+
if dist.class_name == 'SpectralQuadratic':
|
|
120
|
+
if (dist.center_angle is not None) and (kinetic_energy is
|
|
121
|
+
None or hnuminphi is None):
|
|
122
|
+
raise ValueError('Spectral quadratic function is '
|
|
123
|
+
'defined in terms of a center angle. Please provide '
|
|
124
|
+
'a kinetic energy and hnuminphi.')
|
|
125
|
+
extended_result = dist.evaluate(extend,
|
|
126
|
+
kinetic_energy, hnuminphi)
|
|
127
|
+
else:
|
|
128
|
+
extended_result = dist.evaluate(extend)
|
|
129
|
+
|
|
130
|
+
if matrix_element is not None:
|
|
131
|
+
extended_result *= matrix_element(extend, **matrix_args)
|
|
132
|
+
|
|
133
|
+
total_result += extended_result
|
|
134
|
+
|
|
135
|
+
individual_result = gaussian_filter(extended_result, sigma=step
|
|
136
|
+
)[numb:-numb if numb else None]
|
|
137
|
+
ax.plot(angle_range, individual_result, label=dist.label)
|
|
138
|
+
|
|
139
|
+
final_result = gaussian_filter(total_result, sigma=step
|
|
140
|
+
)[numb:-numb if numb else None]
|
|
141
|
+
|
|
142
|
+
ax.plot(angle_range, final_result, label=str('Distribution sum'))
|
|
143
|
+
|
|
144
|
+
ax.legend()
|
|
145
|
+
|
|
146
|
+
return fig
|
|
147
|
+
|
|
148
|
+
class Distribution:
|
|
9
149
|
r"""Parent class for distributions. The class cannot be used on its own,
|
|
10
150
|
but is used to instantiate unique and non-unique distributions.
|
|
11
151
|
|
|
@@ -19,18 +159,52 @@ class distribution:
|
|
|
19
159
|
|
|
20
160
|
@property
|
|
21
161
|
def name(self):
|
|
22
|
-
r"""
|
|
23
|
-
|
|
24
|
-
Returns
|
|
25
|
-
-------
|
|
26
|
-
name : str
|
|
27
|
-
Non-unique name for instances, not to be modified after
|
|
28
|
-
instantiation.
|
|
162
|
+
r"""
|
|
29
163
|
"""
|
|
30
164
|
return self._name
|
|
31
165
|
|
|
32
|
-
|
|
33
|
-
|
|
166
|
+
@property
|
|
167
|
+
def class_name(self):
|
|
168
|
+
r"""TBD
|
|
169
|
+
"""
|
|
170
|
+
return self.__class__.__name__
|
|
171
|
+
|
|
172
|
+
@add_fig_kwargs
|
|
173
|
+
def plot(self, angle_range, angle_resolution, kinetic_energy=None,
|
|
174
|
+
hnuminphi=None, matrix_element=None, matrix_args=None,
|
|
175
|
+
ax=None, **kwargs):
|
|
176
|
+
r"""Overwritten for FermiDirac distribution.
|
|
177
|
+
"""
|
|
178
|
+
if angle_resolution < 0:
|
|
179
|
+
raise ValueError('Distribution cannot be plotted with negative '
|
|
180
|
+
+ 'resolution.')
|
|
181
|
+
from scipy.ndimage import gaussian_filter
|
|
182
|
+
|
|
183
|
+
ax, fig, plt = get_ax_fig_plt(ax=ax)
|
|
184
|
+
|
|
185
|
+
ax.set_xlabel('Angle ($\degree$)')
|
|
186
|
+
ax.set_ylabel('Counts (-)')
|
|
187
|
+
|
|
188
|
+
extend, step, numb = extend_function(angle_range, angle_resolution)
|
|
189
|
+
|
|
190
|
+
if self.class_name == 'SpectralQuadratic':
|
|
191
|
+
extended_result = self.evaluate(extend, kinetic_energy, hnuminphi)
|
|
192
|
+
else:
|
|
193
|
+
extended_result = self.evaluate(extend)
|
|
194
|
+
|
|
195
|
+
if matrix_element is not None:
|
|
196
|
+
extended_result *= matrix_element(extend, **matrix_args)
|
|
197
|
+
|
|
198
|
+
final_result = gaussian_filter(extended_result, sigma=step)\
|
|
199
|
+
[numb:-numb if numb else None]
|
|
200
|
+
|
|
201
|
+
ax.plot(angle_range, final_result, label=self.label)
|
|
202
|
+
|
|
203
|
+
ax.legend()
|
|
204
|
+
|
|
205
|
+
return fig
|
|
206
|
+
|
|
207
|
+
class UniqueDistribution(Distribution):
|
|
34
208
|
r"""Parent class for unique distributions, to be used one at a time, e.g.,
|
|
35
209
|
during the background of an MDC fit or the Fermi-Dirac distribution.
|
|
36
210
|
|
|
@@ -56,108 +230,10 @@ class unique_distribution(distribution):
|
|
|
56
230
|
"""
|
|
57
231
|
return self._label
|
|
58
232
|
|
|
59
|
-
|
|
60
|
-
class
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
Parameters
|
|
65
|
-
----------
|
|
66
|
-
offset : float
|
|
67
|
-
The value of the distribution for the abscissa equal to 0.
|
|
68
|
-
"""
|
|
69
|
-
def __init__(self, offset, name='constant'):
|
|
70
|
-
super().__init__(name)
|
|
71
|
-
self._offset = offset
|
|
72
|
-
|
|
73
|
-
@property
|
|
74
|
-
def offset(self):
|
|
75
|
-
r"""Returns the offset of the constant distribution.
|
|
76
|
-
|
|
77
|
-
Returns
|
|
78
|
-
-------
|
|
79
|
-
offset : float
|
|
80
|
-
The value of the distribution for the abscissa equal to 0.
|
|
81
|
-
"""
|
|
82
|
-
return self._offset
|
|
83
|
-
|
|
84
|
-
@offset.setter
|
|
85
|
-
def set_offset(self, x):
|
|
86
|
-
r"""Sets the offset of the constant distribution.
|
|
87
|
-
|
|
88
|
-
Parameters
|
|
89
|
-
----------
|
|
90
|
-
offset : float
|
|
91
|
-
The value of the distribution for the abscissa equal to 0.
|
|
92
|
-
"""
|
|
93
|
-
self._offset = x
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
class linear(unique_distribution):
|
|
97
|
-
r"""Child cass for for linear distributions, used e.g., during MDC fitting.
|
|
98
|
-
The constant class is unique, only one instance should be used per task.
|
|
99
|
-
|
|
100
|
-
Parameters
|
|
101
|
-
----------
|
|
102
|
-
offset : float
|
|
103
|
-
The value of the distribution for the abscissa equal to 0.
|
|
104
|
-
slope : float
|
|
105
|
-
The linear slope of the distribution w.r.t. the abscissa.
|
|
106
|
-
"""
|
|
107
|
-
def __init__(self, slope, offset, name='linear'):
|
|
108
|
-
super().__init__(name)
|
|
109
|
-
self._offset = offset
|
|
110
|
-
self._slope = slope
|
|
111
|
-
|
|
112
|
-
@property
|
|
113
|
-
def offset(self):
|
|
114
|
-
r"""Returns the offset of the linear distribution.
|
|
115
|
-
|
|
116
|
-
Returns
|
|
117
|
-
-------
|
|
118
|
-
offset : float
|
|
119
|
-
The value of the distribution for the abscissa equal to 0.
|
|
120
|
-
"""
|
|
121
|
-
return self._offset
|
|
122
|
-
|
|
123
|
-
@offset.setter
|
|
124
|
-
def set_offset(self, x):
|
|
125
|
-
r"""Sets the offset of the linear distribution.
|
|
126
|
-
|
|
127
|
-
Parameters
|
|
128
|
-
----------
|
|
129
|
-
offset : float
|
|
130
|
-
The value of the distribution for the abscissa equal to 0.
|
|
131
|
-
"""
|
|
132
|
-
self._offset = x
|
|
133
|
-
|
|
134
|
-
@property
|
|
135
|
-
def slope(self):
|
|
136
|
-
r"""Returns the slope of the linear distribution.
|
|
137
|
-
|
|
138
|
-
Returns
|
|
139
|
-
-------
|
|
140
|
-
slope : float
|
|
141
|
-
The linear slope of the distribution w.r.t. the abscissa.
|
|
142
|
-
"""
|
|
143
|
-
return self._slope
|
|
144
|
-
|
|
145
|
-
@slope.setter
|
|
146
|
-
def set_slope(self, x):
|
|
147
|
-
r"""Sets the slope of the linear distribution.
|
|
148
|
-
|
|
149
|
-
Parameters
|
|
150
|
-
----------
|
|
151
|
-
slope : float
|
|
152
|
-
The linear slope of the distribution w.r.t. the abscissa.
|
|
153
|
-
"""
|
|
154
|
-
self._slope = x
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
class fermi_dirac(unique_distribution):
|
|
158
|
-
r"""Child class for Fermi-Dirac (FD) distributions, used e.g., during Fermi
|
|
159
|
-
edge fitting. The FD class is unique, only one instance should be used
|
|
160
|
-
per task.
|
|
233
|
+
class FermiDirac(UniqueDistribution):
|
|
234
|
+
r"""Child class for Fermi-Dirac (FD) distributions, used e.g., during
|
|
235
|
+
Fermi-edge fitting. The FD class is unique, only one instance should be
|
|
236
|
+
used per task.
|
|
161
237
|
|
|
162
238
|
The Fermi-Dirac distribution is described by the following formula:
|
|
163
239
|
|
|
@@ -181,7 +257,7 @@ class fermi_dirac(unique_distribution):
|
|
|
181
257
|
Integrated weight on top of the background [counts]
|
|
182
258
|
"""
|
|
183
259
|
def __init__(self, temperature, hnuminphi, background=0,
|
|
184
|
-
integrated_weight=1, name='
|
|
260
|
+
integrated_weight=1, name='FermiDirac'):
|
|
185
261
|
super().__init__(name)
|
|
186
262
|
self.temperature = temperature
|
|
187
263
|
self.hnuminphi = hnuminphi
|
|
@@ -200,7 +276,7 @@ class fermi_dirac(unique_distribution):
|
|
|
200
276
|
return self._temperature
|
|
201
277
|
|
|
202
278
|
@temperature.setter
|
|
203
|
-
def
|
|
279
|
+
def temperature(self, x):
|
|
204
280
|
r"""Sets the temperature of the FD distribution.
|
|
205
281
|
|
|
206
282
|
Parameters
|
|
@@ -223,7 +299,7 @@ class fermi_dirac(unique_distribution):
|
|
|
223
299
|
return self._hnuminphi
|
|
224
300
|
|
|
225
301
|
@hnuminphi.setter
|
|
226
|
-
def
|
|
302
|
+
def hnuminphi(self, x):
|
|
227
303
|
r"""Sets the photon energy minus the work function of the FD
|
|
228
304
|
distribution.
|
|
229
305
|
|
|
@@ -246,7 +322,7 @@ class fermi_dirac(unique_distribution):
|
|
|
246
322
|
return self._background
|
|
247
323
|
|
|
248
324
|
@background.setter
|
|
249
|
-
def
|
|
325
|
+
def background(self, x):
|
|
250
326
|
r"""Sets the background intensity of the FD distribution.
|
|
251
327
|
|
|
252
328
|
Parameters
|
|
@@ -268,7 +344,7 @@ class fermi_dirac(unique_distribution):
|
|
|
268
344
|
return self._integrated_weight
|
|
269
345
|
|
|
270
346
|
@integrated_weight.setter
|
|
271
|
-
def
|
|
347
|
+
def integrated_weight(self, x):
|
|
272
348
|
r"""Sets the integrated weight of the FD distribution.
|
|
273
349
|
|
|
274
350
|
Parameters
|
|
@@ -279,7 +355,7 @@ class fermi_dirac(unique_distribution):
|
|
|
279
355
|
self._integrated_weight = x
|
|
280
356
|
|
|
281
357
|
def __call__(self, energy_range, hnuminphi, background, integrated_weight,
|
|
282
|
-
|
|
358
|
+
temperature):
|
|
283
359
|
"""Call method to directly evaluate a FD distribution without having to
|
|
284
360
|
instantiate a class instance.
|
|
285
361
|
|
|
@@ -301,23 +377,10 @@ class fermi_dirac(unique_distribution):
|
|
|
301
377
|
evalf : ndarray
|
|
302
378
|
1D array of the energy-convolved FD distribution [counts]
|
|
303
379
|
"""
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
fwhm_to_std = np.sqrt(8 * np.log(2))
|
|
309
|
-
k_B = 8.617e-5 # Boltzmann constant [eV/K]
|
|
310
|
-
k_BT = self.temperature * k_B
|
|
311
|
-
step_size = np.abs(energy_range[1] - energy_range[0])
|
|
312
|
-
estep = energy_resolution / (step_size * fwhm_to_std)
|
|
313
|
-
enumb = int(sigma_extend * estep)
|
|
314
|
-
extend = np.linspace(energy_range[0] - enumb * step_size,
|
|
315
|
-
energy_range[-1] + enumb * step_size,
|
|
316
|
-
len(energy_range) + 2 * enumb)
|
|
317
|
-
result = (integrated_weight / (1 + np.exp((extend - hnuminphi) / k_BT))
|
|
318
|
-
+ background)
|
|
319
|
-
evalf = gaussian_filter(result, sigma=estep)[enumb:-enumb]
|
|
320
|
-
return evalf
|
|
380
|
+
k_BT = temperature * k_B
|
|
381
|
+
|
|
382
|
+
return (integrated_weight / (1 + np.exp((energy_range - hnuminphi)
|
|
383
|
+
/ k_BT)) + background)
|
|
321
384
|
|
|
322
385
|
def evaluate(self, energy_range):
|
|
323
386
|
r"""Evaluates the FD distribution for a given class instance.
|
|
@@ -332,49 +395,158 @@ class fermi_dirac(unique_distribution):
|
|
|
332
395
|
-------
|
|
333
396
|
evalf : ndarray
|
|
334
397
|
1D array of the evaluated FD distribution [counts]
|
|
335
|
-
"""
|
|
336
|
-
k_B = 8.617e-5 # Boltzmann constant [eV/K]
|
|
398
|
+
"""
|
|
337
399
|
k_BT = self.temperature * k_B
|
|
338
|
-
|
|
400
|
+
|
|
401
|
+
return (self.integrated_weight
|
|
339
402
|
/ (1 + np.exp((energy_range - self.hnuminphi) / k_BT))
|
|
340
403
|
+ self.background)
|
|
341
|
-
return evalf
|
|
342
404
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
405
|
+
@add_fig_kwargs
|
|
406
|
+
def plot(self, energy_range, energy_resolution, ax=None, **kwargs):
|
|
407
|
+
r"""TBD
|
|
408
|
+
"""
|
|
409
|
+
if energy_resolution < 0:
|
|
410
|
+
raise ValueError('Distribution cannot be plotted with negative '
|
|
411
|
+
+ 'resolution.')
|
|
412
|
+
from scipy.ndimage import gaussian_filter
|
|
413
|
+
|
|
414
|
+
ax, fig, plt = get_ax_fig_plt(ax=ax)
|
|
415
|
+
|
|
416
|
+
ax.set_xlabel(r'$E_{\mathrm{kin}}$ (-)')
|
|
417
|
+
ax.set_ylabel('Counts (-)')
|
|
418
|
+
|
|
419
|
+
extend, step, numb = extend_function(energy_range, \
|
|
420
|
+
energy_resolution)
|
|
421
|
+
|
|
422
|
+
extended_result = self.evaluate(extend)
|
|
423
|
+
|
|
424
|
+
final_result = gaussian_filter(extended_result, sigma=step)\
|
|
425
|
+
[numb:-numb if numb else None]
|
|
426
|
+
|
|
427
|
+
ax.plot(energy_range, final_result, label=self.label)
|
|
428
|
+
|
|
429
|
+
ax.legend()
|
|
430
|
+
|
|
431
|
+
return fig
|
|
432
|
+
|
|
433
|
+
class Constant(UniqueDistribution):
|
|
434
|
+
r"""Child class for constant distributions, used e.g., during MDC fitting.
|
|
435
|
+
The constant class is unique, only one instance should be used per task.
|
|
436
|
+
|
|
437
|
+
Parameters
|
|
438
|
+
----------
|
|
439
|
+
offset : float
|
|
440
|
+
The value of the distribution for the abscissa equal to 0.
|
|
441
|
+
"""
|
|
442
|
+
def __init__(self, offset, name='Constant'):
|
|
443
|
+
super().__init__(name)
|
|
444
|
+
self.offset = offset
|
|
445
|
+
|
|
446
|
+
@property
|
|
447
|
+
def offset(self):
|
|
448
|
+
r"""Returns the offset of the constant distribution.
|
|
449
|
+
|
|
450
|
+
Returns
|
|
451
|
+
-------
|
|
452
|
+
offset : float
|
|
453
|
+
The value of the distribution for the abscissa equal to 0.
|
|
454
|
+
"""
|
|
455
|
+
return self._offset
|
|
456
|
+
|
|
457
|
+
@offset.setter
|
|
458
|
+
def offset(self, x):
|
|
459
|
+
r"""Sets the offset of the constant distribution.
|
|
348
460
|
|
|
349
461
|
Parameters
|
|
350
462
|
----------
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
463
|
+
offset : float
|
|
464
|
+
The value of the distribution for the abscissa equal to 0.
|
|
465
|
+
"""
|
|
466
|
+
self._offset = x
|
|
467
|
+
|
|
468
|
+
def __call__(self, angle_range, offset):
|
|
469
|
+
r"""TBD
|
|
470
|
+
"""
|
|
471
|
+
return np.full(np.shape(angle_range), offset)
|
|
472
|
+
|
|
473
|
+
def evaluate(self, angle_range):
|
|
474
|
+
r"""TBD
|
|
475
|
+
"""
|
|
476
|
+
return np.full(np.shape(angle_range), self.offset)
|
|
477
|
+
|
|
478
|
+
class Linear(UniqueDistribution):
|
|
479
|
+
r"""Child cass for for linear distributions, used e.g., during MDC
|
|
480
|
+
fitting. The linear class is unique, only one instance should be used per
|
|
481
|
+
task.
|
|
482
|
+
|
|
483
|
+
Parameters
|
|
484
|
+
----------
|
|
485
|
+
offset : float
|
|
486
|
+
The value of the distribution for the abscissa equal to 0.
|
|
487
|
+
slope : float
|
|
488
|
+
The linear slope of the distribution w.r.t. the abscissa.
|
|
489
|
+
"""
|
|
490
|
+
def __init__(self, slope, offset, name='Linear'):
|
|
491
|
+
super().__init__(name)
|
|
492
|
+
self.offset = offset
|
|
493
|
+
self.slope = slope
|
|
494
|
+
|
|
495
|
+
@property
|
|
496
|
+
def offset(self):
|
|
497
|
+
r"""Returns the offset of the linear distribution.
|
|
355
498
|
|
|
356
499
|
Returns
|
|
357
500
|
-------
|
|
358
|
-
|
|
359
|
-
|
|
501
|
+
offset : float
|
|
502
|
+
The value of the distribution for the abscissa equal to 0.
|
|
360
503
|
"""
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
504
|
+
return self._offset
|
|
505
|
+
|
|
506
|
+
@offset.setter
|
|
507
|
+
def offset(self, x):
|
|
508
|
+
r"""Sets the offset of the linear distribution.
|
|
509
|
+
|
|
510
|
+
Parameters
|
|
511
|
+
----------
|
|
512
|
+
offset : float
|
|
513
|
+
The value of the distribution for the abscissa equal to 0.
|
|
514
|
+
"""
|
|
515
|
+
self._offset = x
|
|
516
|
+
|
|
517
|
+
@property
|
|
518
|
+
def slope(self):
|
|
519
|
+
r"""Returns the slope of the linear distribution.
|
|
520
|
+
|
|
521
|
+
Returns
|
|
522
|
+
-------
|
|
523
|
+
slope : float
|
|
524
|
+
The linear slope of the distribution w.r.t. the abscissa.
|
|
525
|
+
"""
|
|
526
|
+
return self._slope
|
|
527
|
+
|
|
528
|
+
@slope.setter
|
|
529
|
+
def slope(self, x):
|
|
530
|
+
r"""Sets the slope of the linear distribution.
|
|
531
|
+
|
|
532
|
+
Parameters
|
|
533
|
+
----------
|
|
534
|
+
slope : float
|
|
535
|
+
The linear slope of the distribution w.r.t. the abscissa.
|
|
536
|
+
"""
|
|
537
|
+
self._slope = x
|
|
538
|
+
|
|
539
|
+
def __call__(self, angle_range, offset, slope):
|
|
540
|
+
r"""For a linear slope, convolution changes something.
|
|
541
|
+
"""
|
|
542
|
+
return offset + slope * angle_range
|
|
543
|
+
|
|
544
|
+
def evaluate(self, angle_range):
|
|
545
|
+
r"""No energy convolution is performed with evaluate.
|
|
546
|
+
"""
|
|
547
|
+
return self.offset + self.slope * angle_range
|
|
548
|
+
|
|
549
|
+
class NonUniqueDistribution(Distribution):
|
|
378
550
|
r"""Parent class for unique distributions, to be used one at a time, e.g.,
|
|
379
551
|
during the background of an MDC fit or the Fermi-Dirac distribution.
|
|
380
552
|
|
|
@@ -386,7 +558,8 @@ class non_unique_distribution(distribution):
|
|
|
386
558
|
"""
|
|
387
559
|
def __init__(self, name, index):
|
|
388
560
|
super().__init__(name)
|
|
389
|
-
self._label = name + index
|
|
561
|
+
self._label = name + '_' + index
|
|
562
|
+
self._index = index
|
|
390
563
|
|
|
391
564
|
@property
|
|
392
565
|
def label(self):
|
|
@@ -395,27 +568,32 @@ class non_unique_distribution(distribution):
|
|
|
395
568
|
Returns
|
|
396
569
|
-------
|
|
397
570
|
label : str
|
|
398
|
-
Unique label for instances,
|
|
399
|
-
distributions. Not to be modified after
|
|
571
|
+
Unique label for instances, consisting of the label and the index
|
|
572
|
+
for non-unique distributions. Not to be modified after
|
|
573
|
+
instantiation.
|
|
400
574
|
"""
|
|
401
575
|
return self._label
|
|
402
576
|
|
|
577
|
+
@property
|
|
578
|
+
def index(self):
|
|
579
|
+
r"""Returns the unique class index.
|
|
580
|
+
|
|
581
|
+
Returns
|
|
582
|
+
-------
|
|
583
|
+
index : str
|
|
584
|
+
Unique index for instances. Not to be modified after
|
|
585
|
+
instantiation.
|
|
586
|
+
"""
|
|
587
|
+
return self._index
|
|
403
588
|
|
|
404
|
-
class
|
|
589
|
+
class Dispersion(NonUniqueDistribution):
|
|
405
590
|
r"""Dispersions are assumed to be unique, so they need an index.
|
|
406
591
|
"""
|
|
407
|
-
def __init__(self, amplitude,
|
|
408
|
-
super().__init__(name)
|
|
409
|
-
self.
|
|
410
|
-
self.
|
|
411
|
-
self.
|
|
412
|
-
self._label = name + index
|
|
413
|
-
|
|
414
|
-
@property
|
|
415
|
-
def label(self):
|
|
416
|
-
r"""
|
|
417
|
-
"""
|
|
418
|
-
return self._label
|
|
592
|
+
def __init__(self, amplitude, peak, broadening, name, index):
|
|
593
|
+
super().__init__(name, index)
|
|
594
|
+
self.amplitude = amplitude
|
|
595
|
+
self.peak = peak
|
|
596
|
+
self.broadening = broadening
|
|
419
597
|
|
|
420
598
|
@property
|
|
421
599
|
def amplitude(self):
|
|
@@ -424,22 +602,22 @@ class dispersion(distribution):
|
|
|
424
602
|
return self._amplitude
|
|
425
603
|
|
|
426
604
|
@amplitude.setter
|
|
427
|
-
def
|
|
605
|
+
def amplitude(self, x):
|
|
428
606
|
r"""
|
|
429
607
|
"""
|
|
430
608
|
self._amplitude = x
|
|
431
609
|
|
|
432
610
|
@property
|
|
433
|
-
def
|
|
611
|
+
def peak(self):
|
|
434
612
|
r"""
|
|
435
613
|
"""
|
|
436
|
-
return self.
|
|
614
|
+
return self._peak
|
|
437
615
|
|
|
438
|
-
@
|
|
439
|
-
def
|
|
616
|
+
@peak.setter
|
|
617
|
+
def peak(self, x):
|
|
440
618
|
r"""
|
|
441
619
|
"""
|
|
442
|
-
self.
|
|
620
|
+
self._peak = x
|
|
443
621
|
|
|
444
622
|
@property
|
|
445
623
|
def broadening(self):
|
|
@@ -448,58 +626,141 @@ class dispersion(distribution):
|
|
|
448
626
|
return self._broadening
|
|
449
627
|
|
|
450
628
|
@broadening.setter
|
|
451
|
-
def
|
|
629
|
+
def broadening(self, x):
|
|
452
630
|
r"""
|
|
453
631
|
"""
|
|
454
632
|
self._broadening = x
|
|
455
633
|
|
|
634
|
+
class SpectralLinear(Dispersion):
|
|
635
|
+
r"""Class for the linear dispersion spectral function"""
|
|
636
|
+
def __init__(self, amplitude, peak, broadening, name, index):
|
|
637
|
+
super().__init__(amplitude=amplitude, peak=peak,
|
|
638
|
+
broadening=broadening, name=name, index=index)
|
|
639
|
+
|
|
640
|
+
def __call__(self, angle_range, amplitude, broadening,
|
|
641
|
+
peak):
|
|
642
|
+
r"""
|
|
643
|
+
"""
|
|
644
|
+
result = amplitude / np.pi * broadening / ((np.sin(angle_range * dtor)
|
|
645
|
+
- np.sin(peak * dtor)) ** 2 + broadening ** 2)
|
|
646
|
+
return result
|
|
647
|
+
|
|
648
|
+
def evaluate(self, angle_range):
|
|
649
|
+
r"""
|
|
650
|
+
"""
|
|
651
|
+
return self.amplitude / np.pi * self.broadening / ((np.sin(
|
|
652
|
+
angle_range * dtor) - np.sin(self.peak * dtor)) ** 2 +
|
|
653
|
+
self.broadening ** 2)
|
|
456
654
|
|
|
457
|
-
# class spectral_linear(dispersion):
|
|
458
|
-
# r"""Class for the linear dispersion spectral function"""
|
|
459
|
-
# def __init__(self, amplitude, center, broadening, name, index):
|
|
460
|
-
# super().__init__(amplitude=amplitude, center=center,
|
|
461
|
-
# broadening=broadening, name=name, index=index)
|
|
462
|
-
|
|
463
|
-
# def result(self, x):
|
|
464
|
-
# r"""
|
|
465
|
-
# """
|
|
466
|
-
# dtor = np.pi/180
|
|
467
|
-
# evalf = self.amplitude / np.pi * self.broadening / ((np.sin(x*dtor)
|
|
468
|
-
# - np.sin(self.center*dtor))**2 + self.broadening**2)
|
|
469
|
-
# return evalf
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
# class spectral_linear(dispersion):
|
|
473
|
-
# r"""Class for the linear dispersion spectral function"""
|
|
474
|
-
# def __init__(self, amplitude, center, broadening, bottom, side, name,
|
|
475
|
-
# index):
|
|
476
|
-
# super()__init__(amplitude=amplitude, center=center,
|
|
477
|
-
# broadening=broadening, name=name, index=index)
|
|
478
|
-
# self._bottom = bottom
|
|
479
|
-
# self._side = side
|
|
480
|
-
|
|
481
|
-
# @property
|
|
482
|
-
# def bottom(self):
|
|
483
|
-
# r"""
|
|
484
|
-
# """
|
|
485
|
-
# return self._bottom
|
|
486
|
-
|
|
487
|
-
# @bottom.setter
|
|
488
|
-
# def set_bottom(self, x):
|
|
489
|
-
# r"""
|
|
490
|
-
# """
|
|
491
|
-
# self._bottom = x
|
|
492
|
-
|
|
493
|
-
# @property
|
|
494
|
-
# def side(self):
|
|
495
|
-
# r"""
|
|
496
|
-
# """
|
|
497
|
-
# return self._side
|
|
498
655
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
656
|
+
class SpectralQuadratic(Dispersion):
|
|
657
|
+
r"""Class for the quadratic dispersion spectral function"""
|
|
658
|
+
def __init__(self, amplitude, peak, broadening, name, index,
|
|
659
|
+
center_wavevector=None, center_angle=None):
|
|
660
|
+
self.check_center_coordinates(center_wavevector, center_angle)
|
|
661
|
+
super().__init__(amplitude=amplitude, peak=peak,
|
|
662
|
+
broadening=broadening, name=name, index=index)
|
|
663
|
+
self.center_wavevector = center_wavevector
|
|
664
|
+
self.center_angle = center_angle
|
|
665
|
+
|
|
666
|
+
@property
|
|
667
|
+
def center_angle(self):
|
|
668
|
+
r"""TBD
|
|
669
|
+
"""
|
|
670
|
+
return self._center_angle
|
|
671
|
+
|
|
672
|
+
@center_angle.setter
|
|
673
|
+
def center_angle(self, x):
|
|
674
|
+
r"""TBD
|
|
675
|
+
"""
|
|
676
|
+
self._center_angle = x
|
|
677
|
+
|
|
678
|
+
@property
|
|
679
|
+
def center_wavevector(self):
|
|
680
|
+
r"""TBD
|
|
681
|
+
"""
|
|
682
|
+
return self._center_wavevector
|
|
683
|
+
|
|
684
|
+
@center_wavevector.setter
|
|
685
|
+
def center_wavevector(self, x):
|
|
686
|
+
r"""TBD
|
|
687
|
+
"""
|
|
688
|
+
self._center_wavevector = x
|
|
689
|
+
|
|
690
|
+
def check_center_coordinates(self, center_wavevector, center_angle):
|
|
691
|
+
r"""TBD
|
|
692
|
+
"""
|
|
693
|
+
if (center_wavevector is None and center_angle is None) \
|
|
694
|
+
or (center_wavevector is not None and center_angle is not None):
|
|
695
|
+
raise ValueError('Please specify exactly one of '
|
|
696
|
+
'center_wavevector and center_angle.')
|
|
697
|
+
|
|
698
|
+
def check_binding_angle(self, binding_angle):
|
|
699
|
+
r"""TBD
|
|
700
|
+
"""
|
|
701
|
+
if np.isnan(binding_angle):
|
|
702
|
+
raise ValueError('The provided wavevector cannot be reached '
|
|
703
|
+
'with the available range of kinetic '
|
|
704
|
+
'energies. Please check again.')
|
|
705
|
+
|
|
706
|
+
def __call__(self, angle_range, amplitude, broadening,
|
|
707
|
+
peak, kinetic_energy, hnuminphi, center_wavevector=None,
|
|
708
|
+
center_angle=None):
|
|
709
|
+
r"""TBD
|
|
710
|
+
"""
|
|
711
|
+
self.check_center_coordinates(center_wavevector, center_angle)
|
|
712
|
+
|
|
713
|
+
if center_wavevector is not None:
|
|
714
|
+
binding_angle = np.arcsin(np.sqrt(pref / kinetic_energy)
|
|
715
|
+
* center_wavevector) / dtor
|
|
716
|
+
self.check_binding_angle(binding_angle)
|
|
717
|
+
elif center_angle is not None:
|
|
718
|
+
binding_angle = self.center_angle * np.sqrt(hnuminphi /
|
|
719
|
+
kinetic_energy)
|
|
720
|
+
|
|
721
|
+
return amplitude / np.pi * broadening / (((np.sin(angle_range * dtor)
|
|
722
|
+
- np.sin(binding_angle * dtor)) ** 2 - np.sin(peak * dtor) ** 2)
|
|
723
|
+
** 2 + broadening ** 2)
|
|
724
|
+
|
|
725
|
+
def evaluate(self, angle_range, kinetic_energy, hnuminphi):
|
|
726
|
+
r"""TBD
|
|
727
|
+
"""
|
|
728
|
+
if self.center_wavevector is not None:
|
|
729
|
+
binding_angle = np.arcsin(np.sqrt(pref / kinetic_energy)
|
|
730
|
+
* self.center_wavevector) / dtor
|
|
731
|
+
self.check_binding_angle(binding_angle)
|
|
732
|
+
elif self.center_angle is not None:
|
|
733
|
+
binding_angle = self.center_angle * np.sqrt(hnuminphi /
|
|
734
|
+
kinetic_energy)
|
|
735
|
+
|
|
736
|
+
return self.amplitude / np.pi * self.broadening / (((np.sin(
|
|
737
|
+
angle_range * dtor) - np.sin(binding_angle * dtor)) ** 2 - np.sin(
|
|
738
|
+
self.peak * dtor) ** 2) ** 2 + self.broadening ** 2)
|
|
739
|
+
|
|
740
|
+
@add_fig_kwargs
|
|
741
|
+
def plot(self, angle_range, angle_resolution, kinetic_energy, hnuminphi,
|
|
742
|
+
matrix_element=None, matrix_args=None, ax=None, **kwargs):
|
|
743
|
+
r"""Overwrites generic class plotting method.
|
|
744
|
+
"""
|
|
745
|
+
from scipy.ndimage import gaussian_filter
|
|
746
|
+
|
|
747
|
+
ax, fig, plt = get_ax_fig_plt(ax=ax)
|
|
748
|
+
|
|
749
|
+
ax.set_xlabel('Angle ($\degree$)')
|
|
750
|
+
ax.set_ylabel('Counts (-)')
|
|
751
|
+
|
|
752
|
+
extend, step, numb = extend_function(angle_range, angle_resolution)
|
|
753
|
+
|
|
754
|
+
extended_result = self.evaluate(extend, kinetic_energy, hnuminphi)
|
|
755
|
+
|
|
756
|
+
if matrix_element is not None:
|
|
757
|
+
extended_result *= matrix_element(extend, **matrix_args)
|
|
758
|
+
|
|
759
|
+
final_result = gaussian_filter(extended_result, sigma=step)[
|
|
760
|
+
numb:-numb if numb else None]
|
|
761
|
+
|
|
762
|
+
ax.plot(angle_range, final_result, label=self.label)
|
|
763
|
+
|
|
764
|
+
ax.legend()
|
|
765
|
+
|
|
766
|
+
return fig
|