xarpes 0.2.4__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/distributions.py CHANGED
@@ -1,11 +1,151 @@
1
- # Copyright (C) 2024 xARPES Developers
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
- class distribution:
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"""Returns the name of the class instance.
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
- class unique_distribution(distribution):
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 constant(unique_distribution):
59
- r"""Child class for constant distributions, used e.g., during MDC fitting.
60
- The constant class is unique, only one instance should be used per task.
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='fermi_dirac'):
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 set_temperature(self, x):
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 set_hnuminphi(self, x):
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 set_background(self, x):
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 set_integrated_weight(self, x):
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
- energy_resolution):
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
- from scipy.ndimage import gaussian_filter
380
+ k_BT = temperature * k_B
301
381
 
302
- sigma_extend = 5 # Extend data range by "5 sigma"
303
- # Conversion from FWHM to standard deviation [-]
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
- evalf = (self.integrated_weight
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
- def convolve(self, energy_range, energy_resolution):
340
- r"""Evaluates the FD distribution for a given class instance and
341
- performs the energy convolution with the given resolution. The
342
- convolution is performed with an expanded abscissa range of 5
343
- times the standard deviation.
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
- energy_range : ndarray
348
- 1D array on which to evaluate and convolve FD distribution [eV]
349
- energy_resolution : float
350
- Energy resolution of the detector for the convolution [eV]
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
- evalf : ndarray
355
- 1D array of the energy-convolved FD distribution [counts]
501
+ offset : float
502
+ The value of the distribution for the abscissa equal to 0.
356
503
  """
357
- from scipy.ndimage import gaussian_filter
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
- sigma_extend = 5 # Extend data range by "5 sigma"
360
- # Conversion from FWHM to standard deviation [-]
361
- fwhm_to_std = np.sqrt(8 * np.log(2))
362
- step_size = np.abs(energy_range[1] - energy_range[0])
363
- estep = energy_resolution / (step_size * fwhm_to_std)
364
- enumb = int(sigma_extend * estep)
365
- extend = np.linspace(energy_range[0] - enumb * step_size,
366
- energy_range[-1] + enumb * step_size,
367
- len(energy_range) + 2 * enumb)
368
- evalf = gaussian_filter(self.evaluate(extend),
369
- sigma=estep)[enumb:-enumb]
370
- return evalf
371
-
372
- class non_unique_distribution(distribution):
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, identical to the name for unique
394
- distributions. Not to be modified after instantiation.
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 label(self):
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._label
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 set_amplitude(self, x):
605
+ def amplitude(self, x):
422
606
  r"""
423
607
  """
424
608
  self._amplitude = x
425
609
 
426
610
  @property
427
- def center(self):
611
+ def peak(self):
428
612
  r"""
429
613
  """
430
- return self._center
614
+ return self._peak
431
615
 
432
- @amplitude.setter
433
- def set_center(self, x):
616
+ @peak.setter
617
+ def peak(self, x):
434
618
  r"""
435
619
  """
436
- self._center = x
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 set_broadening(self, x):
629
+ def broadening(self, x):
446
630
  r"""
447
631
  """
448
632
  self._broadening = x
449
633
 
450
- # class spectral_linear(dispersion):
451
- # r"""Class for the linear dispersion spectral function"""
452
- # def __init__(self, amplitude, center, broadening, name, index):
453
- # super().__init__(amplitude=amplitude, center=center,
454
- # broadening=broadening, name=name, index=index)
455
-
456
- # def result(self, x):
457
- # r"""
458
- # """
459
- # dtor = np.pi/180
460
- # evalf = self.amplitude / np.pi * self.broadening / ((np.sin(x*dtor)
461
- # - np.sin(self.center*dtor))**2 + self.broadening**2)
462
- # return evalf
463
-
464
- # class spectral_linear(dispersion):
465
- # r"""Class for the linear dispersion spectral function"""
466
- # def __init__(self, amplitude, center, broadening, bottom, side, name,
467
- # index):
468
- # super()__init__(amplitude=amplitude, center=center,
469
- # broadening=broadening, name=name, index=index)
470
- # self._bottom = bottom
471
- # self._side = side
472
-
473
- # @property
474
- # def bottom(self):
475
- # r"""
476
- # """
477
- # return self._bottom
478
-
479
- # @bottom.setter
480
- # def set_bottom(self, x):
481
- # r"""
482
- # """
483
- # self._bottom = x
484
-
485
- # @property
486
- # def side(self):
487
- # r"""
488
- # """
489
- # return self._side
490
-
491
- # def result(self, x):
492
- # r"""
493
- # """
494
- # dtor = np.pi/180
495
- # evalf = self.amplitude / np.pi * self.broadening / (((np.sin(x*dtor)
496
- # - np.sin(self.bottom*dtor))**2 - np.sin(self.center*dtor)**2)**2
497
- # + self.broadening**2)
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