xarpes 0.4.0__py3-none-any.whl → 0.5.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 +35 -5
- xarpes/bandmap.py +829 -0
- xarpes/constants.py +9 -8
- xarpes/distributions.py +45 -43
- xarpes/functions.py +18 -6
- xarpes/mdcs.py +1035 -0
- xarpes/plotting.py +1 -47
- xarpes/selfenergies.py +621 -0
- xarpes/settings_parameters.py +30 -0
- xarpes/settings_plots.py +54 -0
- {xarpes-0.4.0.dist-info → xarpes-0.5.0.dist-info}/METADATA +5 -4
- xarpes-0.5.0.dist-info/RECORD +15 -0
- {xarpes-0.4.0.dist-info → xarpes-0.5.0.dist-info}/WHEEL +1 -1
- xarpes/spectral.py +0 -2476
- xarpes-0.4.0.dist-info/RECORD +0 -11
- {xarpes-0.4.0.dist-info/licenses → xarpes-0.5.0.dist-info}/LICENSE +0 -0
- {xarpes-0.4.0.dist-info → xarpes-0.5.0.dist-info}/entry_points.txt +0 -0
xarpes/plotting.py
CHANGED
|
@@ -11,54 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
"""Functions related to plotting."""
|
|
13
13
|
|
|
14
|
-
from functools import wraps
|
|
15
|
-
from IPython import get_ipython
|
|
16
14
|
import matplotlib.pyplot as plt
|
|
17
|
-
import matplotlib as mpl
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def plot_settings(name='default', register_pre_run=True):
|
|
21
|
-
"""Configure default plotting style for xARPES."""
|
|
22
|
-
|
|
23
|
-
mpl.rc('xtick', labelsize=10, direction='in')
|
|
24
|
-
mpl.rc('ytick', labelsize=10, direction='in')
|
|
25
|
-
plt.rcParams['legend.frameon'] = False
|
|
26
|
-
lw = dict(default=2.0, large=4.0)[name]
|
|
27
|
-
|
|
28
|
-
mpl.rcParams.update({
|
|
29
|
-
'lines.linewidth': lw,
|
|
30
|
-
'lines.markersize': 3,
|
|
31
|
-
'xtick.major.size': 4,
|
|
32
|
-
'xtick.minor.size': 2,
|
|
33
|
-
'xtick.major.width': 0.8,
|
|
34
|
-
'font.size': 16,
|
|
35
|
-
'axes.ymargin': 0.15,
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
if register_pre_run:
|
|
39
|
-
_maybe_register_pre_run_close_all()
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
def _maybe_register_pre_run_close_all():
|
|
43
|
-
"""Register a pre_run_cell hook once, and only inside Jupyter."""
|
|
44
|
-
|
|
45
|
-
# Create the function attribute on first call
|
|
46
|
-
if not hasattr(_maybe_register_pre_run_close_all, "_registered"):
|
|
47
|
-
_maybe_register_pre_run_close_all._registered = False
|
|
48
|
-
|
|
49
|
-
if _maybe_register_pre_run_close_all._registered:
|
|
50
|
-
return
|
|
51
|
-
|
|
52
|
-
ip = get_ipython()
|
|
53
|
-
if ip is None or ip.__class__.__name__ != "ZMQInteractiveShell":
|
|
54
|
-
return
|
|
55
|
-
|
|
56
|
-
def _close_all(_info):
|
|
57
|
-
plt.close('all')
|
|
58
|
-
|
|
59
|
-
ip.events.register('pre_run_cell', _close_all)
|
|
60
|
-
_maybe_register_pre_run_close_all._registered = True
|
|
61
|
-
|
|
62
15
|
|
|
63
16
|
def get_ax_fig_plt(ax=None, **kwargs):
|
|
64
17
|
r"""Helper function used in plot functions supporting an optional `Axes`
|
|
@@ -100,6 +53,7 @@ def add_fig_kwargs(func):
|
|
|
100
53
|
first element is a matplotlib figure, or None to signal some sort of
|
|
101
54
|
error/unexpected event.
|
|
102
55
|
"""
|
|
56
|
+
from functools import wraps
|
|
103
57
|
@wraps(func)
|
|
104
58
|
def wrapper(*args, **kwargs):
|
|
105
59
|
# pop the kwds used by the decorator.
|
xarpes/selfenergies.py
ADDED
|
@@ -0,0 +1,621 @@
|
|
|
1
|
+
# Copyright (C) 2025 xARPES Developers
|
|
2
|
+
# This program is free software under the terms of the GNU GPLv3 license.
|
|
3
|
+
|
|
4
|
+
# get_ax_fig_plt and add_fig_kwargs originate from pymatgen/util/plotting.py.
|
|
5
|
+
# Copyright (C) 2011-2024 Shyue Ping Ong and the pymatgen Development Team
|
|
6
|
+
# Pymatgen is released under the MIT License.
|
|
7
|
+
|
|
8
|
+
# See also abipy/tools/plotting.py.
|
|
9
|
+
# Copyright (C) 2021 Matteo Giantomassi and the AbiPy Group
|
|
10
|
+
# AbiPy is free software under the terms of the GNU GPLv2 license.
|
|
11
|
+
|
|
12
|
+
"""File containing the band map class."""
|
|
13
|
+
|
|
14
|
+
import numpy as np
|
|
15
|
+
from .plotting import get_ax_fig_plt, add_fig_kwargs
|
|
16
|
+
from .constants import PREF
|
|
17
|
+
|
|
18
|
+
class SelfEnergy:
|
|
19
|
+
r"""Self-energy (ekin-leading; hnuminPhi/ekin are read-only)."""
|
|
20
|
+
|
|
21
|
+
def __init__(self, ekin_range, hnuminPhi, label, properties, parameters):
|
|
22
|
+
# core read-only state
|
|
23
|
+
self._ekin_range = ekin_range
|
|
24
|
+
self._hnuminPhi = hnuminPhi
|
|
25
|
+
self._label = label
|
|
26
|
+
|
|
27
|
+
# accept either a dict or a single-element list of dicts
|
|
28
|
+
if isinstance(properties, list):
|
|
29
|
+
if len(properties) == 1:
|
|
30
|
+
properties = properties[0]
|
|
31
|
+
else:
|
|
32
|
+
raise ValueError("`properties` must be a dict or a single " \
|
|
33
|
+
"dict in a list.")
|
|
34
|
+
|
|
35
|
+
# single source of truth for all params (+ their *_sigma)
|
|
36
|
+
self._properties = dict(properties or {})
|
|
37
|
+
self._class = self._properties.get("_class", None)
|
|
38
|
+
|
|
39
|
+
# ---- enforce supported classes at construction
|
|
40
|
+
if self._class not in ("SpectralLinear", "SpectralQuadratic"):
|
|
41
|
+
raise ValueError(
|
|
42
|
+
f"Unsupported spectral class '{self._class}'. "
|
|
43
|
+
"Only 'SpectralLinear' or 'SpectralQuadratic' are allowed."
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# grab user parameters
|
|
47
|
+
self._parameters = dict(parameters or {})
|
|
48
|
+
self._fermi_wavevector = self._parameters.get("fermi_wavevector")
|
|
49
|
+
self._fermi_velocity = self._parameters.get("fermi_velocity")
|
|
50
|
+
self._bare_mass = self._parameters.get("bare_mass")
|
|
51
|
+
self._side = self._parameters.get("side", None)
|
|
52
|
+
|
|
53
|
+
# ---- class-specific parameter constraints
|
|
54
|
+
if self._class == "SpectralLinear" and (self._bare_mass is not None):
|
|
55
|
+
raise ValueError("`bare_mass` cannot be set for SpectralLinear.")
|
|
56
|
+
if self._class == "SpectralQuadratic" and (self._fermi_velocity is not None):
|
|
57
|
+
raise ValueError("`fermi_velocity` cannot be set for SpectralQuadratic.")
|
|
58
|
+
|
|
59
|
+
if self._side is not None and self._side not in ("left", "right"):
|
|
60
|
+
raise ValueError("`side` must be 'left' or 'right' if provided.")
|
|
61
|
+
if self._side is not None:
|
|
62
|
+
self._parameters["side"] = self._side
|
|
63
|
+
|
|
64
|
+
# convenience attributes (read from properties)
|
|
65
|
+
self._amplitude = self._properties.get("amplitude")
|
|
66
|
+
self._amplitude_sigma = self._properties.get("amplitude_sigma")
|
|
67
|
+
self._peak = self._properties.get("peak")
|
|
68
|
+
self._peak_sigma = self._properties.get("peak_sigma")
|
|
69
|
+
self._broadening = self._properties.get("broadening")
|
|
70
|
+
self._broadening_sigma = self._properties.get("broadening_sigma")
|
|
71
|
+
self._center_wavevector = self._properties.get("center_wavevector")
|
|
72
|
+
|
|
73
|
+
# lazy caches
|
|
74
|
+
self._peak_positions = None
|
|
75
|
+
self._peak_positions_sigma = None
|
|
76
|
+
self._real = None
|
|
77
|
+
self._real_sigma = None
|
|
78
|
+
self._imag = None
|
|
79
|
+
self._imag_sigma = None
|
|
80
|
+
|
|
81
|
+
def _check_mass_velocity_exclusivity(self):
|
|
82
|
+
"""Ensure that fermi_velocity and bare_mass are not both set."""
|
|
83
|
+
if (self._fermi_velocity is not None) and (self._bare_mass is not None):
|
|
84
|
+
raise ValueError(
|
|
85
|
+
"Cannot set both `fermi_velocity` and `bare_mass`: "
|
|
86
|
+
"choose one physical parametrization (SpectralLinear or SpectralQuadratic)."
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
# ---------------- core read-only axes ----------------
|
|
90
|
+
@property
|
|
91
|
+
def ekin_range(self):
|
|
92
|
+
return self._ekin_range
|
|
93
|
+
|
|
94
|
+
@property
|
|
95
|
+
def enel_range(self):
|
|
96
|
+
if self._ekin_range is None:
|
|
97
|
+
return None
|
|
98
|
+
hnp = 0.0 if self._hnuminPhi is None else self._hnuminPhi
|
|
99
|
+
return np.asarray(self._ekin_range) - hnp
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def hnuminPhi(self):
|
|
103
|
+
return self._hnuminPhi
|
|
104
|
+
|
|
105
|
+
# ---------------- identifiers ----------------
|
|
106
|
+
@property
|
|
107
|
+
def label(self):
|
|
108
|
+
return self._label
|
|
109
|
+
|
|
110
|
+
@label.setter
|
|
111
|
+
def label(self, x):
|
|
112
|
+
self._label = x
|
|
113
|
+
|
|
114
|
+
# ---------------- exported user parameters ----------------
|
|
115
|
+
@property
|
|
116
|
+
def parameters(self):
|
|
117
|
+
"""Dictionary with user-supplied parameters (read-only view)."""
|
|
118
|
+
return self._parameters
|
|
119
|
+
|
|
120
|
+
@property
|
|
121
|
+
def side(self):
|
|
122
|
+
"""Optional side selector: 'left' or 'right'."""
|
|
123
|
+
return self._side
|
|
124
|
+
|
|
125
|
+
@side.setter
|
|
126
|
+
def side(self, x):
|
|
127
|
+
if x is not None and x not in ("left", "right"):
|
|
128
|
+
raise ValueError("`side` must be 'left' or 'right' if provided.")
|
|
129
|
+
self._side = x
|
|
130
|
+
if x is not None:
|
|
131
|
+
self._parameters["side"] = x
|
|
132
|
+
else:
|
|
133
|
+
self._parameters.pop("side", None)
|
|
134
|
+
# affects sign of peak_positions and thus `real`
|
|
135
|
+
self._peak_positions = None
|
|
136
|
+
self._real = None
|
|
137
|
+
self._real_sigma = None
|
|
138
|
+
self._mdc_maxima = None
|
|
139
|
+
|
|
140
|
+
@property
|
|
141
|
+
def fermi_wavevector(self):
|
|
142
|
+
"""Optional k_F; can be set later."""
|
|
143
|
+
return self._fermi_wavevector
|
|
144
|
+
|
|
145
|
+
@fermi_wavevector.setter
|
|
146
|
+
def fermi_wavevector(self, x):
|
|
147
|
+
self._fermi_wavevector = x
|
|
148
|
+
self._parameters["fermi_wavevector"] = x
|
|
149
|
+
# invalidate dependent cache
|
|
150
|
+
self._real = None
|
|
151
|
+
self._real_sigma = None
|
|
152
|
+
|
|
153
|
+
@property
|
|
154
|
+
def fermi_velocity(self):
|
|
155
|
+
"""Optional v_F; can be set later."""
|
|
156
|
+
return self._fermi_velocity
|
|
157
|
+
|
|
158
|
+
@fermi_velocity.setter
|
|
159
|
+
def fermi_velocity(self, x):
|
|
160
|
+
if self._class == "SpectralQuadratic":
|
|
161
|
+
raise ValueError("`fermi_velocity` cannot be set for" \
|
|
162
|
+
" SpectralQuadratic.")
|
|
163
|
+
self._fermi_velocity = x
|
|
164
|
+
self._parameters["fermi_velocity"] = x
|
|
165
|
+
# invalidate dependents
|
|
166
|
+
self._imag = None; self._imag_sigma = None
|
|
167
|
+
self._real = None; self._real_sigma = None
|
|
168
|
+
|
|
169
|
+
@property
|
|
170
|
+
def bare_mass(self):
|
|
171
|
+
"""Optional bare mass; used by SpectralQuadratic formulas."""
|
|
172
|
+
return self._bare_mass
|
|
173
|
+
|
|
174
|
+
@bare_mass.setter
|
|
175
|
+
def bare_mass(self, x):
|
|
176
|
+
if self._class == "SpectralLinear":
|
|
177
|
+
raise ValueError("`bare_mass` cannot be set for SpectralLinear.")
|
|
178
|
+
self._bare_mass = x
|
|
179
|
+
self._parameters["bare_mass"] = x
|
|
180
|
+
# invalidate dependents
|
|
181
|
+
self._imag = None; self._imag_sigma = None
|
|
182
|
+
self._real = None; self._real_sigma = None
|
|
183
|
+
|
|
184
|
+
# ---------------- optional fit parameters (convenience) ----------------
|
|
185
|
+
@property
|
|
186
|
+
def amplitude(self):
|
|
187
|
+
return self._amplitude
|
|
188
|
+
|
|
189
|
+
@amplitude.setter
|
|
190
|
+
def amplitude(self, x):
|
|
191
|
+
self._amplitude = x
|
|
192
|
+
self._properties["amplitude"] = x
|
|
193
|
+
|
|
194
|
+
@property
|
|
195
|
+
def amplitude_sigma(self):
|
|
196
|
+
return self._amplitude_sigma
|
|
197
|
+
|
|
198
|
+
@amplitude_sigma.setter
|
|
199
|
+
def amplitude_sigma(self, x):
|
|
200
|
+
self._amplitude_sigma = x
|
|
201
|
+
self._properties["amplitude_sigma"] = x
|
|
202
|
+
|
|
203
|
+
@property
|
|
204
|
+
def peak(self):
|
|
205
|
+
return self._peak
|
|
206
|
+
|
|
207
|
+
@peak.setter
|
|
208
|
+
def peak(self, x):
|
|
209
|
+
self._peak = x
|
|
210
|
+
self._properties["peak"] = x
|
|
211
|
+
# invalidate dependent cache
|
|
212
|
+
self._peak_positions = None
|
|
213
|
+
self._real = None
|
|
214
|
+
self._mdc_maxima = None
|
|
215
|
+
|
|
216
|
+
@property
|
|
217
|
+
def peak_sigma(self):
|
|
218
|
+
return self._peak_sigma
|
|
219
|
+
|
|
220
|
+
@peak_sigma.setter
|
|
221
|
+
def peak_sigma(self, x):
|
|
222
|
+
self._peak_sigma = x
|
|
223
|
+
self._properties["peak_sigma"] = x
|
|
224
|
+
self._peak_positions_sigma = None
|
|
225
|
+
self._real_sigma = None
|
|
226
|
+
|
|
227
|
+
@property
|
|
228
|
+
def broadening(self):
|
|
229
|
+
return self._broadening
|
|
230
|
+
|
|
231
|
+
@broadening.setter
|
|
232
|
+
def broadening(self, x):
|
|
233
|
+
self._broadening = x
|
|
234
|
+
self._properties["broadening"] = x
|
|
235
|
+
self._imag = None
|
|
236
|
+
|
|
237
|
+
@property
|
|
238
|
+
def broadening_sigma(self):
|
|
239
|
+
return self._broadening_sigma
|
|
240
|
+
|
|
241
|
+
@broadening_sigma.setter
|
|
242
|
+
def broadening_sigma(self, x):
|
|
243
|
+
self._broadening_sigma = x
|
|
244
|
+
self._properties["broadening_sigma"] = x
|
|
245
|
+
self._imag_sigma = None
|
|
246
|
+
|
|
247
|
+
@property
|
|
248
|
+
def center_wavevector(self):
|
|
249
|
+
"""Read-only center wavevector (SpectralQuadratic, if present)."""
|
|
250
|
+
return self._center_wavevector
|
|
251
|
+
|
|
252
|
+
# ---------------- derived outputs ----------------
|
|
253
|
+
@property
|
|
254
|
+
def peak_positions(self):
|
|
255
|
+
r"""k_parallel = peak * dtor * sqrt(ekin_range / pref) (lazy)."""
|
|
256
|
+
if self._peak_positions is None:
|
|
257
|
+
if self._peak is None or self._ekin_range is None:
|
|
258
|
+
return None
|
|
259
|
+
if self._class == "SpectralQuadratic":
|
|
260
|
+
if self._side is None:
|
|
261
|
+
raise AttributeError(
|
|
262
|
+
"For SpectralQuadratic, set `side` ('left'/'right') "
|
|
263
|
+
"before accessing peak_positions and quantities that "
|
|
264
|
+
"depend on the latter."
|
|
265
|
+
)
|
|
266
|
+
kpar_mag = np.sqrt(self._ekin_range / PREF) * \
|
|
267
|
+
np.sin(np.deg2rad(np.abs(self._peak)))
|
|
268
|
+
self._peak_positions = (-1.0 if self._side == "left" \
|
|
269
|
+
else 1.0) * kpar_mag
|
|
270
|
+
else:
|
|
271
|
+
self._peak_positions = np.sqrt(self._ekin_range / PREF) \
|
|
272
|
+
* np.sin(np.deg2rad(self._peak))
|
|
273
|
+
return self._peak_positions
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
@property
|
|
277
|
+
def peak_positions_sigma(self):
|
|
278
|
+
r"""Std. dev. of k_parallel (lazy)."""
|
|
279
|
+
if self._peak_positions_sigma is None:
|
|
280
|
+
if self._peak_sigma is None or self._ekin_range is None:
|
|
281
|
+
return None
|
|
282
|
+
self._peak_positions_sigma = (np.sqrt(self._ekin_range / PREF) \
|
|
283
|
+
* np.abs(np.cos(np.deg2rad(self._peak))) \
|
|
284
|
+
* np.deg2rad(self._peak_sigma))
|
|
285
|
+
return self._peak_positions_sigma
|
|
286
|
+
|
|
287
|
+
@property
|
|
288
|
+
def imag(self):
|
|
289
|
+
r"""-Σ'' (lazy)."""
|
|
290
|
+
if self._imag is None:
|
|
291
|
+
if self._broadening is None or self._ekin_range is None:
|
|
292
|
+
return None
|
|
293
|
+
if self._class == "SpectralLinear":
|
|
294
|
+
if self._fermi_velocity is None:
|
|
295
|
+
raise AttributeError("Cannot compute `imag` "
|
|
296
|
+
"(SpectralLinear): set `fermi_velocity` first.")
|
|
297
|
+
self._imag = np.abs(self._fermi_velocity) * np.sqrt(self._ekin_range \
|
|
298
|
+
/ PREF) * self._broadening
|
|
299
|
+
else:
|
|
300
|
+
if self._bare_mass is None:
|
|
301
|
+
raise AttributeError("Cannot compute `imag` "
|
|
302
|
+
"(SpectralQuadratic): set `bare_mass` first.")
|
|
303
|
+
self._imag = (self._ekin_range * self._broadening) \
|
|
304
|
+
/ np.abs(self._bare_mass)
|
|
305
|
+
return self._imag
|
|
306
|
+
|
|
307
|
+
@property
|
|
308
|
+
def imag_sigma(self):
|
|
309
|
+
r"""Std. dev. of -Σ'' (lazy)."""
|
|
310
|
+
if self._imag_sigma is None:
|
|
311
|
+
if self._broadening_sigma is None or self._ekin_range is None:
|
|
312
|
+
return None
|
|
313
|
+
if self._class == "SpectralLinear":
|
|
314
|
+
if self._fermi_velocity is None:
|
|
315
|
+
raise AttributeError("Cannot compute `imag_sigma` "
|
|
316
|
+
"(SpectralLinear): set `fermi_velocity` first.")
|
|
317
|
+
self._imag_sigma = np.abs(self._fermi_velocity) * \
|
|
318
|
+
np.sqrt(self._ekin_range / PREF) * self._broadening_sigma
|
|
319
|
+
else:
|
|
320
|
+
if self._bare_mass is None:
|
|
321
|
+
raise AttributeError("Cannot compute `imag_sigma` "
|
|
322
|
+
"(SpectralQuadratic): set `bare_mass` first.")
|
|
323
|
+
self._imag_sigma = (self._ekin_range * \
|
|
324
|
+
self._broadening_sigma) / np.abs(self._bare_mass)
|
|
325
|
+
return self._imag_sigma
|
|
326
|
+
|
|
327
|
+
@property
|
|
328
|
+
def real(self):
|
|
329
|
+
r"""Σ' (lazy)."""
|
|
330
|
+
if self._real is None:
|
|
331
|
+
if self._peak is None or self._ekin_range is None:
|
|
332
|
+
return None
|
|
333
|
+
if self._class == "SpectralLinear":
|
|
334
|
+
if self._fermi_velocity is None or self._fermi_wavevector is None:
|
|
335
|
+
raise AttributeError("Cannot compute `real` "
|
|
336
|
+
"(SpectralLinear): set `fermi_velocity` and " \
|
|
337
|
+
"`fermi_wavevector` first.")
|
|
338
|
+
self._real = self.enel_range - self._fermi_velocity * \
|
|
339
|
+
(self.peak_positions - self._fermi_wavevector)
|
|
340
|
+
else:
|
|
341
|
+
if self._bare_mass is None or self._fermi_wavevector is None:
|
|
342
|
+
raise AttributeError("Cannot compute `real` "
|
|
343
|
+
"(SpectralQuadratic): set `bare_mass` and " \
|
|
344
|
+
"`fermi_wavevector` first.")
|
|
345
|
+
self._real = self.enel_range - (PREF / \
|
|
346
|
+
self._bare_mass) * (self.peak_positions**2 \
|
|
347
|
+
- self._fermi_wavevector**2)
|
|
348
|
+
return self._real
|
|
349
|
+
|
|
350
|
+
@property
|
|
351
|
+
def real_sigma(self):
|
|
352
|
+
r"""Std. dev. of Σ' (lazy)."""
|
|
353
|
+
if self._real_sigma is None:
|
|
354
|
+
if self._peak_sigma is None or self._ekin_range is None:
|
|
355
|
+
return None
|
|
356
|
+
if self._class == "SpectralLinear":
|
|
357
|
+
if self._fermi_velocity is None:
|
|
358
|
+
raise AttributeError("Cannot compute `real_sigma` "
|
|
359
|
+
"(SpectralLinear): set `fermi_velocity` first.")
|
|
360
|
+
self._real_sigma = np.abs(self._fermi_velocity) * self.peak_positions_sigma
|
|
361
|
+
else:
|
|
362
|
+
if self._bare_mass is None or self._fermi_wavevector is None:
|
|
363
|
+
raise AttributeError("Cannot compute `real_sigma` "
|
|
364
|
+
"(SpectralQuadratic): set `bare_mass` and " \
|
|
365
|
+
"`fermi_wavevector` first.")
|
|
366
|
+
self._real_sigma = 2 * PREF * self.peak_positions_sigma \
|
|
367
|
+
* np.abs(self.peak_positions / self._bare_mass)
|
|
368
|
+
return self._real_sigma
|
|
369
|
+
|
|
370
|
+
@property
|
|
371
|
+
def mdc_maxima(self):
|
|
372
|
+
"""
|
|
373
|
+
MDC maxima (lazy).
|
|
374
|
+
|
|
375
|
+
SpectralLinear:
|
|
376
|
+
identical to peak_positions
|
|
377
|
+
|
|
378
|
+
SpectralQuadratic:
|
|
379
|
+
peak_positions + center_wavevector
|
|
380
|
+
"""
|
|
381
|
+
if getattr(self, "_mdc_maxima", None) is None:
|
|
382
|
+
if self.peak_positions is None:
|
|
383
|
+
return None
|
|
384
|
+
|
|
385
|
+
if self._class == "SpectralLinear":
|
|
386
|
+
self._mdc_maxima = self.peak_positions
|
|
387
|
+
elif self._class == "SpectralQuadratic":
|
|
388
|
+
self._mdc_maxima = (
|
|
389
|
+
self.peak_positions + self._center_wavevector
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
return self._mdc_maxima
|
|
393
|
+
|
|
394
|
+
def _se_legend_labels(self):
|
|
395
|
+
"""Return (real_label, imag_label) for legend with safe subscripts."""
|
|
396
|
+
se_label = getattr(self, "label", None)
|
|
397
|
+
|
|
398
|
+
if se_label is None:
|
|
399
|
+
real_label = r"$\Sigma'(E)$"
|
|
400
|
+
imag_label = r"$-\Sigma''(E)$"
|
|
401
|
+
return real_label, imag_label
|
|
402
|
+
|
|
403
|
+
safe_label = str(se_label).replace("_", r"\_")
|
|
404
|
+
|
|
405
|
+
# If the label is empty after conversion, fall back
|
|
406
|
+
if safe_label == "":
|
|
407
|
+
real_label = r"$\Sigma'(E)$"
|
|
408
|
+
imag_label = r"$-\Sigma''(E)$"
|
|
409
|
+
return real_label, imag_label
|
|
410
|
+
|
|
411
|
+
real_label = rf"$\Sigma_{{\mathrm{{{safe_label}}}}}'(E)$"
|
|
412
|
+
imag_label = rf"$-\Sigma_{{\mathrm{{{safe_label}}}}}''(E)$"
|
|
413
|
+
|
|
414
|
+
return real_label, imag_label
|
|
415
|
+
|
|
416
|
+
@add_fig_kwargs
|
|
417
|
+
def plot_real(self, ax=None, **kwargs):
|
|
418
|
+
r"""Plot the real part Σ' of the self-energy as a function of E-μ.
|
|
419
|
+
|
|
420
|
+
Parameters
|
|
421
|
+
----------
|
|
422
|
+
ax : Matplotlib-Axes or None
|
|
423
|
+
Axis to plot on. Created if not provided by the user.
|
|
424
|
+
**kwargs :
|
|
425
|
+
Additional keyword arguments passed to ``ax.errorbar``.
|
|
426
|
+
|
|
427
|
+
Returns
|
|
428
|
+
-------
|
|
429
|
+
fig : Matplotlib-Figure
|
|
430
|
+
Figure containing the Σ'(E) plot.
|
|
431
|
+
"""
|
|
432
|
+
from . import settings_parameters as xprs
|
|
433
|
+
|
|
434
|
+
ax, fig, plt = get_ax_fig_plt(ax=ax)
|
|
435
|
+
|
|
436
|
+
x = self.enel_range
|
|
437
|
+
y = self.real
|
|
438
|
+
y_sigma = self.real_sigma
|
|
439
|
+
|
|
440
|
+
real_label, _ = self._se_legend_labels()
|
|
441
|
+
kwargs.setdefault("label", real_label)
|
|
442
|
+
|
|
443
|
+
if y_sigma is not None:
|
|
444
|
+
if np.isnan(y_sigma).any():
|
|
445
|
+
print(
|
|
446
|
+
"Warning: some Σ'(E) uncertainty values are missing. "
|
|
447
|
+
"Error bars omitted at those energies."
|
|
448
|
+
)
|
|
449
|
+
kwargs.setdefault("yerr", xprs.sigma_confidence * y_sigma)
|
|
450
|
+
|
|
451
|
+
ax.errorbar(x, y, **kwargs)
|
|
452
|
+
ax.set_xlabel(r"$E-\mu$ (eV)")
|
|
453
|
+
ax.set_ylabel(r"$\Sigma'(E)$ (eV)")
|
|
454
|
+
ax.legend()
|
|
455
|
+
|
|
456
|
+
return fig
|
|
457
|
+
|
|
458
|
+
@add_fig_kwargs
|
|
459
|
+
def plot_imag(self, ax=None, **kwargs):
|
|
460
|
+
r"""Plot the imaginary part -Σ'' of the self-energy vs. E-μ.
|
|
461
|
+
|
|
462
|
+
Parameters
|
|
463
|
+
----------
|
|
464
|
+
ax : Matplotlib-Axes or None
|
|
465
|
+
Axis to plot on. Created if not provided by the user.
|
|
466
|
+
**kwargs :
|
|
467
|
+
Additional keyword arguments passed to ``ax.errorbar``.
|
|
468
|
+
|
|
469
|
+
Returns
|
|
470
|
+
-------
|
|
471
|
+
fig : Matplotlib-Figure
|
|
472
|
+
Figure containing the -Σ''(E) plot.
|
|
473
|
+
"""
|
|
474
|
+
from . import settings_parameters as xprs
|
|
475
|
+
|
|
476
|
+
ax, fig, plt = get_ax_fig_plt(ax=ax)
|
|
477
|
+
|
|
478
|
+
x = self.enel_range
|
|
479
|
+
y = self.imag
|
|
480
|
+
y_sigma = self.imag_sigma
|
|
481
|
+
|
|
482
|
+
_, imag_label = self._se_legend_labels()
|
|
483
|
+
kwargs.setdefault("label", imag_label)
|
|
484
|
+
|
|
485
|
+
if y_sigma is not None:
|
|
486
|
+
if np.isnan(y_sigma).any():
|
|
487
|
+
print(
|
|
488
|
+
"Warning: some -Σ''(E) uncertainty values are missing. "
|
|
489
|
+
"Error bars omitted at those energies."
|
|
490
|
+
)
|
|
491
|
+
kwargs.setdefault("yerr", xprs.sigma_confidence * y_sigma)
|
|
492
|
+
|
|
493
|
+
ax.errorbar(x, y, **kwargs)
|
|
494
|
+
ax.set_xlabel(r"$E-\mu$ (eV)")
|
|
495
|
+
ax.set_ylabel(r"$-\Sigma''(E)$ (eV)")
|
|
496
|
+
ax.legend()
|
|
497
|
+
|
|
498
|
+
return fig
|
|
499
|
+
|
|
500
|
+
@add_fig_kwargs
|
|
501
|
+
def plot_both(self, ax=None, **kwargs):
|
|
502
|
+
r"""Plot Σ'(E) and -Σ''(E) vs. E-μ on the same axis."""
|
|
503
|
+
from . import settings_parameters as xprs
|
|
504
|
+
|
|
505
|
+
ax, fig, plt = get_ax_fig_plt(ax=ax)
|
|
506
|
+
|
|
507
|
+
x = self.enel_range
|
|
508
|
+
real = self.real
|
|
509
|
+
imag = self.imag
|
|
510
|
+
real_sigma = self.real_sigma
|
|
511
|
+
imag_sigma = self.imag_sigma
|
|
512
|
+
|
|
513
|
+
real_label, imag_label = self._se_legend_labels()
|
|
514
|
+
|
|
515
|
+
# --- plot Σ'
|
|
516
|
+
kw_real = dict(kwargs)
|
|
517
|
+
if real_sigma is not None:
|
|
518
|
+
if np.isnan(real_sigma).any():
|
|
519
|
+
print(
|
|
520
|
+
"Warning: some Σ'(E) uncertainty values are missing. "
|
|
521
|
+
"Error bars omitted at those energies."
|
|
522
|
+
)
|
|
523
|
+
kw_real.setdefault("yerr", xprs.sigma_confidence * real_sigma)
|
|
524
|
+
kw_real.setdefault("label", real_label)
|
|
525
|
+
ax.errorbar(x, real, **kw_real)
|
|
526
|
+
|
|
527
|
+
# --- plot -Σ''
|
|
528
|
+
kw_imag = dict(kwargs)
|
|
529
|
+
if imag_sigma is not None:
|
|
530
|
+
if np.isnan(imag_sigma).any():
|
|
531
|
+
print(
|
|
532
|
+
"Warning: some -Σ''(E) uncertainty values are missing. "
|
|
533
|
+
"Error bars omitted at those energies."
|
|
534
|
+
)
|
|
535
|
+
kw_imag.setdefault("yerr", xprs.sigma_confidence * imag_sigma)
|
|
536
|
+
kw_imag.setdefault("label", imag_label)
|
|
537
|
+
ax.errorbar(x, imag, **kw_imag)
|
|
538
|
+
|
|
539
|
+
ax.set_xlabel(r"$E-\mu$ (eV)")
|
|
540
|
+
ax.set_ylabel(r"$\Sigma'(E),\ -\Sigma''(E)$ (eV)")
|
|
541
|
+
ax.legend()
|
|
542
|
+
|
|
543
|
+
return fig
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
class CreateSelfEnergies:
|
|
547
|
+
r"""
|
|
548
|
+
Thin container for self-energies with leaf-aware utilities.
|
|
549
|
+
All items are assumed to be leaf self-energy objects with
|
|
550
|
+
a `.label` attribute for identification.
|
|
551
|
+
"""
|
|
552
|
+
|
|
553
|
+
def __init__(self, self_energies):
|
|
554
|
+
self.self_energies = self_energies
|
|
555
|
+
|
|
556
|
+
# ------ Basic container protocol ------
|
|
557
|
+
def __call__(self):
|
|
558
|
+
return self.self_energies
|
|
559
|
+
|
|
560
|
+
@property
|
|
561
|
+
def self_energies(self):
|
|
562
|
+
return self._self_energies
|
|
563
|
+
|
|
564
|
+
@self_energies.setter
|
|
565
|
+
def self_energies(self, x):
|
|
566
|
+
self._self_energies = x
|
|
567
|
+
|
|
568
|
+
def __iter__(self):
|
|
569
|
+
return iter(self.self_energies)
|
|
570
|
+
|
|
571
|
+
def __getitem__(self, index):
|
|
572
|
+
return self.self_energies[index]
|
|
573
|
+
|
|
574
|
+
def __setitem__(self, index, value):
|
|
575
|
+
self.self_energies[index] = value
|
|
576
|
+
|
|
577
|
+
def __len__(self):
|
|
578
|
+
return len(self.self_energies)
|
|
579
|
+
|
|
580
|
+
def __deepcopy__(self, memo):
|
|
581
|
+
import copy
|
|
582
|
+
return type(self)(copy.deepcopy(self.self_energies, memo))
|
|
583
|
+
|
|
584
|
+
# ------ Label-based utilities ------
|
|
585
|
+
def get_by_label(self, label):
|
|
586
|
+
r"""
|
|
587
|
+
Return the self-energy object with the given label.
|
|
588
|
+
|
|
589
|
+
Parameters
|
|
590
|
+
----------
|
|
591
|
+
label : str
|
|
592
|
+
Label of the self-energy to retrieve.
|
|
593
|
+
|
|
594
|
+
Returns
|
|
595
|
+
-------
|
|
596
|
+
obj : SelfEnergy
|
|
597
|
+
The corresponding self-energy instance.
|
|
598
|
+
|
|
599
|
+
Raises
|
|
600
|
+
------
|
|
601
|
+
KeyError
|
|
602
|
+
If no self-energy with the given label exists.
|
|
603
|
+
"""
|
|
604
|
+
for se in self.self_energies:
|
|
605
|
+
if getattr(se, "label", None) == label:
|
|
606
|
+
return se
|
|
607
|
+
raise KeyError(
|
|
608
|
+
f"No self-energy with label {label!r} found in container."
|
|
609
|
+
)
|
|
610
|
+
|
|
611
|
+
def labels(self):
|
|
612
|
+
r"""
|
|
613
|
+
Return a list of all labels.
|
|
614
|
+
"""
|
|
615
|
+
return [getattr(se, "label", None) for se in self.self_energies]
|
|
616
|
+
|
|
617
|
+
def as_dict(self):
|
|
618
|
+
r"""
|
|
619
|
+
Return a {label: self_energy} dictionary for convenient access.
|
|
620
|
+
"""
|
|
621
|
+
return {se.label: se for se in self.self_energies}
|