xarpes 0.6.0__py3-none-any.whl → 0.6.2__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 +1 -1
- xarpes/bandmap.py +12 -9
- xarpes/functions.py +39 -42
- xarpes/selfenergies.py +532 -88
- {xarpes-0.6.0.dist-info → xarpes-0.6.2.dist-info}/METADATA +1 -1
- xarpes-0.6.2.dist-info/RECORD +15 -0
- xarpes-0.6.0.dist-info/RECORD +0 -15
- {xarpes-0.6.0.dist-info → xarpes-0.6.2.dist-info}/LICENSE +0 -0
- {xarpes-0.6.0.dist-info → xarpes-0.6.2.dist-info}/WHEEL +0 -0
- {xarpes-0.6.0.dist-info → xarpes-0.6.2.dist-info}/entry_points.txt +0 -0
xarpes/__init__.py
CHANGED
xarpes/bandmap.py
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
import numpy as np
|
|
15
15
|
from igor2 import binarywave
|
|
16
16
|
from .plotting import get_ax_fig_plt, add_fig_kwargs
|
|
17
|
-
from .functions import
|
|
17
|
+
from .functions import fit_least_squares, extend_function
|
|
18
18
|
from .distributions import FermiDirac, Linear
|
|
19
19
|
from .constants import PREF
|
|
20
20
|
|
|
@@ -746,9 +746,10 @@ class BandMap:
|
|
|
746
746
|
|
|
747
747
|
extra_args = (self.temperature,)
|
|
748
748
|
|
|
749
|
-
popt, pcov =
|
|
750
|
-
|
|
751
|
-
|
|
749
|
+
popt, pcov, success = fit_least_squares(
|
|
750
|
+
p0=parameters, xdata=energy_range, ydata=integrated_intensity,
|
|
751
|
+
function=fdir_initial, resolution=self.energy_resolution,
|
|
752
|
+
yerr=None, bounds=None, extra_args=extra_args)
|
|
752
753
|
|
|
753
754
|
# Update hnuminPhi; automatically sets self.enel
|
|
754
755
|
self.hnuminPhi = popt[0]
|
|
@@ -846,9 +847,10 @@ class BandMap:
|
|
|
846
847
|
for indx in range(angle_max_index - angle_min_index + 1):
|
|
847
848
|
edge = Intensities[:, indx]
|
|
848
849
|
|
|
849
|
-
parameters, pcov =
|
|
850
|
-
|
|
851
|
-
|
|
850
|
+
parameters, pcov, success = fit_least_squares(
|
|
851
|
+
p0=parameters, xdata=energy_range, ydata=edge,
|
|
852
|
+
function=fdir_initial, resolution=self.energy_resolution,
|
|
853
|
+
yerr=None, bounds=None, extra_args=extra_args)
|
|
852
854
|
|
|
853
855
|
nmps[indx] = parameters[0]
|
|
854
856
|
stds[indx] = np.sqrt(np.diag(pcov)[0])
|
|
@@ -861,8 +863,9 @@ class BandMap:
|
|
|
861
863
|
|
|
862
864
|
lin_fun = Linear(offset_guess, slope_guess, 'Linear')
|
|
863
865
|
|
|
864
|
-
popt, pcov =
|
|
865
|
-
|
|
866
|
+
popt, pcov, success = fit_least_squares(p0=parameters, xdata=angle_range,
|
|
867
|
+
ydata=nmps, function=lin_fun, resolution=None,
|
|
868
|
+
yerr=stds, bounds=None)
|
|
866
869
|
|
|
867
870
|
linsp = lin_fun(angle_range, popt[0], popt[1])
|
|
868
871
|
|
xarpes/functions.py
CHANGED
|
@@ -148,8 +148,8 @@ def extend_function(abscissa_range, abscissa_resolution):
|
|
|
148
148
|
return extend, step, numb
|
|
149
149
|
|
|
150
150
|
|
|
151
|
-
def error_function(p, xdata, ydata, function, resolution, yerr, extra_args):
|
|
152
|
-
r"""The error function used inside the
|
|
151
|
+
def error_function(p, xdata, ydata, function, resolution, yerr, *extra_args):
|
|
152
|
+
r"""The error function used inside the fit_least_squares function.
|
|
153
153
|
|
|
154
154
|
Parameters
|
|
155
155
|
----------
|
|
@@ -187,57 +187,54 @@ def error_function(p, xdata, ydata, function, resolution, yerr, extra_args):
|
|
|
187
187
|
return residual
|
|
188
188
|
|
|
189
189
|
|
|
190
|
-
def
|
|
191
|
-
|
|
192
|
-
r"""
|
|
190
|
+
def fit_least_squares(p0, xdata, ydata, function, resolution=None, yerr=None,
|
|
191
|
+
bounds=None, extra_args=None):
|
|
192
|
+
r"""Least-squares fit using `scipy.optimize.least_squares`.
|
|
193
193
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
p0 : ndarray
|
|
197
|
-
Initial guess for parameters to be optimized.
|
|
198
|
-
xdata : ndarray
|
|
199
|
-
Abscissa values the function is evaluated on.
|
|
200
|
-
ydata : ndarray
|
|
201
|
-
Measured values to compare to.
|
|
202
|
-
function : callable
|
|
203
|
-
Function or class with __call__ method to evaluate.
|
|
204
|
-
resolution : float or None, optional
|
|
205
|
-
Convolution resolution (sigma), if applicable.
|
|
206
|
-
yerr : ndarray or None, optional
|
|
207
|
-
Standard deviations of ydata. Defaults to ones if None.
|
|
208
|
-
extra_args : tuple
|
|
209
|
-
Additional arguments passed to the function.
|
|
194
|
+
Default behavior is Levenberg–Marquardt (`method="lm"`) when unbounded.
|
|
195
|
+
If `bounds` is provided, switches to trust-region reflective (`"trf"`).
|
|
210
196
|
|
|
211
|
-
Returns
|
|
212
|
-
|
|
213
|
-
pfit_leastsq : ndarray
|
|
214
|
-
Optimized parameters.
|
|
215
|
-
pcov : ndarray or float
|
|
216
|
-
Scaled covariance matrix of the optimized parameters.
|
|
217
|
-
If the covariance could not be estimated, returns np.inf.
|
|
197
|
+
Returns (pfit, pcov, success) in the same style as the old `leastsq`
|
|
198
|
+
wrapper, with an additional boolean `success` from SciPy.
|
|
218
199
|
"""
|
|
219
|
-
from scipy.optimize import
|
|
200
|
+
from scipy.optimize import least_squares
|
|
220
201
|
|
|
221
202
|
if yerr is None:
|
|
222
203
|
yerr = np.ones_like(ydata)
|
|
223
204
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
205
|
+
if extra_args is None:
|
|
206
|
+
extra_args = ()
|
|
207
|
+
|
|
208
|
+
def _residuals(p):
|
|
209
|
+
return error_function(
|
|
210
|
+
p, xdata, ydata, function, resolution, yerr, *extra_args
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
if bounds is None:
|
|
214
|
+
res = least_squares(_residuals, p0, method="lm")
|
|
215
|
+
else:
|
|
216
|
+
res = least_squares(_residuals, p0, method="trf", bounds=bounds)
|
|
217
|
+
|
|
218
|
+
pfit = res.x
|
|
219
|
+
success = bool(getattr(res, "success", False))
|
|
220
|
+
|
|
221
|
+
m = len(ydata)
|
|
222
|
+
n = pfit.size
|
|
230
223
|
|
|
231
|
-
if (
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
224
|
+
if (m > n) and (res.jac is not None) and res.jac.size:
|
|
225
|
+
resid = res.fun
|
|
226
|
+
s_sq = (resid ** 2).sum() / (m - n)
|
|
227
|
+
|
|
228
|
+
try:
|
|
229
|
+
jtj = res.jac.T @ res.jac
|
|
230
|
+
pcov = np.linalg.inv(jtj) * s_sq
|
|
231
|
+
except np.linalg.LinAlgError:
|
|
232
|
+
pcov = np.inf
|
|
237
233
|
else:
|
|
238
234
|
pcov = np.inf
|
|
239
235
|
|
|
240
|
-
return pfit, pcov
|
|
236
|
+
return pfit, pcov, success
|
|
237
|
+
|
|
241
238
|
|
|
242
239
|
|
|
243
240
|
def download_examples():
|
xarpes/selfenergies.py
CHANGED
|
@@ -34,7 +34,7 @@ class SelfEnergy:
|
|
|
34
34
|
else:
|
|
35
35
|
raise ValueError(
|
|
36
36
|
"`properties` must be a dict or a single dict in a list."
|
|
37
|
-
|
|
37
|
+
)
|
|
38
38
|
|
|
39
39
|
# single source of truth for all params (+ their *_sigma)
|
|
40
40
|
self._properties = dict(properties or {})
|
|
@@ -84,12 +84,19 @@ class SelfEnergy:
|
|
|
84
84
|
self._imag = None
|
|
85
85
|
self._imag_sigma = None
|
|
86
86
|
|
|
87
|
+
# lazy caches for α²F(ω) extraction results
|
|
88
|
+
self._a2f_spectrum = None
|
|
89
|
+
self._a2f_model = None
|
|
90
|
+
self._a2f_omega_range = None
|
|
91
|
+
self._a2f_alpha_select = None
|
|
92
|
+
self._a2f_cost = None
|
|
93
|
+
|
|
87
94
|
def _check_mass_velocity_exclusivity(self):
|
|
88
95
|
"""Ensure that fermi_velocity and bare_mass are not both set."""
|
|
89
96
|
if (self._fermi_velocity is not None) and (self._bare_mass is not None):
|
|
90
97
|
raise ValueError(
|
|
91
|
-
|
|
92
|
-
|
|
98
|
+
"Cannot set both `fermi_velocity` and `bare_mass`: choose one "
|
|
99
|
+
"physical parametrization (SpectralLinear or SpectralQuadratic)."
|
|
93
100
|
)
|
|
94
101
|
|
|
95
102
|
# ---------------- core read-only axes ----------------
|
|
@@ -289,8 +296,10 @@ class SelfEnergy:
|
|
|
289
296
|
self._peak_positions = ((-1.0 if self._side == "left"
|
|
290
297
|
else 1.0) * kpar_mag)
|
|
291
298
|
else:
|
|
292
|
-
self._peak_positions = (
|
|
293
|
-
|
|
299
|
+
self._peak_positions = (
|
|
300
|
+
np.sqrt(self._ekin_range / PREF)
|
|
301
|
+
* np.sin(np.deg2rad(self._peak))
|
|
302
|
+
)
|
|
294
303
|
return self._peak_positions
|
|
295
304
|
|
|
296
305
|
|
|
@@ -346,6 +355,31 @@ class SelfEnergy:
|
|
|
346
355
|
return None
|
|
347
356
|
self._real_sigma = self._compute_real_sigma()
|
|
348
357
|
return self._real_sigma
|
|
358
|
+
|
|
359
|
+
@property
|
|
360
|
+
def a2f_spectrum(self):
|
|
361
|
+
"""Cached α²F(ω) spectrum from last extraction (or None)."""
|
|
362
|
+
return self._a2f_spectrum
|
|
363
|
+
|
|
364
|
+
@property
|
|
365
|
+
def a2f_model(self):
|
|
366
|
+
"""Cached MEM model spectrum from last extraction (or None)."""
|
|
367
|
+
return self._a2f_model
|
|
368
|
+
|
|
369
|
+
@property
|
|
370
|
+
def a2f_omega_range(self):
|
|
371
|
+
"""Cached ω grid for the last extraction (or None)."""
|
|
372
|
+
return self._a2f_omega_range
|
|
373
|
+
|
|
374
|
+
@property
|
|
375
|
+
def a2f_alpha_select(self):
|
|
376
|
+
"""Cached selected alpha from last extraction (or None)."""
|
|
377
|
+
return self._a2f_alpha_select
|
|
378
|
+
|
|
379
|
+
@property
|
|
380
|
+
def a2f_cost(self):
|
|
381
|
+
"""Cached cost from last bayesian_loop (or None)."""
|
|
382
|
+
return self._a2f_cost
|
|
349
383
|
|
|
350
384
|
|
|
351
385
|
def _compute_imag(self, fermi_velocity=None, bare_mass=None):
|
|
@@ -553,15 +587,37 @@ class SelfEnergy:
|
|
|
553
587
|
imag_label = rf"$-\Sigma_{{\mathrm{{{safe_label}}}}}''(E)$"
|
|
554
588
|
|
|
555
589
|
return real_label, imag_label
|
|
590
|
+
|
|
591
|
+
def _a2f_legend_labels(self):
|
|
592
|
+
"""Return (a2f_label, model_label) for legend with safe subscripts."""
|
|
593
|
+
se_label = getattr(self, "label", None)
|
|
594
|
+
|
|
595
|
+
if se_label is None:
|
|
596
|
+
return r"$\alpha^2F(\omega)$", r"$m(\omega)$"
|
|
597
|
+
|
|
598
|
+
safe_label = str(se_label).replace("_", r"\_")
|
|
599
|
+
if safe_label == "":
|
|
600
|
+
return r"$\alpha^2F(\omega)$", r"$m(\omega)$"
|
|
601
|
+
|
|
602
|
+
a2f_label = rf"$\alpha^2F_{{\mathrm{{{safe_label}}}}}(\omega)$"
|
|
603
|
+
model_label = rf"$m_{{\mathrm{{{safe_label}}}}}(\omega)$"
|
|
604
|
+
return a2f_label, model_label
|
|
556
605
|
|
|
557
606
|
@add_fig_kwargs
|
|
558
|
-
def plot_real(self, ax=None, **kwargs):
|
|
607
|
+
def plot_real(self, ax=None, scale="eV", resolution_range="absent", **kwargs):
|
|
559
608
|
r"""Plot the real part Σ' of the self-energy as a function of E-μ.
|
|
560
609
|
|
|
561
610
|
Parameters
|
|
562
611
|
----------
|
|
563
612
|
ax : Matplotlib-Axes or None
|
|
564
613
|
Axis to plot on. Created if not provided by the user.
|
|
614
|
+
scale : {"eV", "meV"}
|
|
615
|
+
Units for both axes. If "meV", x and y (and yerr) are multiplied by
|
|
616
|
+
`KILO`.
|
|
617
|
+
resolution_range : {"absent", "applied"}
|
|
618
|
+
If "applied", removes points with |E-μ| <= energy_resolution (around
|
|
619
|
+
the chemical potential). The energy resolution is taken from
|
|
620
|
+
``self.energy_resolution`` (in eV) and scaled consistently with `scale`.
|
|
565
621
|
**kwargs :
|
|
566
622
|
Additional keyword arguments passed to ``ax.errorbar``.
|
|
567
623
|
|
|
@@ -574,10 +630,31 @@ class SelfEnergy:
|
|
|
574
630
|
|
|
575
631
|
ax, fig, plt = get_ax_fig_plt(ax=ax)
|
|
576
632
|
|
|
633
|
+
if scale not in ("eV", "meV"):
|
|
634
|
+
raise ValueError("scale must be either 'eV' or 'meV'.")
|
|
635
|
+
if resolution_range not in ("absent", "applied"):
|
|
636
|
+
raise ValueError("resolution_range must be 'absent' or 'applied'.")
|
|
637
|
+
|
|
638
|
+
factor = KILO if scale == "meV" else 1.0
|
|
639
|
+
|
|
577
640
|
x = self.enel_range
|
|
578
641
|
y = self.real
|
|
579
642
|
y_sigma = self.real_sigma
|
|
580
643
|
|
|
644
|
+
if x is not None:
|
|
645
|
+
x = factor * np.asarray(x, dtype=float)
|
|
646
|
+
if y is not None:
|
|
647
|
+
y = factor * np.asarray(y, dtype=float)
|
|
648
|
+
|
|
649
|
+
if resolution_range == "applied" and x is not None and y is not None:
|
|
650
|
+
res = self.energy_resolution
|
|
651
|
+
if res is not None:
|
|
652
|
+
keep = np.abs(x) > (factor * float(res))
|
|
653
|
+
x = x[keep]
|
|
654
|
+
y = y[keep]
|
|
655
|
+
if y_sigma is not None:
|
|
656
|
+
y_sigma = np.asarray(y_sigma, dtype=float)[keep]
|
|
657
|
+
|
|
581
658
|
real_label, _ = self._se_legend_labels()
|
|
582
659
|
kwargs.setdefault("label", real_label)
|
|
583
660
|
|
|
@@ -587,23 +664,33 @@ class SelfEnergy:
|
|
|
587
664
|
"Warning: some Σ'(E) uncertainty values are missing. "
|
|
588
665
|
"Error bars omitted at those energies."
|
|
589
666
|
)
|
|
590
|
-
kwargs.setdefault("yerr", xprs.sigma_confidence * y_sigma)
|
|
667
|
+
kwargs.setdefault("yerr", xprs.sigma_confidence * factor * y_sigma)
|
|
591
668
|
|
|
592
669
|
ax.errorbar(x, y, **kwargs)
|
|
593
|
-
|
|
594
|
-
|
|
670
|
+
|
|
671
|
+
x_unit = "meV" if scale == "meV" else "eV"
|
|
672
|
+
ax.set_xlabel(rf"$E-\mu$ ({x_unit})")
|
|
673
|
+
ax.set_ylabel(rf"$\Sigma'(E)$ ({x_unit})")
|
|
595
674
|
ax.legend()
|
|
596
675
|
|
|
597
676
|
return fig
|
|
598
677
|
|
|
678
|
+
|
|
599
679
|
@add_fig_kwargs
|
|
600
|
-
def plot_imag(self, ax=None, **kwargs):
|
|
680
|
+
def plot_imag(self, ax=None, scale="eV", resolution_range="absent", **kwargs):
|
|
601
681
|
r"""Plot the imaginary part -Σ'' of the self-energy vs. E-μ.
|
|
602
682
|
|
|
603
683
|
Parameters
|
|
604
684
|
----------
|
|
605
685
|
ax : Matplotlib-Axes or None
|
|
606
686
|
Axis to plot on. Created if not provided by the user.
|
|
687
|
+
scale : {"eV", "meV"}
|
|
688
|
+
Units for both axes. If "meV", x and y (and yerr) are multiplied by
|
|
689
|
+
`KILO`.
|
|
690
|
+
resolution_range : {"absent", "applied"}
|
|
691
|
+
If "applied", removes points with |E-μ| <= energy_resolution (around
|
|
692
|
+
the chemical potential). The energy resolution is taken from
|
|
693
|
+
``self.energy_resolution`` (in eV) and scaled consistently with `scale`.
|
|
607
694
|
**kwargs :
|
|
608
695
|
Additional keyword arguments passed to ``ax.errorbar``.
|
|
609
696
|
|
|
@@ -616,10 +703,31 @@ class SelfEnergy:
|
|
|
616
703
|
|
|
617
704
|
ax, fig, plt = get_ax_fig_plt(ax=ax)
|
|
618
705
|
|
|
706
|
+
if scale not in ("eV", "meV"):
|
|
707
|
+
raise ValueError("scale must be either 'eV' or 'meV'.")
|
|
708
|
+
if resolution_range not in ("absent", "applied"):
|
|
709
|
+
raise ValueError("resolution_range must be 'absent' or 'applied'.")
|
|
710
|
+
|
|
711
|
+
factor = KILO if scale == "meV" else 1.0
|
|
712
|
+
|
|
619
713
|
x = self.enel_range
|
|
620
714
|
y = self.imag
|
|
621
715
|
y_sigma = self.imag_sigma
|
|
622
716
|
|
|
717
|
+
if x is not None:
|
|
718
|
+
x = factor * np.asarray(x, dtype=float)
|
|
719
|
+
if y is not None:
|
|
720
|
+
y = factor * np.asarray(y, dtype=float)
|
|
721
|
+
|
|
722
|
+
if resolution_range == "applied" and x is not None and y is not None:
|
|
723
|
+
res = self.energy_resolution
|
|
724
|
+
if res is not None:
|
|
725
|
+
keep = np.abs(x) > (factor * float(res))
|
|
726
|
+
x = x[keep]
|
|
727
|
+
y = y[keep]
|
|
728
|
+
if y_sigma is not None:
|
|
729
|
+
y_sigma = np.asarray(y_sigma, dtype=float)[keep]
|
|
730
|
+
|
|
623
731
|
_, imag_label = self._se_legend_labels()
|
|
624
732
|
kwargs.setdefault("label", imag_label)
|
|
625
733
|
|
|
@@ -629,31 +737,76 @@ class SelfEnergy:
|
|
|
629
737
|
"Warning: some -Σ''(E) uncertainty values are missing. "
|
|
630
738
|
"Error bars omitted at those energies."
|
|
631
739
|
)
|
|
632
|
-
kwargs.setdefault("yerr", xprs.sigma_confidence * y_sigma)
|
|
740
|
+
kwargs.setdefault("yerr", xprs.sigma_confidence * factor * y_sigma)
|
|
633
741
|
|
|
634
742
|
ax.errorbar(x, y, **kwargs)
|
|
635
|
-
|
|
636
|
-
|
|
743
|
+
|
|
744
|
+
x_unit = "meV" if scale == "meV" else "eV"
|
|
745
|
+
ax.set_xlabel(rf"$E-\mu$ ({x_unit})")
|
|
746
|
+
ax.set_ylabel(rf"$-\Sigma''(E)$ ({x_unit})")
|
|
637
747
|
ax.legend()
|
|
638
748
|
|
|
639
749
|
return fig
|
|
640
750
|
|
|
751
|
+
|
|
641
752
|
@add_fig_kwargs
|
|
642
|
-
def plot_both(self, ax=None, **kwargs):
|
|
643
|
-
r"""Plot Σ'(E) and -Σ''(E) vs. E-μ on the same axis.
|
|
753
|
+
def plot_both(self, ax=None, scale="eV", resolution_range="absent", **kwargs):
|
|
754
|
+
r"""Plot Σ'(E) and -Σ''(E) vs. E-μ on the same axis.
|
|
755
|
+
|
|
756
|
+
Parameters
|
|
757
|
+
----------
|
|
758
|
+
ax : Matplotlib-Axes or None
|
|
759
|
+
Axis to plot on. Created if not provided by the user.
|
|
760
|
+
scale : {"eV", "meV"}
|
|
761
|
+
Units for both axes. If "meV", x, y, and yerr are multiplied by
|
|
762
|
+
`KILO`.
|
|
763
|
+
resolution_range : {"absent", "applied"}
|
|
764
|
+
If "applied", removes points with |E-μ| <= energy_resolution (around
|
|
765
|
+
the chemical potential). The energy resolution is taken from
|
|
766
|
+
``self.energy_resolution`` (in eV) and scaled consistently with `scale`.
|
|
767
|
+
**kwargs :
|
|
768
|
+
Additional keyword arguments passed to ``ax.errorbar``.
|
|
769
|
+
"""
|
|
644
770
|
from . import settings_parameters as xprs
|
|
645
771
|
|
|
646
772
|
ax, fig, plt = get_ax_fig_plt(ax=ax)
|
|
647
773
|
|
|
774
|
+
if scale not in ("eV", "meV"):
|
|
775
|
+
raise ValueError("scale must be either 'eV' or 'meV'.")
|
|
776
|
+
if resolution_range not in ("absent", "applied"):
|
|
777
|
+
raise ValueError("resolution_range must be 'absent' or 'applied'.")
|
|
778
|
+
|
|
779
|
+
factor = KILO if scale == "meV" else 1.0
|
|
780
|
+
|
|
648
781
|
x = self.enel_range
|
|
649
782
|
real = self.real
|
|
650
783
|
imag = self.imag
|
|
651
784
|
real_sigma = self.real_sigma
|
|
652
785
|
imag_sigma = self.imag_sigma
|
|
653
786
|
|
|
787
|
+
if x is not None:
|
|
788
|
+
x = factor * np.asarray(x, dtype=float)
|
|
789
|
+
if real is not None:
|
|
790
|
+
real = factor * np.asarray(real, dtype=float)
|
|
791
|
+
if imag is not None:
|
|
792
|
+
imag = factor * np.asarray(imag, dtype=float)
|
|
793
|
+
|
|
794
|
+
if resolution_range == "applied" and x is not None:
|
|
795
|
+
res = self.energy_resolution
|
|
796
|
+
if res is not None:
|
|
797
|
+
keep = np.abs(x) > (factor * float(res))
|
|
798
|
+
x = x[keep]
|
|
799
|
+
if real is not None:
|
|
800
|
+
real = real[keep]
|
|
801
|
+
if imag is not None:
|
|
802
|
+
imag = imag[keep]
|
|
803
|
+
if real_sigma is not None:
|
|
804
|
+
real_sigma = np.asarray(real_sigma, dtype=float)[keep]
|
|
805
|
+
if imag_sigma is not None:
|
|
806
|
+
imag_sigma = np.asarray(imag_sigma, dtype=float)[keep]
|
|
807
|
+
|
|
654
808
|
real_label, imag_label = self._se_legend_labels()
|
|
655
809
|
|
|
656
|
-
# --- plot Σ'
|
|
657
810
|
kw_real = dict(kwargs)
|
|
658
811
|
if real_sigma is not None:
|
|
659
812
|
if np.isnan(real_sigma).any():
|
|
@@ -661,11 +814,10 @@ class SelfEnergy:
|
|
|
661
814
|
"Warning: some Σ'(E) uncertainty values are missing. "
|
|
662
815
|
"Error bars omitted at those energies."
|
|
663
816
|
)
|
|
664
|
-
kw_real.setdefault("yerr", xprs.sigma_confidence * real_sigma)
|
|
817
|
+
kw_real.setdefault("yerr", xprs.sigma_confidence * factor * real_sigma)
|
|
665
818
|
kw_real.setdefault("label", real_label)
|
|
666
819
|
ax.errorbar(x, real, **kw_real)
|
|
667
820
|
|
|
668
|
-
# --- plot -Σ''
|
|
669
821
|
kw_imag = dict(kwargs)
|
|
670
822
|
if imag_sigma is not None:
|
|
671
823
|
if np.isnan(imag_sigma).any():
|
|
@@ -673,27 +825,187 @@ class SelfEnergy:
|
|
|
673
825
|
"Warning: some -Σ''(E) uncertainty values are missing. "
|
|
674
826
|
"Error bars omitted at those energies."
|
|
675
827
|
)
|
|
676
|
-
kw_imag.setdefault("yerr", xprs.sigma_confidence * imag_sigma)
|
|
828
|
+
kw_imag.setdefault("yerr", xprs.sigma_confidence * factor * imag_sigma)
|
|
677
829
|
kw_imag.setdefault("label", imag_label)
|
|
678
830
|
ax.errorbar(x, imag, **kw_imag)
|
|
679
831
|
|
|
680
|
-
|
|
681
|
-
ax.
|
|
832
|
+
x_unit = "meV" if scale == "meV" else "eV"
|
|
833
|
+
ax.set_xlabel(rf"$E-\mu$ ({x_unit})")
|
|
834
|
+
ax.set_ylabel(rf"$\Sigma'(E),\ -\Sigma''(E)$ ({x_unit})")
|
|
835
|
+
ax.legend()
|
|
836
|
+
|
|
837
|
+
return fig
|
|
838
|
+
|
|
839
|
+
@add_fig_kwargs
|
|
840
|
+
def plot_a2f(self, ax=None, abscissa="forward", **kwargs):
|
|
841
|
+
ax, fig, plt = get_ax_fig_plt(ax=ax)
|
|
842
|
+
|
|
843
|
+
xlim_in = ax.get_xlim()
|
|
844
|
+
ylim_in = ax.get_ylim()
|
|
845
|
+
|
|
846
|
+
if abscissa not in ("forward", "reversed"):
|
|
847
|
+
raise ValueError("abscissa must be either 'forward' or 'reversed'.")
|
|
848
|
+
|
|
849
|
+
omega = self.a2f_omega_range
|
|
850
|
+
spectrum = self.a2f_spectrum
|
|
851
|
+
|
|
852
|
+
if omega is None or spectrum is None:
|
|
853
|
+
raise AttributeError(
|
|
854
|
+
"No cached α²F(ω) spectrum found. Run `extract_a2f()` or "
|
|
855
|
+
"`bayesian_loop()` first."
|
|
856
|
+
)
|
|
857
|
+
|
|
858
|
+
if abscissa == "reversed":
|
|
859
|
+
omega = -omega[::-1]
|
|
860
|
+
spectrum = spectrum[::-1]
|
|
861
|
+
|
|
862
|
+
a2f_label, _ = self._a2f_legend_labels()
|
|
863
|
+
kwargs.setdefault("label", a2f_label)
|
|
864
|
+
ax.plot(omega, spectrum, **kwargs)
|
|
865
|
+
|
|
866
|
+
ax.set_xlabel(r"$\omega$ (meV)")
|
|
867
|
+
ax.set_ylabel(r"$\alpha^2F(\omega)$ (-)")
|
|
868
|
+
|
|
869
|
+
self._apply_spectra_axis_defaults(ax, omega, abscissa, xlim_in, ylim_in)
|
|
870
|
+
|
|
871
|
+
ax.legend()
|
|
872
|
+
return fig
|
|
873
|
+
|
|
874
|
+
@add_fig_kwargs
|
|
875
|
+
def plot_model(self, ax=None, abscissa="forward", **kwargs):
|
|
876
|
+
ax, fig, plt = get_ax_fig_plt(ax=ax)
|
|
877
|
+
|
|
878
|
+
xlim_in = ax.get_xlim()
|
|
879
|
+
ylim_in = ax.get_ylim()
|
|
880
|
+
|
|
881
|
+
if abscissa not in ("forward", "reversed"):
|
|
882
|
+
raise ValueError("abscissa must be either 'forward' or 'reversed'.")
|
|
883
|
+
|
|
884
|
+
omega = self.a2f_omega_range
|
|
885
|
+
model = self.a2f_model
|
|
886
|
+
|
|
887
|
+
if omega is None or model is None:
|
|
888
|
+
raise AttributeError(
|
|
889
|
+
"No cached model spectrum found. Run `extract_a2f()` or "
|
|
890
|
+
"`bayesian_loop()` first."
|
|
891
|
+
)
|
|
892
|
+
|
|
893
|
+
if abscissa == "reversed":
|
|
894
|
+
omega = -omega[::-1]
|
|
895
|
+
model = model[::-1]
|
|
896
|
+
|
|
897
|
+
_, model_label = self._a2f_legend_labels()
|
|
898
|
+
kwargs.setdefault("label", model_label)
|
|
899
|
+
ax.plot(omega, model, **kwargs)
|
|
900
|
+
|
|
901
|
+
ax.set_xlabel(r"$\omega$ (meV)")
|
|
902
|
+
ax.set_ylabel(r"$m(\omega)$ (-)")
|
|
903
|
+
|
|
904
|
+
self._apply_spectra_axis_defaults(ax, omega, abscissa, xlim_in, ylim_in)
|
|
905
|
+
|
|
906
|
+
ax.legend()
|
|
907
|
+
return fig
|
|
908
|
+
|
|
909
|
+
@add_fig_kwargs
|
|
910
|
+
def plot_spectra(self, ax=None, abscissa="forward", **kwargs):
|
|
911
|
+
ax, fig, plt = get_ax_fig_plt(ax=ax)
|
|
912
|
+
|
|
913
|
+
xlim_in = ax.get_xlim()
|
|
914
|
+
ylim_in = ax.get_ylim()
|
|
915
|
+
|
|
916
|
+
if abscissa not in ("forward", "reversed"):
|
|
917
|
+
raise ValueError("abscissa must be either 'forward' or 'reversed'.")
|
|
918
|
+
|
|
919
|
+
omega = self.a2f_omega_range
|
|
920
|
+
spectrum = self.a2f_spectrum
|
|
921
|
+
model = self.a2f_model
|
|
922
|
+
|
|
923
|
+
if omega is None or spectrum is None or model is None:
|
|
924
|
+
raise AttributeError(
|
|
925
|
+
"No cached spectra found. Run `extract_a2f()` or `bayesian_loop()` "
|
|
926
|
+
"first."
|
|
927
|
+
)
|
|
928
|
+
|
|
929
|
+
if abscissa == "reversed":
|
|
930
|
+
omega = -omega[::-1]
|
|
931
|
+
spectrum = spectrum[::-1]
|
|
932
|
+
model = model[::-1]
|
|
933
|
+
|
|
934
|
+
kw_a2f = dict(kwargs)
|
|
935
|
+
kw_model = dict(kwargs)
|
|
936
|
+
a2f_label, model_label = self._a2f_legend_labels()
|
|
937
|
+
kw_a2f.setdefault("label", a2f_label)
|
|
938
|
+
kw_model.setdefault("label", model_label)
|
|
939
|
+
|
|
940
|
+
ax.plot(omega, spectrum, **kw_a2f)
|
|
941
|
+
ax.plot(omega, model, **kw_model)
|
|
942
|
+
|
|
943
|
+
self._apply_spectra_axis_defaults(ax, omega, abscissa, xlim_in, ylim_in)
|
|
944
|
+
|
|
945
|
+
ax.set_xlabel(r"$\omega$ (meV)")
|
|
946
|
+
ax.set_ylabel(r"$\alpha^2F_n(\omega),~m_n(\omega)~(-)$")
|
|
682
947
|
ax.legend()
|
|
683
948
|
|
|
684
949
|
return fig
|
|
685
950
|
|
|
951
|
+
@staticmethod
|
|
952
|
+
def _apply_spectra_axis_defaults(ax, omega, abscissa, xlim_in, ylim_in):
|
|
953
|
+
"""Apply default spectra x-range and y-min, without stomping overrides.
|
|
954
|
+
|
|
955
|
+
Defaults are applied only if the incoming axis limits were Matplotlib's
|
|
956
|
+
defaults (0, 1), i.e. the caller did not pre-set them.
|
|
957
|
+
"""
|
|
958
|
+
if abscissa not in ("forward", "reversed"):
|
|
959
|
+
raise ValueError("abscissa must be either 'forward' or 'reversed'.")
|
|
960
|
+
|
|
961
|
+
omega_max = float(np.max(np.abs(omega)))
|
|
962
|
+
|
|
963
|
+
# --- X defaults (only if user did not pre-set xlim)
|
|
964
|
+
x0, x1 = xlim_in
|
|
965
|
+
x_is_default = np.isclose(x0, 0.0) and np.isclose(x1, 1.0)
|
|
966
|
+
if x_is_default:
|
|
967
|
+
if abscissa == "forward":
|
|
968
|
+
ax.set_xlim(0.0, omega_max)
|
|
969
|
+
else:
|
|
970
|
+
ax.set_xlim(-omega_max, 0.0)
|
|
971
|
+
|
|
972
|
+
# --- Y default: set only the bottom to 0 (only if user did not pre-set)
|
|
973
|
+
y0, y1 = ylim_in
|
|
974
|
+
y_is_default = np.isclose(y0, 0.0) and np.isclose(y1, 1.0)
|
|
975
|
+
if y_is_default:
|
|
976
|
+
ax.set_ylim(bottom=0.0)
|
|
977
|
+
|
|
686
978
|
|
|
979
|
+
@add_fig_kwargs
|
|
687
980
|
def extract_a2f(self, *, omega_min, omega_max, omega_num, omega_I, omega_M,
|
|
688
|
-
mem=None, **mem_kwargs):
|
|
981
|
+
mem=None, ax=None, **mem_kwargs):
|
|
689
982
|
r"""
|
|
690
983
|
Extract Eliashberg function α²F(ω) from the self-energy. While working
|
|
691
984
|
with band maps and MDCs is more intuitive in eV, the self-energy
|
|
692
985
|
extraction is performed in eV.
|
|
693
986
|
|
|
987
|
+
Parameters
|
|
988
|
+
----------
|
|
989
|
+
ax : Matplotlib-Axes or None
|
|
990
|
+
Axis to plot on. Created if not provided by the user. (Not used yet;
|
|
991
|
+
reserved for future plotting.)
|
|
992
|
+
|
|
993
|
+
Returns
|
|
994
|
+
-------
|
|
995
|
+
spectrum : ndarray
|
|
996
|
+
Extracted α²F(ω).
|
|
997
|
+
model : ndarray
|
|
998
|
+
MEM model spectrum.
|
|
999
|
+
omega_range : ndarray
|
|
1000
|
+
ω grid used for the extraction.
|
|
1001
|
+
alpha_select : float
|
|
1002
|
+
Selected alpha returned by the chi2kink procedure.
|
|
694
1003
|
"""
|
|
695
1004
|
from . import settings_parameters as xprs
|
|
696
1005
|
|
|
1006
|
+
# Reserve the plot API now; not used yet, but this matches xARPES style.
|
|
1007
|
+
ax, fig, plt = get_ax_fig_plt(ax=ax)
|
|
1008
|
+
|
|
697
1009
|
mem_cfg = self._merge_defaults(xprs.mem_defaults, mem, mem_kwargs)
|
|
698
1010
|
|
|
699
1011
|
method = mem_cfg["method"]
|
|
@@ -724,22 +1036,24 @@ class SelfEnergy:
|
|
|
724
1036
|
f_chi_squared = 2.5 if parts == "both" else 2.0
|
|
725
1037
|
else:
|
|
726
1038
|
f_chi_squared = float(f_chi_squared)
|
|
1039
|
+
if d_guess <= 0.0:
|
|
1040
|
+
raise ValueError(
|
|
1041
|
+
"chi2kink requires d_guess > 0 to fix the logistic sign "
|
|
1042
|
+
"ambiguity."
|
|
1043
|
+
)
|
|
727
1044
|
|
|
728
1045
|
h_n = mem_cfg.get("h_n", None)
|
|
729
1046
|
if h_n is None:
|
|
730
1047
|
raise ValueError(
|
|
731
|
-
"`
|
|
732
|
-
"No default is assumed."
|
|
1048
|
+
"`optimisation_parameters` must include 'h_n' for cost evaluation."
|
|
733
1049
|
)
|
|
734
1050
|
|
|
735
1051
|
from . import (create_model_function, create_kernel_function,
|
|
736
1052
|
singular_value_decomposition, MEM_core)
|
|
737
1053
|
|
|
738
1054
|
omega_range = np.linspace(omega_min, omega_max, omega_num)
|
|
1055
|
+
model = create_model_function(omega_range, omega_I, omega_M, omega_S, h_n)
|
|
739
1056
|
|
|
740
|
-
model = create_model_function(omega_range, omega_I, omega_M, omega_S,
|
|
741
|
-
h_n)
|
|
742
|
-
|
|
743
1057
|
delta_omega = (omega_max - omega_min) / (omega_num - 1)
|
|
744
1058
|
model_in = model * delta_omega
|
|
745
1059
|
|
|
@@ -763,7 +1077,6 @@ class SelfEnergy:
|
|
|
763
1077
|
|
|
764
1078
|
energies_eV_masked = energies_eV[mE]
|
|
765
1079
|
energies = energies_eV_masked * KILO
|
|
766
|
-
|
|
767
1080
|
k_BT = K_B * self.temperature * KILO
|
|
768
1081
|
|
|
769
1082
|
kernel = create_kernel_function(energies, omega_range, k_BT)
|
|
@@ -771,16 +1084,13 @@ class SelfEnergy:
|
|
|
771
1084
|
if lambda_el:
|
|
772
1085
|
if W is None:
|
|
773
1086
|
if self._class == "SpectralQuadratic":
|
|
774
|
-
W = (
|
|
775
|
-
PREF * self._fermi_wavevector**2 / self._bare_mass
|
|
776
|
-
) * KILO
|
|
1087
|
+
W = (PREF * self._fermi_wavevector**2 / self._bare_mass) * KILO
|
|
777
1088
|
else:
|
|
778
1089
|
raise ValueError(
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
1090
|
+
"lambda_el was provided, but W is None. For a linearised "
|
|
1091
|
+
"band (SpectralLinear), you must also provide W in meV: "
|
|
1092
|
+
"the electron–electron interaction scale."
|
|
782
1093
|
)
|
|
783
|
-
|
|
784
1094
|
|
|
785
1095
|
energies_el = energies_eV_masked * KILO
|
|
786
1096
|
real_el, imag_el = self._el_el_self_energy(
|
|
@@ -798,14 +1108,12 @@ class SelfEnergy:
|
|
|
798
1108
|
dvec = np.concatenate((real, imag))
|
|
799
1109
|
wvec = np.concatenate((real_sigma**(-2), imag_sigma**(-2)))
|
|
800
1110
|
H = np.concatenate((np.real(kernel), -np.imag(kernel)))
|
|
801
|
-
|
|
802
1111
|
elif parts == "real":
|
|
803
1112
|
real = self.real[mE] * KILO - real_el
|
|
804
1113
|
real_sigma = self.real_sigma[mE] * KILO
|
|
805
1114
|
dvec = real
|
|
806
1115
|
wvec = real_sigma**(-2)
|
|
807
1116
|
H = np.real(kernel)
|
|
808
|
-
|
|
809
1117
|
else: # parts == "imag"
|
|
810
1118
|
imag = self.imag[mE] * KILO - impurity_magnitude - imag_el
|
|
811
1119
|
imag_sigma = self.imag_sigma[mE] * KILO
|
|
@@ -816,14 +1124,55 @@ class SelfEnergy:
|
|
|
816
1124
|
V_Sigma, U, uvec = singular_value_decomposition(H, sigma_svd)
|
|
817
1125
|
|
|
818
1126
|
if method == "chi2kink":
|
|
819
|
-
spectrum_in,
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
1127
|
+
(spectrum_in, alpha_select, fit_curve, guess_curve,
|
|
1128
|
+
chi2kink_result) = self._chi2kink_a2f(
|
|
1129
|
+
dvec, model_in, uvec, mu, wvec, V_Sigma, U, alpha_min,
|
|
1130
|
+
alpha_max, alpha_num, a_guess, b_guess, c_guess, d_guess,
|
|
1131
|
+
f_chi_squared, t_criterion, iter_max, MEM_core
|
|
1132
|
+
)
|
|
1133
|
+
else:
|
|
1134
|
+
raise NotImplementedError(
|
|
1135
|
+
f"extract_a2f does not support method='{method}'."
|
|
1136
|
+
)
|
|
1137
|
+
|
|
1138
|
+
# --- Plot on ax: always raw chi2 + guess; add fit only if success ---
|
|
1139
|
+
alpha_range = chi2kink_result["alpha_range"]
|
|
1140
|
+
alpha0 = float(alpha_range[0])
|
|
1141
|
+
x_plot = np.log10(alpha_range / alpha0)
|
|
1142
|
+
y_chi2 = chi2kink_result["log_chi_squared"]
|
|
1143
|
+
|
|
1144
|
+
ax.set_xlabel(r"log$_{10}(\alpha)$ (-)")
|
|
1145
|
+
ax.set_ylabel(r"log$_{10}(\chi^2)$ (-)")
|
|
1146
|
+
|
|
1147
|
+
ax.plot(x_plot, y_chi2, label="data")
|
|
1148
|
+
ax.plot(x_plot, guess_curve, label="guess")
|
|
1149
|
+
|
|
1150
|
+
if chi2kink_result["success"]:
|
|
1151
|
+
ax.plot(x_plot, fit_curve, label="fit")
|
|
1152
|
+
ax.axvline(
|
|
1153
|
+
np.log10(alpha_select / alpha0),
|
|
1154
|
+
linestyle="--",
|
|
1155
|
+
label=r"$\alpha_{\rm sel}$",
|
|
1156
|
+
)
|
|
1157
|
+
ax.legend()
|
|
1158
|
+
|
|
1159
|
+
# Abort extraction if fit failed (you asked to terminate in that case)
|
|
1160
|
+
if not chi2kink_result["success"]:
|
|
1161
|
+
raise RuntimeError(
|
|
1162
|
+
"chi2kink logistic fit failed; aborting extract_a2f after "
|
|
1163
|
+
"plotting chi2 and guess."
|
|
1164
|
+
)
|
|
824
1165
|
|
|
1166
|
+
# From here on, we know spectrum_in and alpha_select exist
|
|
825
1167
|
spectrum = spectrum_in * omega_num / omega_max
|
|
826
|
-
|
|
1168
|
+
|
|
1169
|
+
self._a2f_spectrum = spectrum
|
|
1170
|
+
self._a2f_model = model
|
|
1171
|
+
self._a2f_omega_range = omega_range
|
|
1172
|
+
self._a2f_alpha_select = alpha_select
|
|
1173
|
+
self._a2f_cost = None
|
|
1174
|
+
|
|
1175
|
+
return fig, spectrum, model, omega_range, alpha_select
|
|
827
1176
|
|
|
828
1177
|
|
|
829
1178
|
def bayesian_loop(self, *, omega_min, omega_max, omega_num, omega_I,
|
|
@@ -1193,12 +1542,15 @@ class SelfEnergy:
|
|
|
1193
1542
|
print(" | ".join(msg))
|
|
1194
1543
|
|
|
1195
1544
|
return cost_f
|
|
1196
|
-
|
|
1545
|
+
|
|
1197
1546
|
class TerminationCallback:
|
|
1198
|
-
def __init__(self, tole, converge_iters,
|
|
1547
|
+
def __init__(self, tole, converge_iters,
|
|
1548
|
+
min_steps_for_regression):
|
|
1199
1549
|
self.tole = None if tole is None else float(tole)
|
|
1200
1550
|
self.converge_iters = int(converge_iters)
|
|
1201
|
-
self.min_steps_for_regression = int(
|
|
1551
|
+
self.min_steps_for_regression = int(
|
|
1552
|
+
min_steps_for_regression
|
|
1553
|
+
)
|
|
1202
1554
|
self.iter_count = 0
|
|
1203
1555
|
self.call_count = 0
|
|
1204
1556
|
|
|
@@ -1212,36 +1564,41 @@ class SelfEnergy:
|
|
|
1212
1564
|
if current is None:
|
|
1213
1565
|
return
|
|
1214
1566
|
|
|
1215
|
-
best_cost = float(
|
|
1216
|
-
if np.isfinite(best_cost)
|
|
1217
|
-
self.
|
|
1218
|
-
|
|
1219
|
-
|
|
1567
|
+
best_cost = float(best_global["cost"])
|
|
1568
|
+
if np.isfinite(best_cost):
|
|
1569
|
+
if abs(current - best_cost) < self.tole:
|
|
1570
|
+
self.iter_count += 1
|
|
1571
|
+
else:
|
|
1572
|
+
self.iter_count = 0
|
|
1220
1573
|
|
|
1221
1574
|
if self.iter_count >= self.converge_iters:
|
|
1222
1575
|
raise ConvergenceException(
|
|
1223
|
-
|
|
1576
|
+
"Converged: |cost-best| < "
|
|
1577
|
+
f"{self.tole:g} for "
|
|
1224
1578
|
f"{self.converge_iters} iterations."
|
|
1225
1579
|
)
|
|
1226
|
-
|
|
1580
|
+
|
|
1227
1581
|
if self.call_count < self.min_steps_for_regression:
|
|
1228
1582
|
return
|
|
1229
1583
|
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
if init is None:
|
|
1584
|
+
init_cost = initial_cost["cost"]
|
|
1585
|
+
if init_cost is None:
|
|
1233
1586
|
return
|
|
1234
1587
|
|
|
1235
|
-
|
|
1588
|
+
current = float(current)
|
|
1589
|
+
init_cost = float(init_cost)
|
|
1590
|
+
|
|
1236
1591
|
if not np.isfinite(best_cost):
|
|
1237
1592
|
return
|
|
1238
1593
|
|
|
1239
|
-
if
|
|
1594
|
+
if (
|
|
1595
|
+
abs(current - init_cost) * relative_best
|
|
1596
|
+
< abs(current - best_cost)
|
|
1597
|
+
):
|
|
1240
1598
|
raise RegressionException(
|
|
1241
1599
|
"Regression toward initial guess detected."
|
|
1242
1600
|
)
|
|
1243
1601
|
|
|
1244
|
-
|
|
1245
1602
|
callback = TerminationCallback(
|
|
1246
1603
|
tole=tole,
|
|
1247
1604
|
converge_iters=converge_iters,
|
|
@@ -1345,17 +1702,26 @@ class SelfEnergy:
|
|
|
1345
1702
|
print("Optimised parameters:")
|
|
1346
1703
|
print(args)
|
|
1347
1704
|
|
|
1348
|
-
|
|
1705
|
+
# store inside class methods
|
|
1706
|
+
self._a2f_spectrum = spectrum
|
|
1707
|
+
self._a2f_model = model
|
|
1708
|
+
self._a2f_omega_range = omega_range
|
|
1709
|
+
self._a2f_alpha_select = alpha_select
|
|
1710
|
+
self._a2f_cost = cost
|
|
1349
1711
|
|
|
1712
|
+
return spectrum, model, omega_range, alpha_select, cost, params
|
|
1350
1713
|
|
|
1351
1714
|
@staticmethod
|
|
1352
1715
|
def _merge_defaults(defaults, override_dict=None, override_kwargs=None):
|
|
1353
1716
|
"""Merge defaults with dict + kwargs overrides (kwargs win)."""
|
|
1354
1717
|
cfg = dict(defaults)
|
|
1355
|
-
|
|
1718
|
+
|
|
1719
|
+
if override_dict is not None:
|
|
1356
1720
|
cfg.update(dict(override_dict))
|
|
1357
|
-
|
|
1721
|
+
|
|
1722
|
+
if override_kwargs is not None:
|
|
1358
1723
|
cfg.update({k: v for k, v in override_kwargs.items() if v is not None})
|
|
1724
|
+
|
|
1359
1725
|
return cfg
|
|
1360
1726
|
|
|
1361
1727
|
def _prepare_bare(self, fermi_velocity, fermi_wavevector, bare_mass):
|
|
@@ -1490,11 +1856,9 @@ class SelfEnergy:
|
|
|
1490
1856
|
else:
|
|
1491
1857
|
f_chi_squared = float(f_chi_squared)
|
|
1492
1858
|
|
|
1493
|
-
|
|
1494
|
-
if h_n is None:
|
|
1859
|
+
if d_guess <= 0.0:
|
|
1495
1860
|
raise ValueError(
|
|
1496
|
-
"
|
|
1497
|
-
"No default is assumed."
|
|
1861
|
+
"chi2kink requires d_guess > 0 to fix the logistic sign ambiguity."
|
|
1498
1862
|
)
|
|
1499
1863
|
|
|
1500
1864
|
if parts not in {"both", "real", "imag"}:
|
|
@@ -1622,7 +1986,8 @@ class SelfEnergy:
|
|
|
1622
1986
|
dvec = imag_m
|
|
1623
1987
|
wvec = imag_sig_m**(-2)
|
|
1624
1988
|
|
|
1625
|
-
spectrum_in, alpha_select
|
|
1989
|
+
(spectrum_in, alpha_select, fit_curve, guess_curve,
|
|
1990
|
+
chi2kink_result) = self._chi2kink_a2f(
|
|
1626
1991
|
dvec, model_in, uvec, mu, wvec, V_Sigma, U, alpha_min, alpha_max,
|
|
1627
1992
|
alpha_num, a_guess, b_guess, c_guess, d_guess, f_chi_squared,
|
|
1628
1993
|
t_criterion, iter_max, MEM_core,
|
|
@@ -1657,26 +2022,44 @@ class SelfEnergy:
|
|
|
1657
2022
|
|
|
1658
2023
|
|
|
1659
2024
|
@staticmethod
|
|
1660
|
-
def _chi2kink_a2f(dvec, model_in, uvec, mu, wvec, V_Sigma, U,
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
r"""
|
|
2025
|
+
def _chi2kink_a2f(dvec, model_in, uvec, mu, wvec, V_Sigma, U, alpha_min,
|
|
2026
|
+
alpha_max, alpha_num, a_guess, b_guess, c_guess, d_guess,
|
|
2027
|
+
f_chi_squared, t_criterion, iter_max, MEM_core, *,
|
|
2028
|
+
plot=None):
|
|
2029
|
+
r"""
|
|
2030
|
+
Compute MEM spectrum using the chi2-kink alpha-selection procedure.
|
|
1665
2031
|
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
2032
|
+
Notes
|
|
2033
|
+
-----
|
|
2034
|
+
This routine contains extensive logic to detect failure modes of the
|
|
2035
|
+
chi2-kink logistic fit, including (but not limited to):
|
|
2036
|
+
|
|
2037
|
+
- non-finite or non-positive chi² values,
|
|
2038
|
+
- lack of meaningful parameter updates relative to the initial guess,
|
|
2039
|
+
- absence of improvement in the residual sum of squares,
|
|
2040
|
+
- numerical instabilities or overflows in the logistic model,
|
|
2041
|
+
- invalid or non-finite alpha selection.
|
|
2042
|
+
|
|
2043
|
+
Despite these safeguards, it is **not possible to guarantee** that all
|
|
2044
|
+
failure modes are detected in a nonlinear least-squares problem.
|
|
2045
|
+
Consequently, a reported success should be interpreted as a *necessary*
|
|
2046
|
+
but *not sufficient* condition for physical or numerical reliability.
|
|
2047
|
+
|
|
2048
|
+
Callers **must** inspect the returned ``success`` flag (contained in
|
|
2049
|
+
``chi2kink_result``) before using the fitted curve, selected alpha, or
|
|
2050
|
+
MEM spectrum. When ``success`` is False, the returned quantities are
|
|
2051
|
+
limited to those required for diagnostic plotting only.
|
|
1670
2052
|
"""
|
|
1671
|
-
from . import
|
|
2053
|
+
from . import fit_least_squares, chi2kink_logistic
|
|
1672
2054
|
|
|
1673
|
-
alpha_range = np.logspace(alpha_min, alpha_max, alpha_num)
|
|
2055
|
+
alpha_range = np.logspace(alpha_min, alpha_max, int(alpha_num))
|
|
1674
2056
|
chi_squared = np.empty_like(alpha_range, dtype=float)
|
|
1675
2057
|
|
|
1676
2058
|
for i, alpha in enumerate(alpha_range):
|
|
1677
|
-
spectrum_in, uvec = MEM_core(
|
|
1678
|
-
wvec, V_Sigma, U,
|
|
1679
|
-
|
|
2059
|
+
spectrum_in, uvec = MEM_core(
|
|
2060
|
+
dvec, model_in, uvec, mu, alpha, wvec, V_Sigma, U,
|
|
2061
|
+
t_criterion, iter_max
|
|
2062
|
+
)
|
|
1680
2063
|
T = V_Sigma @ (U.T @ spectrum_in)
|
|
1681
2064
|
chi_squared[i] = wvec @ ((T - dvec) ** 2)
|
|
1682
2065
|
|
|
@@ -1689,18 +2072,79 @@ class SelfEnergy:
|
|
|
1689
2072
|
log_chi_squared = np.log10(chi_squared)
|
|
1690
2073
|
|
|
1691
2074
|
p0 = np.array([a_guess, b_guess, c_guess, d_guess], dtype=float)
|
|
1692
|
-
pfit, pcov =
|
|
2075
|
+
pfit, pcov, lsq_success = fit_least_squares(
|
|
1693
2076
|
p0, log_alpha, log_chi_squared, chi2kink_logistic
|
|
1694
2077
|
)
|
|
1695
2078
|
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
2079
|
+
with np.errstate(over="ignore", invalid="ignore", divide="ignore"):
|
|
2080
|
+
guess_curve = chi2kink_logistic(log_alpha, *p0)
|
|
2081
|
+
|
|
2082
|
+
# Start from the necessary requirement: least_squares must say success
|
|
2083
|
+
success = bool(lsq_success)
|
|
1699
2084
|
|
|
1700
|
-
|
|
1701
|
-
|
|
2085
|
+
# If the guess itself blows up, we can't trust anything
|
|
2086
|
+
if not np.all(np.isfinite(guess_curve)):
|
|
2087
|
+
success = False
|
|
2088
|
+
|
|
2089
|
+
fit_curve_tmp = None
|
|
2090
|
+
if success:
|
|
2091
|
+
pfit = np.asarray(pfit, dtype=float)
|
|
2092
|
+
|
|
2093
|
+
if np.allclose(pfit, p0, rtol=1e-12, atol=0.0):
|
|
2094
|
+
success = False
|
|
2095
|
+
else:
|
|
2096
|
+
with np.errstate(over="ignore", invalid="ignore",
|
|
2097
|
+
divide="ignore"):
|
|
2098
|
+
fit_curve_tmp = chi2kink_logistic(log_alpha, *pfit)
|
|
2099
|
+
|
|
2100
|
+
if not np.all(np.isfinite(fit_curve_tmp)):
|
|
2101
|
+
success = False
|
|
2102
|
+
else:
|
|
2103
|
+
r0 = guess_curve - log_chi_squared
|
|
2104
|
+
r1 = fit_curve_tmp - log_chi_squared
|
|
2105
|
+
sse0 = float(r0 @ r0)
|
|
2106
|
+
sse1 = float(r1 @ r1)
|
|
2107
|
+
|
|
2108
|
+
tol = 1e-12 * max(1.0, sse0)
|
|
2109
|
+
if (not np.isfinite(sse1)) or (sse1 >= sse0 - tol):
|
|
2110
|
+
success = False
|
|
2111
|
+
|
|
2112
|
+
alpha_select = None
|
|
2113
|
+
fit_curve = None
|
|
2114
|
+
spectrum_out = None
|
|
2115
|
+
|
|
2116
|
+
if success:
|
|
2117
|
+
fit_curve = fit_curve_tmp
|
|
2118
|
+
|
|
2119
|
+
cout = float(pfit[2])
|
|
2120
|
+
dout = float(pfit[3])
|
|
2121
|
+
exp10 = cout - float(f_chi_squared) / dout
|
|
2122
|
+
|
|
2123
|
+
if (not np.isfinite(exp10)) or (exp10 < -308.0) or (exp10 > 308.0):
|
|
2124
|
+
success = False
|
|
2125
|
+
fit_curve = None
|
|
2126
|
+
else:
|
|
2127
|
+
with np.errstate(over="raise", invalid="raise"):
|
|
2128
|
+
alpha_select = float(np.power(10.0, exp10))
|
|
2129
|
+
|
|
2130
|
+
spectrum_out, uvec = MEM_core(
|
|
2131
|
+
dvec, model_in, uvec, mu, alpha_select, wvec, V_Sigma, U,
|
|
2132
|
+
t_criterion, iter_max
|
|
2133
|
+
)
|
|
2134
|
+
|
|
2135
|
+
chi2kink_result = {
|
|
2136
|
+
"alpha_range": alpha_range,
|
|
2137
|
+
"chi_squared": chi_squared,
|
|
2138
|
+
"log_alpha": log_alpha,
|
|
2139
|
+
"log_chi_squared": log_chi_squared,
|
|
2140
|
+
"p0": p0,
|
|
2141
|
+
"pfit": pfit,
|
|
2142
|
+
"pcov": pcov,
|
|
2143
|
+
"success": bool(success),
|
|
2144
|
+
"alpha_select": alpha_select,
|
|
2145
|
+
}
|
|
1702
2146
|
|
|
1703
|
-
return
|
|
2147
|
+
return spectrum_out, alpha_select, fit_curve, guess_curve, chi2kink_result
|
|
1704
2148
|
|
|
1705
2149
|
|
|
1706
2150
|
@staticmethod
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
xarpes/__init__.py,sha256=Gl_nk_DezgxOU2V3uvpZCmIQekEExG-2w6vgZTGVuPY,756
|
|
2
|
+
xarpes/bandmap.py,sha256=1B5GbRXFdBPqnmeKPJW0mzlz-IUoLD-28rxrThpp4co,34664
|
|
3
|
+
xarpes/constants.py,sha256=XOdgSzyrmHr5xocHZfmcFHHoVAa1G05a305hm3XOTtY,504
|
|
4
|
+
xarpes/distributions.py,sha256=pC8V5MlZDNFdooMonFREEASiN5QodHiyKc2ehnxMKvQ,23498
|
|
5
|
+
xarpes/functions.py,sha256=ibWoSa7_yXD9XsIEif0kbbbHidIozBivvaAEui9f64A,20675
|
|
6
|
+
xarpes/mdcs.py,sha256=WRKSfGlRVKBssJp9FIHcAFsINVunPkmW9fBnFjqBHYI,42844
|
|
7
|
+
xarpes/plotting.py,sha256=lGCReHcXhYLQXR5ns3EHFjCQjJ9Sc-HifV7n4BnWby4,5189
|
|
8
|
+
xarpes/selfenergies.py,sha256=iP4WDPpcS5-3lyoREC0YaJZm3QXFdukvMD1_oH3I6mc,81141
|
|
9
|
+
xarpes/settings_parameters.py,sha256=yOYvgEiDeDiLzzLkvysCTiVwqg6fKIkN48B-WSad728,1912
|
|
10
|
+
xarpes/settings_plots.py,sha256=X-qteB2fIbBKOAcLMvMYDfQ8QdlUeA5xYQqF_Nyb4uA,1562
|
|
11
|
+
xarpes-0.6.2.dist-info/entry_points.txt,sha256=917UR-cqFTMMI_vMqIbk7boYSuFX_zHwQlXKcj9vlCE,79
|
|
12
|
+
xarpes-0.6.2.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
13
|
+
xarpes-0.6.2.dist-info/WHEEL,sha256=jPMR_Dzkc4X4icQtmz81lnNY_kAsfog7ry7qoRvYLXw,81
|
|
14
|
+
xarpes-0.6.2.dist-info/METADATA,sha256=c3EJtxdCJ9TDSFQLuO2wrYly9sG2JIqeOndeWAPKtDM,7154
|
|
15
|
+
xarpes-0.6.2.dist-info/RECORD,,
|
xarpes-0.6.0.dist-info/RECORD
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
xarpes/__init__.py,sha256=dfT64reIAoDadP6Kz8T8s8lXi7jgcoau4Jy2Du_GzCY,756
|
|
2
|
-
xarpes/bandmap.py,sha256=CGmXhY1ViZMlpDdqpgVUJsmhGRQdcPLa79l53QMP-Fc,34365
|
|
3
|
-
xarpes/constants.py,sha256=XOdgSzyrmHr5xocHZfmcFHHoVAa1G05a305hm3XOTtY,504
|
|
4
|
-
xarpes/distributions.py,sha256=pC8V5MlZDNFdooMonFREEASiN5QodHiyKc2ehnxMKvQ,23498
|
|
5
|
-
xarpes/functions.py,sha256=_ecottx3vi1twu_7lUrO_G3UbhCd0YMIaKezYEAW0Sw,20837
|
|
6
|
-
xarpes/mdcs.py,sha256=WRKSfGlRVKBssJp9FIHcAFsINVunPkmW9fBnFjqBHYI,42844
|
|
7
|
-
xarpes/plotting.py,sha256=lGCReHcXhYLQXR5ns3EHFjCQjJ9Sc-HifV7n4BnWby4,5189
|
|
8
|
-
xarpes/selfenergies.py,sha256=mal1SiezXq0Upg8xBL8PagUQN2ZAzCF84geRtY3gSDc,64611
|
|
9
|
-
xarpes/settings_parameters.py,sha256=yOYvgEiDeDiLzzLkvysCTiVwqg6fKIkN48B-WSad728,1912
|
|
10
|
-
xarpes/settings_plots.py,sha256=X-qteB2fIbBKOAcLMvMYDfQ8QdlUeA5xYQqF_Nyb4uA,1562
|
|
11
|
-
xarpes-0.6.0.dist-info/entry_points.txt,sha256=917UR-cqFTMMI_vMqIbk7boYSuFX_zHwQlXKcj9vlCE,79
|
|
12
|
-
xarpes-0.6.0.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
13
|
-
xarpes-0.6.0.dist-info/WHEEL,sha256=jPMR_Dzkc4X4icQtmz81lnNY_kAsfog7ry7qoRvYLXw,81
|
|
14
|
-
xarpes-0.6.0.dist-info/METADATA,sha256=NlUZioy3lvyew1P0QlKuBbyxO0g_YkTC6A5MmzVZLdY,7154
|
|
15
|
-
xarpes-0.6.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|