xarpes 0.2.4__py3-none-any.whl → 0.3.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.
- xarpes/__init__.py +3 -5
- xarpes/constants.py +12 -0
- xarpes/distributions.py +500 -231
- xarpes/functions.py +255 -55
- xarpes/plotting.py +46 -28
- xarpes/spectral.py +2067 -0
- xarpes-0.3.1.dist-info/METADATA +168 -0
- xarpes-0.3.1.dist-info/RECORD +11 -0
- {xarpes-0.2.4.dist-info → xarpes-0.3.1.dist-info}/WHEEL +1 -1
- xarpes-0.3.1.dist-info/entry_points.txt +3 -0
- {xarpes-0.2.4.dist-info → xarpes-0.3.1.dist-info/licenses}/LICENSE +0 -0
- xarpes/.ipynb_checkpoints/__init__-checkpoint.py +0 -8
- xarpes/band_map.py +0 -302
- xarpes-0.2.4.dist-info/METADATA +0 -122
- xarpes-0.2.4.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,17 +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
|
-
|
|
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):
|
|
33
208
|
r"""Parent class for unique distributions, to be used one at a time, e.g.,
|
|
34
209
|
during the background of an MDC fit or the Fermi-Dirac distribution.
|
|
35
210
|
|
|
@@ -55,105 +230,10 @@ class unique_distribution(distribution):
|
|
|
55
230
|
"""
|
|
56
231
|
return self._label
|
|
57
232
|
|
|
58
|
-
class
|
|
59
|
-
r"""Child class for
|
|
60
|
-
The
|
|
61
|
-
|
|
62
|
-
Parameters
|
|
63
|
-
----------
|
|
64
|
-
offset : float
|
|
65
|
-
The value of the distribution for the abscissa equal to 0.
|
|
66
|
-
"""
|
|
67
|
-
def __init__(self, offset, name='constant'):
|
|
68
|
-
super().__init__(name)
|
|
69
|
-
self._offset = offset
|
|
70
|
-
|
|
71
|
-
@property
|
|
72
|
-
def offset(self):
|
|
73
|
-
r"""Returns the offset of the constant distribution.
|
|
74
|
-
|
|
75
|
-
Returns
|
|
76
|
-
-------
|
|
77
|
-
offset : float
|
|
78
|
-
The value of the distribution for the abscissa equal to 0.
|
|
79
|
-
"""
|
|
80
|
-
return self._offset
|
|
81
|
-
|
|
82
|
-
@offset.setter
|
|
83
|
-
def set_offset(self, x):
|
|
84
|
-
r"""Sets the offset of the constant distribution.
|
|
85
|
-
|
|
86
|
-
Parameters
|
|
87
|
-
----------
|
|
88
|
-
offset : float
|
|
89
|
-
The value of the distribution for the abscissa equal to 0.
|
|
90
|
-
"""
|
|
91
|
-
self._offset = x
|
|
92
|
-
|
|
93
|
-
class linear(unique_distribution):
|
|
94
|
-
r"""Child cass for for linear distributions, used e.g., during MDC fitting.
|
|
95
|
-
The constant class is unique, only one instance should be used per task.
|
|
96
|
-
|
|
97
|
-
Parameters
|
|
98
|
-
----------
|
|
99
|
-
offset : float
|
|
100
|
-
The value of the distribution for the abscissa equal to 0.
|
|
101
|
-
slope : float
|
|
102
|
-
The linear slope of the distribution w.r.t. the abscissa.
|
|
103
|
-
"""
|
|
104
|
-
def __init__(self, slope, offset, name='linear'):
|
|
105
|
-
super().__init__(name)
|
|
106
|
-
self._offset = offset
|
|
107
|
-
self._slope = slope
|
|
108
|
-
|
|
109
|
-
@property
|
|
110
|
-
def offset(self):
|
|
111
|
-
r"""Returns the offset of the linear distribution.
|
|
112
|
-
|
|
113
|
-
Returns
|
|
114
|
-
-------
|
|
115
|
-
offset : float
|
|
116
|
-
The value of the distribution for the abscissa equal to 0.
|
|
117
|
-
"""
|
|
118
|
-
return self._offset
|
|
119
|
-
|
|
120
|
-
@offset.setter
|
|
121
|
-
def set_offset(self, x):
|
|
122
|
-
r"""Sets the offset of the linear distribution.
|
|
123
|
-
|
|
124
|
-
Parameters
|
|
125
|
-
----------
|
|
126
|
-
offset : float
|
|
127
|
-
The value of the distribution for the abscissa equal to 0.
|
|
128
|
-
"""
|
|
129
|
-
self._offset = x
|
|
130
|
-
|
|
131
|
-
@property
|
|
132
|
-
def slope(self):
|
|
133
|
-
r"""Returns the slope of the linear distribution.
|
|
134
|
-
|
|
135
|
-
Returns
|
|
136
|
-
-------
|
|
137
|
-
slope : float
|
|
138
|
-
The linear slope of the distribution w.r.t. the abscissa.
|
|
139
|
-
"""
|
|
140
|
-
return self._slope
|
|
141
|
-
|
|
142
|
-
@slope.setter
|
|
143
|
-
def set_slope(self, x):
|
|
144
|
-
r"""Sets the slope of the linear distribution.
|
|
145
|
-
|
|
146
|
-
Parameters
|
|
147
|
-
----------
|
|
148
|
-
slope : float
|
|
149
|
-
The linear slope of the distribution w.r.t. the abscissa.
|
|
150
|
-
"""
|
|
151
|
-
self._slope = x
|
|
152
|
-
|
|
153
|
-
class fermi_dirac(unique_distribution):
|
|
154
|
-
r"""Child class for Fermi-Dirac (FD) distributions, used e.g., during Fermi
|
|
155
|
-
edge fitting. The FD class is unique, only one instance should be used
|
|
156
|
-
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.
|
|
157
237
|
|
|
158
238
|
The Fermi-Dirac distribution is described by the following formula:
|
|
159
239
|
|
|
@@ -177,7 +257,7 @@ class fermi_dirac(unique_distribution):
|
|
|
177
257
|
Integrated weight on top of the background [counts]
|
|
178
258
|
"""
|
|
179
259
|
def __init__(self, temperature, hnuminphi, background=0,
|
|
180
|
-
integrated_weight=1, name='
|
|
260
|
+
integrated_weight=1, name='FermiDirac'):
|
|
181
261
|
super().__init__(name)
|
|
182
262
|
self.temperature = temperature
|
|
183
263
|
self.hnuminphi = hnuminphi
|
|
@@ -196,7 +276,7 @@ class fermi_dirac(unique_distribution):
|
|
|
196
276
|
return self._temperature
|
|
197
277
|
|
|
198
278
|
@temperature.setter
|
|
199
|
-
def
|
|
279
|
+
def temperature(self, x):
|
|
200
280
|
r"""Sets the temperature of the FD distribution.
|
|
201
281
|
|
|
202
282
|
Parameters
|
|
@@ -219,7 +299,7 @@ class fermi_dirac(unique_distribution):
|
|
|
219
299
|
return self._hnuminphi
|
|
220
300
|
|
|
221
301
|
@hnuminphi.setter
|
|
222
|
-
def
|
|
302
|
+
def hnuminphi(self, x):
|
|
223
303
|
r"""Sets the photon energy minus the work function of the FD
|
|
224
304
|
distribution.
|
|
225
305
|
|
|
@@ -242,7 +322,7 @@ class fermi_dirac(unique_distribution):
|
|
|
242
322
|
return self._background
|
|
243
323
|
|
|
244
324
|
@background.setter
|
|
245
|
-
def
|
|
325
|
+
def background(self, x):
|
|
246
326
|
r"""Sets the background intensity of the FD distribution.
|
|
247
327
|
|
|
248
328
|
Parameters
|
|
@@ -264,7 +344,7 @@ class fermi_dirac(unique_distribution):
|
|
|
264
344
|
return self._integrated_weight
|
|
265
345
|
|
|
266
346
|
@integrated_weight.setter
|
|
267
|
-
def
|
|
347
|
+
def integrated_weight(self, x):
|
|
268
348
|
r"""Sets the integrated weight of the FD distribution.
|
|
269
349
|
|
|
270
350
|
Parameters
|
|
@@ -275,7 +355,7 @@ class fermi_dirac(unique_distribution):
|
|
|
275
355
|
self._integrated_weight = x
|
|
276
356
|
|
|
277
357
|
def __call__(self, energy_range, hnuminphi, background, integrated_weight,
|
|
278
|
-
|
|
358
|
+
temperature):
|
|
279
359
|
"""Call method to directly evaluate a FD distribution without having to
|
|
280
360
|
instantiate a class instance.
|
|
281
361
|
|
|
@@ -297,23 +377,10 @@ class fermi_dirac(unique_distribution):
|
|
|
297
377
|
evalf : ndarray
|
|
298
378
|
1D array of the energy-convolved FD distribution [counts]
|
|
299
379
|
"""
|
|
300
|
-
|
|
380
|
+
k_BT = temperature * k_B
|
|
301
381
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
fwhm_to_std = np.sqrt(8 * np.log(2))
|
|
305
|
-
k_B = 8.617e-5 # Boltzmann constant [eV/K]
|
|
306
|
-
k_BT = self.temperature * k_B
|
|
307
|
-
step_size = np.abs(energy_range[1] - energy_range[0])
|
|
308
|
-
estep = energy_resolution / (step_size * fwhm_to_std)
|
|
309
|
-
enumb = int(sigma_extend * estep)
|
|
310
|
-
extend = np.linspace(energy_range[0] - enumb * step_size,
|
|
311
|
-
energy_range[-1] + enumb * step_size,
|
|
312
|
-
len(energy_range) + 2 * enumb)
|
|
313
|
-
result = (integrated_weight / (1 + np.exp((extend - hnuminphi) / k_BT))
|
|
314
|
-
+ background)
|
|
315
|
-
evalf = gaussian_filter(result, sigma=estep)[enumb:-enumb]
|
|
316
|
-
return evalf
|
|
382
|
+
return (integrated_weight / (1 + np.exp((energy_range - hnuminphi)
|
|
383
|
+
/ k_BT)) + background)
|
|
317
384
|
|
|
318
385
|
def evaluate(self, energy_range):
|
|
319
386
|
r"""Evaluates the FD distribution for a given class instance.
|
|
@@ -329,47 +396,157 @@ class fermi_dirac(unique_distribution):
|
|
|
329
396
|
evalf : ndarray
|
|
330
397
|
1D array of the evaluated FD distribution [counts]
|
|
331
398
|
"""
|
|
332
|
-
k_B = 8.617e-5 # Boltzmann constant [eV/K]
|
|
333
399
|
k_BT = self.temperature * k_B
|
|
334
|
-
|
|
400
|
+
|
|
401
|
+
return (self.integrated_weight
|
|
335
402
|
/ (1 + np.exp((energy_range - self.hnuminphi) / k_BT))
|
|
336
403
|
+ self.background)
|
|
337
|
-
return evalf
|
|
338
404
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
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.
|
|
344
460
|
|
|
345
461
|
Parameters
|
|
346
462
|
----------
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
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.
|
|
351
498
|
|
|
352
499
|
Returns
|
|
353
500
|
-------
|
|
354
|
-
|
|
355
|
-
|
|
501
|
+
offset : float
|
|
502
|
+
The value of the distribution for the abscissa equal to 0.
|
|
356
503
|
"""
|
|
357
|
-
|
|
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.
|
|
358
531
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
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):
|
|
373
550
|
r"""Parent class for unique distributions, to be used one at a time, e.g.,
|
|
374
551
|
during the background of an MDC fit or the Fermi-Dirac distribution.
|
|
375
552
|
|
|
@@ -381,7 +558,8 @@ class non_unique_distribution(distribution):
|
|
|
381
558
|
"""
|
|
382
559
|
def __init__(self, name, index):
|
|
383
560
|
super().__init__(name)
|
|
384
|
-
self._label = name + index
|
|
561
|
+
self._label = name + '_' + index
|
|
562
|
+
self._index = index
|
|
385
563
|
|
|
386
564
|
@property
|
|
387
565
|
def label(self):
|
|
@@ -390,26 +568,32 @@ class non_unique_distribution(distribution):
|
|
|
390
568
|
Returns
|
|
391
569
|
-------
|
|
392
570
|
label : str
|
|
393
|
-
Unique label for instances,
|
|
394
|
-
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.
|
|
395
574
|
"""
|
|
396
575
|
return self._label
|
|
397
576
|
|
|
398
|
-
class dispersion(distribution):
|
|
399
|
-
r"""Dispersions are assumed to be unique, so they need an index.
|
|
400
|
-
"""
|
|
401
|
-
def __init__(self, amplitude, center, broadening, name, index):
|
|
402
|
-
super().__init__(name)
|
|
403
|
-
self._amplitude = amplitude
|
|
404
|
-
self._center = center
|
|
405
|
-
self._broadening = broadening
|
|
406
|
-
self._label = name + index
|
|
407
|
-
|
|
408
577
|
@property
|
|
409
|
-
def
|
|
410
|
-
r"""
|
|
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.
|
|
411
586
|
"""
|
|
412
|
-
return self.
|
|
587
|
+
return self._index
|
|
588
|
+
|
|
589
|
+
class Dispersion(NonUniqueDistribution):
|
|
590
|
+
r"""Dispersions are assumed to be unique, so they need an index.
|
|
591
|
+
"""
|
|
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
|
|
413
597
|
|
|
414
598
|
@property
|
|
415
599
|
def amplitude(self):
|
|
@@ -418,22 +602,22 @@ class dispersion(distribution):
|
|
|
418
602
|
return self._amplitude
|
|
419
603
|
|
|
420
604
|
@amplitude.setter
|
|
421
|
-
def
|
|
605
|
+
def amplitude(self, x):
|
|
422
606
|
r"""
|
|
423
607
|
"""
|
|
424
608
|
self._amplitude = x
|
|
425
609
|
|
|
426
610
|
@property
|
|
427
|
-
def
|
|
611
|
+
def peak(self):
|
|
428
612
|
r"""
|
|
429
613
|
"""
|
|
430
|
-
return self.
|
|
614
|
+
return self._peak
|
|
431
615
|
|
|
432
|
-
@
|
|
433
|
-
def
|
|
616
|
+
@peak.setter
|
|
617
|
+
def peak(self, x):
|
|
434
618
|
r"""
|
|
435
619
|
"""
|
|
436
|
-
self.
|
|
620
|
+
self._peak = x
|
|
437
621
|
|
|
438
622
|
@property
|
|
439
623
|
def broadening(self):
|
|
@@ -442,56 +626,141 @@ class dispersion(distribution):
|
|
|
442
626
|
return self._broadening
|
|
443
627
|
|
|
444
628
|
@broadening.setter
|
|
445
|
-
def
|
|
629
|
+
def broadening(self, x):
|
|
446
630
|
r"""
|
|
447
631
|
"""
|
|
448
632
|
self._broadening = x
|
|
449
633
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
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)
|
|
654
|
+
|
|
655
|
+
|
|
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
|