madman.analysis.spectrum
Spectrum base classes.
1r"""Spectrum base classes.""" 2 3from collections.abc import Mapping, Sequence 4from numbers import Integral, Real 5from sys import modules 6from typing import Any, Self, Type 7 8import numpy as np 9import seaborn as sns 10from matplotlib import pyplot as plt 11from scipy.ndimage import gaussian_filter1d 12 13from madman.utilities import gen_regular_grid 14 15 16sns.set_theme( 17 context="talk", 18 style="white", 19 rc={"figure.titlesize": "medium", "axes.formatter.useoffset": False}, 20) 21 22 23class EnergySpectrumMeta(type): 24 r"""Metaclas of EnergySpectrum.""" 25 26 @property 27 def rsv(cls) -> Type: 28 r"""Associated resolved class.""" 29 module = modules[cls.__module__] 30 name = "Resolved" + cls.__name__ 31 return getattr(module, name) 32 33 @property 34 def band_rsv(cls) -> Type: 35 r"""Associated band resolved class.""" 36 module = modules[cls.__module__] 37 name = "BandResolved" + cls.__name__ 38 return getattr(module, name) 39 40 @property 41 def cell_rsv(cls) -> Type: 42 r"""Associated cell resolved class.""" 43 module = modules[cls.__module__] 44 name = "CellResolved" + cls.__name__ 45 return getattr(module, name) 46 47 @property 48 def std_axes(cls) -> plt.Axes: 49 r"""Standard axes to plot energy spectrum.""" 50 _, ax = plt.subplots(tight_layout=True) 51 ax.set_xlabel(r"Energy / $\mathrm{eV}$") 52 ax.set_ylabel(r"Values / $\mathrm{n.a.}$") 53 return ax 54 55 56class EnergySpectrum(metaclass=EnergySpectrumMeta): 57 r"""Energy spectrum.""" 58 59 def __init__( 60 self, 61 energies: Sequence[Real, ...], 62 values: Sequence[Real, ...], 63 *, 64 d_energy: Real | None = None, 65 ) -> None: 66 r"""Initialize EnergySpectrum object. 67 68 Args: 69 energies: Spectrum energies [$\mathrm{eV}$]. 70 values: Spectrum values. 71 d_energy: Target energy increment [$\mathrm{eV}$]. 72 73 Raises: 74 ValueError: If there are duplicate energies. 75 """ 76 energies = np.asarray(energies, dtype=float).flatten() 77 values = np.asarray(values, dtype=float).flatten() 78 79 sort_i = np.argsort(energies) 80 energies = energies[sort_i] 81 values = values[sort_i] 82 83 if not np.array_equal(energies, np.unique(energies)): 84 raise ValueError("Duplicate energies!") 85 86 energy_a = np.amin(energies) 87 energy_b = np.amax(energies) 88 if d_energy is None: 89 d_energy = np.diff(energies).min() 90 91 self._energies = gen_regular_grid(energy_a, energy_b, d_energy) 92 self._values = np.interp(self._energies, energies, values) 93 self._integr = np.trapz(self.values, x=self.energies) 94 95 def __add__(self, other: Self) -> Self: 96 r"""Add energy spectrum. 97 98 Args: 99 other: Energy spectrum to add. 100 101 Returns: 102 Sum of `self` and `other`. 103 104 Raises: 105 TypeError: If `self` and `other` have incompatible types. 106 ValueError: If `self` and `other` have incompatible energies. 107 """ 108 if type(self) != type(other): 109 raise TypeError("Incompatible types!") 110 if not np.array_equal(self.energies, other.energies): 111 raise ValueError("Incompatible energies!") 112 return type(self)(self.energies, self.values + other.values) 113 114 @property 115 def energies(self) -> np.ndarray[float]: 116 r"""Spectrum energies [$\mathrm{eV}$].""" 117 return self._energies 118 119 @property 120 def values(self) -> np.ndarray[float]: 121 r"""Spectrum values.""" 122 return self._values 123 124 @property 125 def integr(self) -> float: 126 r"""Spectrum integrated value.""" 127 return self._integr 128 129 def ret_zero(self) -> Self: 130 r"""Return zero energy spectrum.""" 131 return type(self).zero(self.energies) 132 133 def ret_smeared(self, *, sigma: Real) -> Self: 134 r"""Return smeared energy spectrum. 135 136 Args: 137 sigma: Standard deviation for Gaussian kernel. 138 139 Returns: 140 Smeared energy spectrum. 141 """ 142 values = gaussian_filter1d(self.values, sigma) 143 return type(self)(self.energies, values) 144 145 def plot( 146 self, 147 *, 148 ax: plt.Axes | None = None, 149 color: str | None = None, 150 label: str | None = None, 151 ) -> plt.Figure: 152 r"""Plot energy spectrum. 153 154 Args: 155 ax: Plot axes; if None, new figure and axes are created. 156 color: Energy spectrum color. 157 label: Energy spectrum label. 158 159 Returns: 160 Plot figure. 161 """ 162 if ax is None: 163 ax = type(self).std_axes 164 ax.plot(self.energies, self.values, color=color, label=label) 165 return ax.get_figure() 166 167 @classmethod 168 def zero( 169 cls, energies: Sequence[Real, ...], *, d_energy: Real | None = None 170 ) -> np.ndarray[float]: 171 r"""Zero energy spectrum. 172 173 Args: 174 energies: Spectrum energies [$\mathrm{eV}$]. 175 d_energy: Target energy increment [$\mathrm{eV}$]. 176 177 Returns: 178 Zero energy spectrum. 179 """ 180 values = np.zeros_like(energies) 181 return cls(energies, values, d_energy=d_energy) 182 183 @classmethod 184 def piecewise_constant( 185 cls, 186 energy_a: Real, 187 energy_b: Real, 188 d_energy: Real, 189 *, 190 amplitudes: Sequence[Real, ...], 191 low_bounds: Sequence[Real, ...], 192 upp_bounds: Sequence[Real, ...], 193 ) -> Self: 194 r"""Piecewise constant energy spectrum. 195 196 Args: 197 energy_a: Energy minimum [$\mathrm{eV}$]. 198 energy_b: Energy maximum [$\mathrm{eV}$]. 199 d_energy: Target energy increment [$\mathrm{eV}$]. 200 amplitudes: Piece amplitudes. 201 low_bounds: Piece lower bounds [$\mathrm{eV}$]. 202 upp_bounds: Piece upper bounds [$\mathrm{eV}$]. 203 204 Returns: 205 Piecewise constant energy spectrum. 206 """ 207 energies = gen_regular_grid(energy_a, energy_b, d_energy) 208 209 def rect(x, a, b): 210 return np.heaviside(x - a, 0.5) - np.heaviside(x - b, 0.5) 211 212 values = 0.0 213 for amp, lb, ub in zip(amplitudes, low_bounds, upp_bounds): 214 values += amp * rect(energies, lb, ub) 215 return cls(energies, values) 216 217 218class ResolvedEnergySpectrumMeta(type): 219 r"""Metaclass of ResolvedEnergySpectrum.""" 220 221 @property 222 def total(cls) -> Type: 223 r"""Total energy spectrum class.""" 224 module = modules[cls.__module__] 225 name = cls.__name__.split("Resolved")[1] 226 return getattr(module, name) 227 228 229class ResolvedEnergySpectrum(metaclass=ResolvedEnergySpectrumMeta): 230 r"""Resolved energy spectrum.""" 231 232 def __init__( 233 self, 234 energies: Sequence[Real, ...], 235 rsv_values: Mapping[Any, Sequence[Real, ...]], 236 *, 237 d_energy: Real | None = None, 238 ) -> None: 239 r"""Initialize ResolvedEnergySpectrum object. 240 241 Args: 242 energies: Spectrum energies [$\mathrm{eV}$]. 243 rsv_values: Mapping of feature into spectrum values. 244 d_energy: Target energy increment [$\mathrm{eV}$]. 245 246 Raises: 247 ValueError: If band index is non-integer. 248 ValueError: If cell role is not in {'v', 'i', 'c'}. 249 """ 250 self._rsv_values = {} 251 self._rsv_integr = {} 252 self._total = type(self).total.zero(energies, d_energy=d_energy) 253 254 for feat, values in rsv_values.items(): 255 if not isinstance(self, PairResolvedEnergySpectrum): 256 257 if "Band" in type(self).__name__: 258 if not isinstance(feat, Integral): 259 raise ValueError("Non-integer band index!") 260 feat = int(feat) 261 262 if "Cell" in type(self).__name__: 263 if not feat in {"v", "i", "c"}: 264 raise ValueError("Cell role not in {'v', 'i', 'c'}") 265 266 spectrum = type(self).total(energies, values, d_energy=d_energy) 267 self._rsv_values[feat] = spectrum.values 268 self._rsv_integr[feat] = np.trapz( 269 spectrum.values, x=spectrum.energies 270 ) 271 self._total += spectrum 272 273 if ( 274 not isinstance(self, PairResolvedEnergySpectrum) 275 and "Cell" in type(self).__name__ 276 ): 277 rsv_values_sorted = {} 278 for feat in ["v", "i", "c"]: 279 if feat in self._rsv_values: 280 rsv_values_sorted.update({feat: self._rsv_values[feat]}) 281 self._rsv_values = rsv_values_sorted 282 283 self._energies = spectrum.energies 284 285 self._rsv_sp_weight = {} 286 for feat, values in rsv_values.items(): 287 sp_weight = self._rsv_integr[feat] / self._total.integr 288 self._rsv_sp_weight[feat] = sp_weight 289 290 self._rsv_sp_select = {} 291 for feat, values in rsv_values.items(): 292 sp_select = 1.0 - np.trapz( 293 values * (self._total.values - values), x=self._energies 294 ) / np.sqrt( 295 np.trapz(values**2, x=self._energies) 296 * np.trapz((self._total.values - values) ** 2, x=self._energies) 297 ) 298 self._rsv_sp_select[feat] = sp_select 299 300 def __add__(self, other: Self) -> Self: 301 r"""Add resolved energy spectrum. 302 303 Args: 304 other: Resolved energy spectrum to add. 305 306 Returns: 307 Sum of `self` and `other`. 308 309 Raises: 310 TypeError: If `self` and `other` have incompatible types. 311 ValueError: If `self` and `other` have incompatible energies. 312 ValueError: If `self` and `other` have incompatible features. 313 """ 314 if type(self) != type(other): 315 raise TypeError("Incompatible types!") 316 if not np.array_equal(self.energies, other.energies): 317 raise ValueError("Incompatible energies!") 318 if self.rsv_values.keys() != other.rsv_values.keys(): 319 raise ValueError("Incompatible features!") 320 rsv_values = { 321 feat: values + other.rsv_values[feat] 322 for feat, values in self.rsv_values.items() 323 } 324 return type(self)(self.energies, rsv_values) 325 326 @property 327 def energies(self) -> np.ndarray[float]: 328 r"""Spectrum energies [$\mathrm{eV}$].""" 329 return self._energies 330 331 @property 332 def rsv_values(self) -> dict[Any, np.ndarray[float]]: 333 r"""Resolved spectrum values.""" 334 return self._rsv_values 335 336 @property 337 def rsv_integr(self) -> dict[Any, float]: 338 r"""Resolved spectrum integrated values.""" 339 return self._rsv_integr 340 341 @property 342 def rsv_sp_weight(self) -> dict[Any, float]: 343 r"""Resolved spectral weight. 344 345 Note: 346 The spectral weight of feature $x$ is defined as: 347 348 $$ 349 \frac{\int \mathrm{d}(\hbar \, \omega) \, \Sigma_{x}(\hbar \, \omega)}{\int \mathrm{d}(\hbar \, \omega) \, \Sigma_{\text{tot}}(\hbar \, \omega)} 350 $$ 351 352 where $\hbar \, \omega$ is the photon energy, $\Sigma_{x}$ is the 353 partial spectrum og feature $x$, and $\Sigma_{\text{tot}}$ is the 354 total spectrum. 355 """ 356 return self._rsv_sp_weight 357 358 @property 359 def rsv_sp_select(self) -> dict[Any, float]: 360 r"""Resolved spectral selectivity. 361 362 Note: 363 The spectral selectivity of feature $x$ is defined as: 364 365 $$ 366 1 - \frac{\int \mathrm{d}(\hbar \, \omega) \, \Sigma_{x}(\hbar \, \omega) \, \left[ \Sigma_{\text{tot}}(\hbar \, \omega) - \Sigma_{x}(\hbar \, \omega) \right]}{\sqrt{\int \mathrm{d}(\hbar \, \omega) \, \Sigma_{x}^{2}(\hbar \, \omega) \, \int \mathrm{d}(\hbar \, \omega) \, \left[ \Sigma_{\text{tot}}(\hbar \, \omega) - \Sigma_{x}(\hbar \, \omega)\right]^{2}}} 367 $$ 368 369 where $\hbar \, \omega$ is the photon energy, $\Sigma_{x}$ is the 370 partial spectrum og feature $x$, and $\Sigma_{\text{tot}}$ is the total 371 spectrum. 372 """ 373 return self._rsv_sp_select 374 375 @property 376 def total(self) -> EnergySpectrum: 377 r"""Total energy spectrum.""" 378 return self._total 379 380 def ret_zero(self) -> Self: 381 r"""Return zero resolved energy spectrum.""" 382 return type(self).zero(self.energies, self.rsv_values) 383 384 def ret_smeared(self, *, sigma: Real) -> Self: 385 r"""Return smeared resolved energy spectrum. 386 387 Args: 388 sigma: Standard deviation for Gaussian kernel. 389 390 Returns: 391 Smeared resolved energy spectrum. 392 """ 393 rsv_values = { 394 feat: gaussian_filter1d(values, sigma) 395 for feat, values in self.rsv_values.items() 396 } 397 return type(self)(self.energies, rsv_values) 398 399 def plot( 400 self, 401 *, 402 ax: plt.Axes | None = None, 403 stack: bool = False, 404 ) -> plt.Figure: 405 r"""Plot resolved energy spectrum. 406 407 Args: 408 ax: Plot axes; if None, new figure and axes are created. 409 stack: If True, a stackplot is generated. 410 411 Returns: 412 Plot figure. 413 414 Raises: 415 TypeError: If `stack` is not boolean. 416 """ 417 if ax is None: 418 ax = type(self).total.std_axes 419 420 if stack is True: 421 ax.stackplot( 422 self.energies, 423 self.rsv_values.values(), 424 labels=self.rsv_values.keys(), 425 ) 426 elif stack is False: 427 for feat, values in self.rsv_values.items(): 428 ax.plot(self.energies, values, label=feat) 429 else: 430 raise TypeError("`stack` must be boolean!") 431 432 ax.plot( 433 self.energies, 434 self.total.values, 435 linestyle="--", 436 color="k", 437 label="total", 438 ) 439 ax.legend(loc="upper left", bbox_to_anchor=[1.0, 1.0], frameon=False) 440 return ax.get_figure() 441 442 @classmethod 443 def zero( 444 cls, 445 energies: Sequence[Real, ...], 446 features: Sequence[Any, ...], 447 *, 448 d_energy: Real | None = None, 449 ) -> Self: 450 r"""Zero resolved energy spectrum. 451 452 Args: 453 energies: Resolved spectrum energies [$\mathrm{eV}$]. 454 features: Resolved spectrum features. 455 d_energy: Target energy increment [$\mathrm{eV}$]. 456 457 Returns: 458 Zero resolved energy spectrum. 459 """ 460 rsv_values = {feat: np.zeros_like(energies) for feat in features} 461 return cls(energies, rsv_values, d_energy=d_energy) 462 463 464class BandResolvedEnergySpectrum(ResolvedEnergySpectrum): 465 r"""Band resolved energy spectrum. 466 467 Note: 468 'Band' is hereby intended as a range of electron energies with no zero 469 electron density of states values such that any larger range contains 470 zero electron density of states values. 471 To distinguish, a band of a dispersion relation (e.g. the electronic 472 band structure of a crystalline solid) is hereby referred as 'branch'. 473 """ 474 475 476class CellResolvedEnergySpectrum(ResolvedEnergySpectrum): 477 r"""Cell resolved energy spectrum. 478 479 Note: 480 The energy spectrum is resolved according to the components role in a 481 solar cells. 482 The following convention is adopted: 483 484 - 'v': valence bands; 485 - 'i': intermediate bands; 486 - 'c': conduction bands. 487 488 See 489 [Shockley & Queisser (1961)](https://dx.doi.org/10.1063%2F1.1736034) 490 and 491 [Levy & Honsberg (2008)](http://dx.doi.org/10.1103%2FPhysRevB.78.165122) 492 for details. 493 """ 494 495 496class PairResolvedEnergySpectrum(ResolvedEnergySpectrum): 497 r"""Pair resolved energy spectrum.""" 498 499 def __init__( 500 self, 501 energies: Sequence[Real, ...], 502 rsv_values: Mapping[Sequence[Any, Any], Sequence[Real, ...]], 503 *, 504 d_energy: Real | None = None, 505 ) -> None: 506 r"""Initialize ResolvedEnergySpectrum object. 507 508 Args: 509 energies: Spectrum energies [$\mathrm{eV}$]. 510 rsv_values: Mapping of feature pair into spectrum values. 511 d_energy: Target energy increment [$\mathrm{eV}$]. 512 513 Raises: 514 TypeError: If a feature pair is not a sequence. 515 ValueError: If a feature pair is not a pair. 516 ValueError: If a band index pair is not a pair of integers. 517 ValueError: If a cell role pair is not in {'ii', 'vi', 'ic', 'vc'}. 518 """ 519 rsv_values_new = {} 520 for pair, values in rsv_values.items(): 521 if not isinstance(pair, Sequence): 522 raise TypeError("Not a sequence!") 523 if not len(pair) == 2: 524 raise ValueError("Not a pair!") 525 526 if "Band" in type(self).__name__: 527 i, j = pair 528 if not isinstance(i, Integral) or not isinstance(j, Integral): 529 raise ValueError("Non-integer band pair index!") 530 pair = tuple([int(i), int(j)]) 531 532 elif "Cell" in type(self).__name__: 533 if pair not in {"ii", "vi", "ic", "vc"}: 534 raise ValueError( 535 "Cell role pair not in {'ii', 'vi', 'ic', 'vc'}!" 536 ) 537 538 else: 539 pair = tuple(pair) 540 541 spectrum = type(self).total(energies, values, d_energy=d_energy) 542 rsv_values_new[pair] = spectrum.values 543 544 if "Cell" in type(self).__name__: 545 rsv_values_new_sorted = {} 546 for pair in ["ii", "vi", "ic", "vc"]: 547 if pair in rsv_values_new: 548 rsv_values_new_sorted.update({pair: rsv_values_new[pair]}) 549 rsv_values_new = rsv_values_new_sorted 550 551 super().__init__(energies, rsv_values_new, d_energy=d_energy) 552 553 def plot( 554 self, 555 *, 556 ax: plt.Axes | None = None, 557 stack: bool = False, 558 ) -> plt.Figure: 559 r"""Plot pair resolved energy spectrum. 560 561 Args: 562 ax: Plot axes; if None, new figure and axes are created. 563 stack: If True, a stackplot is generated. 564 565 Returns: 566 Plot figure. 567 568 Raises: 569 TypeError: If `stack` is not a boolean. 570 """ 571 if ax is None: 572 ax = type(self).total.std_axes 573 574 if stack is True: 575 ax.stackplot( 576 self.energies, 577 self.rsv_values.values(), 578 labels=[f"{pair[0]} → {pair[1]}" for pair in self.rsv_values], 579 ) 580 elif stack is False: 581 for pair, values in self.rsv_values.items(): 582 ax.plot(self.energies, values, label=f"{pair[0]} → {pair[1]}") 583 else: 584 raise TypeError("`stack` must be boolean!") 585 586 ax.plot( 587 self.energies, 588 self.total.values, 589 linestyle="--", 590 color="k", 591 label="total", 592 ) 593 ax.legend(loc="upper left", bbox_to_anchor=[1.0, 1.0], frameon=False) 594 return ax.get_figure() 595 596 597class BandPairResolvedEnergySpectrum(PairResolvedEnergySpectrum): 598 r"""Band pair resolved energy spectrum. 599 600 Note: 601 See `madman.analysis.spectrum.BandResolvedEnergySpectrum` for details. 602 """ 603 604 605class CellPairResolvedEnergySpectrum(PairResolvedEnergySpectrum): 606 r"""Cell pair resolved energy spectrum. 607 608 Note: 609 See `madman.analysis.spectrum.CellResolvedEnergySpectrum` for details. 610 """
24class EnergySpectrumMeta(type): 25 r"""Metaclas of EnergySpectrum.""" 26 27 @property 28 def rsv(cls) -> Type: 29 r"""Associated resolved class.""" 30 module = modules[cls.__module__] 31 name = "Resolved" + cls.__name__ 32 return getattr(module, name) 33 34 @property 35 def band_rsv(cls) -> Type: 36 r"""Associated band resolved class.""" 37 module = modules[cls.__module__] 38 name = "BandResolved" + cls.__name__ 39 return getattr(module, name) 40 41 @property 42 def cell_rsv(cls) -> Type: 43 r"""Associated cell resolved class.""" 44 module = modules[cls.__module__] 45 name = "CellResolved" + cls.__name__ 46 return getattr(module, name) 47 48 @property 49 def std_axes(cls) -> plt.Axes: 50 r"""Standard axes to plot energy spectrum.""" 51 _, ax = plt.subplots(tight_layout=True) 52 ax.set_xlabel(r"Energy / $\mathrm{eV}$") 53 ax.set_ylabel(r"Values / $\mathrm{n.a.}$") 54 return ax
Metaclas of EnergySpectrum.
27 @property 28 def rsv(cls) -> Type: 29 r"""Associated resolved class.""" 30 module = modules[cls.__module__] 31 name = "Resolved" + cls.__name__ 32 return getattr(module, name)
Associated resolved class.
34 @property 35 def band_rsv(cls) -> Type: 36 r"""Associated band resolved class.""" 37 module = modules[cls.__module__] 38 name = "BandResolved" + cls.__name__ 39 return getattr(module, name)
Associated band resolved class.
41 @property 42 def cell_rsv(cls) -> Type: 43 r"""Associated cell resolved class.""" 44 module = modules[cls.__module__] 45 name = "CellResolved" + cls.__name__ 46 return getattr(module, name)
Associated cell resolved class.
48 @property 49 def std_axes(cls) -> plt.Axes: 50 r"""Standard axes to plot energy spectrum.""" 51 _, ax = plt.subplots(tight_layout=True) 52 ax.set_xlabel(r"Energy / $\mathrm{eV}$") 53 ax.set_ylabel(r"Values / $\mathrm{n.a.}$") 54 return ax
Standard axes to plot energy spectrum.
Inherited Members
- builtins.type
- type
- mro
57class EnergySpectrum(metaclass=EnergySpectrumMeta): 58 r"""Energy spectrum.""" 59 60 def __init__( 61 self, 62 energies: Sequence[Real, ...], 63 values: Sequence[Real, ...], 64 *, 65 d_energy: Real | None = None, 66 ) -> None: 67 r"""Initialize EnergySpectrum object. 68 69 Args: 70 energies: Spectrum energies [$\mathrm{eV}$]. 71 values: Spectrum values. 72 d_energy: Target energy increment [$\mathrm{eV}$]. 73 74 Raises: 75 ValueError: If there are duplicate energies. 76 """ 77 energies = np.asarray(energies, dtype=float).flatten() 78 values = np.asarray(values, dtype=float).flatten() 79 80 sort_i = np.argsort(energies) 81 energies = energies[sort_i] 82 values = values[sort_i] 83 84 if not np.array_equal(energies, np.unique(energies)): 85 raise ValueError("Duplicate energies!") 86 87 energy_a = np.amin(energies) 88 energy_b = np.amax(energies) 89 if d_energy is None: 90 d_energy = np.diff(energies).min() 91 92 self._energies = gen_regular_grid(energy_a, energy_b, d_energy) 93 self._values = np.interp(self._energies, energies, values) 94 self._integr = np.trapz(self.values, x=self.energies) 95 96 def __add__(self, other: Self) -> Self: 97 r"""Add energy spectrum. 98 99 Args: 100 other: Energy spectrum to add. 101 102 Returns: 103 Sum of `self` and `other`. 104 105 Raises: 106 TypeError: If `self` and `other` have incompatible types. 107 ValueError: If `self` and `other` have incompatible energies. 108 """ 109 if type(self) != type(other): 110 raise TypeError("Incompatible types!") 111 if not np.array_equal(self.energies, other.energies): 112 raise ValueError("Incompatible energies!") 113 return type(self)(self.energies, self.values + other.values) 114 115 @property 116 def energies(self) -> np.ndarray[float]: 117 r"""Spectrum energies [$\mathrm{eV}$].""" 118 return self._energies 119 120 @property 121 def values(self) -> np.ndarray[float]: 122 r"""Spectrum values.""" 123 return self._values 124 125 @property 126 def integr(self) -> float: 127 r"""Spectrum integrated value.""" 128 return self._integr 129 130 def ret_zero(self) -> Self: 131 r"""Return zero energy spectrum.""" 132 return type(self).zero(self.energies) 133 134 def ret_smeared(self, *, sigma: Real) -> Self: 135 r"""Return smeared energy spectrum. 136 137 Args: 138 sigma: Standard deviation for Gaussian kernel. 139 140 Returns: 141 Smeared energy spectrum. 142 """ 143 values = gaussian_filter1d(self.values, sigma) 144 return type(self)(self.energies, values) 145 146 def plot( 147 self, 148 *, 149 ax: plt.Axes | None = None, 150 color: str | None = None, 151 label: str | None = None, 152 ) -> plt.Figure: 153 r"""Plot energy spectrum. 154 155 Args: 156 ax: Plot axes; if None, new figure and axes are created. 157 color: Energy spectrum color. 158 label: Energy spectrum label. 159 160 Returns: 161 Plot figure. 162 """ 163 if ax is None: 164 ax = type(self).std_axes 165 ax.plot(self.energies, self.values, color=color, label=label) 166 return ax.get_figure() 167 168 @classmethod 169 def zero( 170 cls, energies: Sequence[Real, ...], *, d_energy: Real | None = None 171 ) -> np.ndarray[float]: 172 r"""Zero energy spectrum. 173 174 Args: 175 energies: Spectrum energies [$\mathrm{eV}$]. 176 d_energy: Target energy increment [$\mathrm{eV}$]. 177 178 Returns: 179 Zero energy spectrum. 180 """ 181 values = np.zeros_like(energies) 182 return cls(energies, values, d_energy=d_energy) 183 184 @classmethod 185 def piecewise_constant( 186 cls, 187 energy_a: Real, 188 energy_b: Real, 189 d_energy: Real, 190 *, 191 amplitudes: Sequence[Real, ...], 192 low_bounds: Sequence[Real, ...], 193 upp_bounds: Sequence[Real, ...], 194 ) -> Self: 195 r"""Piecewise constant energy spectrum. 196 197 Args: 198 energy_a: Energy minimum [$\mathrm{eV}$]. 199 energy_b: Energy maximum [$\mathrm{eV}$]. 200 d_energy: Target energy increment [$\mathrm{eV}$]. 201 amplitudes: Piece amplitudes. 202 low_bounds: Piece lower bounds [$\mathrm{eV}$]. 203 upp_bounds: Piece upper bounds [$\mathrm{eV}$]. 204 205 Returns: 206 Piecewise constant energy spectrum. 207 """ 208 energies = gen_regular_grid(energy_a, energy_b, d_energy) 209 210 def rect(x, a, b): 211 return np.heaviside(x - a, 0.5) - np.heaviside(x - b, 0.5) 212 213 values = 0.0 214 for amp, lb, ub in zip(amplitudes, low_bounds, upp_bounds): 215 values += amp * rect(energies, lb, ub) 216 return cls(energies, values)
Energy spectrum.
60 def __init__( 61 self, 62 energies: Sequence[Real, ...], 63 values: Sequence[Real, ...], 64 *, 65 d_energy: Real | None = None, 66 ) -> None: 67 r"""Initialize EnergySpectrum object. 68 69 Args: 70 energies: Spectrum energies [$\mathrm{eV}$]. 71 values: Spectrum values. 72 d_energy: Target energy increment [$\mathrm{eV}$]. 73 74 Raises: 75 ValueError: If there are duplicate energies. 76 """ 77 energies = np.asarray(energies, dtype=float).flatten() 78 values = np.asarray(values, dtype=float).flatten() 79 80 sort_i = np.argsort(energies) 81 energies = energies[sort_i] 82 values = values[sort_i] 83 84 if not np.array_equal(energies, np.unique(energies)): 85 raise ValueError("Duplicate energies!") 86 87 energy_a = np.amin(energies) 88 energy_b = np.amax(energies) 89 if d_energy is None: 90 d_energy = np.diff(energies).min() 91 92 self._energies = gen_regular_grid(energy_a, energy_b, d_energy) 93 self._values = np.interp(self._energies, energies, values) 94 self._integr = np.trapz(self.values, x=self.energies)
Initialize EnergySpectrum object.
Arguments:
- energies: Spectrum energies [$\mathrm{eV}$].
- values: Spectrum values.
- d_energy: Target energy increment [$\mathrm{eV}$].
Raises:
- ValueError: If there are duplicate energies.
115 @property 116 def energies(self) -> np.ndarray[float]: 117 r"""Spectrum energies [$\mathrm{eV}$].""" 118 return self._energies
Spectrum energies [$\mathrm{eV}$].
120 @property 121 def values(self) -> np.ndarray[float]: 122 r"""Spectrum values.""" 123 return self._values
Spectrum values.
125 @property 126 def integr(self) -> float: 127 r"""Spectrum integrated value.""" 128 return self._integr
Spectrum integrated value.
130 def ret_zero(self) -> Self: 131 r"""Return zero energy spectrum.""" 132 return type(self).zero(self.energies)
Return zero energy spectrum.
134 def ret_smeared(self, *, sigma: Real) -> Self: 135 r"""Return smeared energy spectrum. 136 137 Args: 138 sigma: Standard deviation for Gaussian kernel. 139 140 Returns: 141 Smeared energy spectrum. 142 """ 143 values = gaussian_filter1d(self.values, sigma) 144 return type(self)(self.energies, values)
Return smeared energy spectrum.
Arguments:
- sigma: Standard deviation for Gaussian kernel.
Returns:
Smeared energy spectrum.
146 def plot( 147 self, 148 *, 149 ax: plt.Axes | None = None, 150 color: str | None = None, 151 label: str | None = None, 152 ) -> plt.Figure: 153 r"""Plot energy spectrum. 154 155 Args: 156 ax: Plot axes; if None, new figure and axes are created. 157 color: Energy spectrum color. 158 label: Energy spectrum label. 159 160 Returns: 161 Plot figure. 162 """ 163 if ax is None: 164 ax = type(self).std_axes 165 ax.plot(self.energies, self.values, color=color, label=label) 166 return ax.get_figure()
Plot energy spectrum.
Arguments:
- ax: Plot axes; if None, new figure and axes are created.
- color: Energy spectrum color.
- label: Energy spectrum label.
Returns:
Plot figure.
168 @classmethod 169 def zero( 170 cls, energies: Sequence[Real, ...], *, d_energy: Real | None = None 171 ) -> np.ndarray[float]: 172 r"""Zero energy spectrum. 173 174 Args: 175 energies: Spectrum energies [$\mathrm{eV}$]. 176 d_energy: Target energy increment [$\mathrm{eV}$]. 177 178 Returns: 179 Zero energy spectrum. 180 """ 181 values = np.zeros_like(energies) 182 return cls(energies, values, d_energy=d_energy)
Zero energy spectrum.
Arguments:
- energies: Spectrum energies [$\mathrm{eV}$].
- d_energy: Target energy increment [$\mathrm{eV}$].
Returns:
Zero energy spectrum.
184 @classmethod 185 def piecewise_constant( 186 cls, 187 energy_a: Real, 188 energy_b: Real, 189 d_energy: Real, 190 *, 191 amplitudes: Sequence[Real, ...], 192 low_bounds: Sequence[Real, ...], 193 upp_bounds: Sequence[Real, ...], 194 ) -> Self: 195 r"""Piecewise constant energy spectrum. 196 197 Args: 198 energy_a: Energy minimum [$\mathrm{eV}$]. 199 energy_b: Energy maximum [$\mathrm{eV}$]. 200 d_energy: Target energy increment [$\mathrm{eV}$]. 201 amplitudes: Piece amplitudes. 202 low_bounds: Piece lower bounds [$\mathrm{eV}$]. 203 upp_bounds: Piece upper bounds [$\mathrm{eV}$]. 204 205 Returns: 206 Piecewise constant energy spectrum. 207 """ 208 energies = gen_regular_grid(energy_a, energy_b, d_energy) 209 210 def rect(x, a, b): 211 return np.heaviside(x - a, 0.5) - np.heaviside(x - b, 0.5) 212 213 values = 0.0 214 for amp, lb, ub in zip(amplitudes, low_bounds, upp_bounds): 215 values += amp * rect(energies, lb, ub) 216 return cls(energies, values)
Piecewise constant energy spectrum.
Arguments:
- energy_a: Energy minimum [$\mathrm{eV}$].
- energy_b: Energy maximum [$\mathrm{eV}$].
- d_energy: Target energy increment [$\mathrm{eV}$].
- amplitudes: Piece amplitudes.
- low_bounds: Piece lower bounds [$\mathrm{eV}$].
- upp_bounds: Piece upper bounds [$\mathrm{eV}$].
Returns:
Piecewise constant energy spectrum.
219class ResolvedEnergySpectrumMeta(type): 220 r"""Metaclass of ResolvedEnergySpectrum.""" 221 222 @property 223 def total(cls) -> Type: 224 r"""Total energy spectrum class.""" 225 module = modules[cls.__module__] 226 name = cls.__name__.split("Resolved")[1] 227 return getattr(module, name)
Metaclass of ResolvedEnergySpectrum.
222 @property 223 def total(cls) -> Type: 224 r"""Total energy spectrum class.""" 225 module = modules[cls.__module__] 226 name = cls.__name__.split("Resolved")[1] 227 return getattr(module, name)
Total energy spectrum class.
Inherited Members
- builtins.type
- type
- mro
230class ResolvedEnergySpectrum(metaclass=ResolvedEnergySpectrumMeta): 231 r"""Resolved energy spectrum.""" 232 233 def __init__( 234 self, 235 energies: Sequence[Real, ...], 236 rsv_values: Mapping[Any, Sequence[Real, ...]], 237 *, 238 d_energy: Real | None = None, 239 ) -> None: 240 r"""Initialize ResolvedEnergySpectrum object. 241 242 Args: 243 energies: Spectrum energies [$\mathrm{eV}$]. 244 rsv_values: Mapping of feature into spectrum values. 245 d_energy: Target energy increment [$\mathrm{eV}$]. 246 247 Raises: 248 ValueError: If band index is non-integer. 249 ValueError: If cell role is not in {'v', 'i', 'c'}. 250 """ 251 self._rsv_values = {} 252 self._rsv_integr = {} 253 self._total = type(self).total.zero(energies, d_energy=d_energy) 254 255 for feat, values in rsv_values.items(): 256 if not isinstance(self, PairResolvedEnergySpectrum): 257 258 if "Band" in type(self).__name__: 259 if not isinstance(feat, Integral): 260 raise ValueError("Non-integer band index!") 261 feat = int(feat) 262 263 if "Cell" in type(self).__name__: 264 if not feat in {"v", "i", "c"}: 265 raise ValueError("Cell role not in {'v', 'i', 'c'}") 266 267 spectrum = type(self).total(energies, values, d_energy=d_energy) 268 self._rsv_values[feat] = spectrum.values 269 self._rsv_integr[feat] = np.trapz( 270 spectrum.values, x=spectrum.energies 271 ) 272 self._total += spectrum 273 274 if ( 275 not isinstance(self, PairResolvedEnergySpectrum) 276 and "Cell" in type(self).__name__ 277 ): 278 rsv_values_sorted = {} 279 for feat in ["v", "i", "c"]: 280 if feat in self._rsv_values: 281 rsv_values_sorted.update({feat: self._rsv_values[feat]}) 282 self._rsv_values = rsv_values_sorted 283 284 self._energies = spectrum.energies 285 286 self._rsv_sp_weight = {} 287 for feat, values in rsv_values.items(): 288 sp_weight = self._rsv_integr[feat] / self._total.integr 289 self._rsv_sp_weight[feat] = sp_weight 290 291 self._rsv_sp_select = {} 292 for feat, values in rsv_values.items(): 293 sp_select = 1.0 - np.trapz( 294 values * (self._total.values - values), x=self._energies 295 ) / np.sqrt( 296 np.trapz(values**2, x=self._energies) 297 * np.trapz((self._total.values - values) ** 2, x=self._energies) 298 ) 299 self._rsv_sp_select[feat] = sp_select 300 301 def __add__(self, other: Self) -> Self: 302 r"""Add resolved energy spectrum. 303 304 Args: 305 other: Resolved energy spectrum to add. 306 307 Returns: 308 Sum of `self` and `other`. 309 310 Raises: 311 TypeError: If `self` and `other` have incompatible types. 312 ValueError: If `self` and `other` have incompatible energies. 313 ValueError: If `self` and `other` have incompatible features. 314 """ 315 if type(self) != type(other): 316 raise TypeError("Incompatible types!") 317 if not np.array_equal(self.energies, other.energies): 318 raise ValueError("Incompatible energies!") 319 if self.rsv_values.keys() != other.rsv_values.keys(): 320 raise ValueError("Incompatible features!") 321 rsv_values = { 322 feat: values + other.rsv_values[feat] 323 for feat, values in self.rsv_values.items() 324 } 325 return type(self)(self.energies, rsv_values) 326 327 @property 328 def energies(self) -> np.ndarray[float]: 329 r"""Spectrum energies [$\mathrm{eV}$].""" 330 return self._energies 331 332 @property 333 def rsv_values(self) -> dict[Any, np.ndarray[float]]: 334 r"""Resolved spectrum values.""" 335 return self._rsv_values 336 337 @property 338 def rsv_integr(self) -> dict[Any, float]: 339 r"""Resolved spectrum integrated values.""" 340 return self._rsv_integr 341 342 @property 343 def rsv_sp_weight(self) -> dict[Any, float]: 344 r"""Resolved spectral weight. 345 346 Note: 347 The spectral weight of feature $x$ is defined as: 348 349 $$ 350 \frac{\int \mathrm{d}(\hbar \, \omega) \, \Sigma_{x}(\hbar \, \omega)}{\int \mathrm{d}(\hbar \, \omega) \, \Sigma_{\text{tot}}(\hbar \, \omega)} 351 $$ 352 353 where $\hbar \, \omega$ is the photon energy, $\Sigma_{x}$ is the 354 partial spectrum og feature $x$, and $\Sigma_{\text{tot}}$ is the 355 total spectrum. 356 """ 357 return self._rsv_sp_weight 358 359 @property 360 def rsv_sp_select(self) -> dict[Any, float]: 361 r"""Resolved spectral selectivity. 362 363 Note: 364 The spectral selectivity of feature $x$ is defined as: 365 366 $$ 367 1 - \frac{\int \mathrm{d}(\hbar \, \omega) \, \Sigma_{x}(\hbar \, \omega) \, \left[ \Sigma_{\text{tot}}(\hbar \, \omega) - \Sigma_{x}(\hbar \, \omega) \right]}{\sqrt{\int \mathrm{d}(\hbar \, \omega) \, \Sigma_{x}^{2}(\hbar \, \omega) \, \int \mathrm{d}(\hbar \, \omega) \, \left[ \Sigma_{\text{tot}}(\hbar \, \omega) - \Sigma_{x}(\hbar \, \omega)\right]^{2}}} 368 $$ 369 370 where $\hbar \, \omega$ is the photon energy, $\Sigma_{x}$ is the 371 partial spectrum og feature $x$, and $\Sigma_{\text{tot}}$ is the total 372 spectrum. 373 """ 374 return self._rsv_sp_select 375 376 @property 377 def total(self) -> EnergySpectrum: 378 r"""Total energy spectrum.""" 379 return self._total 380 381 def ret_zero(self) -> Self: 382 r"""Return zero resolved energy spectrum.""" 383 return type(self).zero(self.energies, self.rsv_values) 384 385 def ret_smeared(self, *, sigma: Real) -> Self: 386 r"""Return smeared resolved energy spectrum. 387 388 Args: 389 sigma: Standard deviation for Gaussian kernel. 390 391 Returns: 392 Smeared resolved energy spectrum. 393 """ 394 rsv_values = { 395 feat: gaussian_filter1d(values, sigma) 396 for feat, values in self.rsv_values.items() 397 } 398 return type(self)(self.energies, rsv_values) 399 400 def plot( 401 self, 402 *, 403 ax: plt.Axes | None = None, 404 stack: bool = False, 405 ) -> plt.Figure: 406 r"""Plot resolved energy spectrum. 407 408 Args: 409 ax: Plot axes; if None, new figure and axes are created. 410 stack: If True, a stackplot is generated. 411 412 Returns: 413 Plot figure. 414 415 Raises: 416 TypeError: If `stack` is not boolean. 417 """ 418 if ax is None: 419 ax = type(self).total.std_axes 420 421 if stack is True: 422 ax.stackplot( 423 self.energies, 424 self.rsv_values.values(), 425 labels=self.rsv_values.keys(), 426 ) 427 elif stack is False: 428 for feat, values in self.rsv_values.items(): 429 ax.plot(self.energies, values, label=feat) 430 else: 431 raise TypeError("`stack` must be boolean!") 432 433 ax.plot( 434 self.energies, 435 self.total.values, 436 linestyle="--", 437 color="k", 438 label="total", 439 ) 440 ax.legend(loc="upper left", bbox_to_anchor=[1.0, 1.0], frameon=False) 441 return ax.get_figure() 442 443 @classmethod 444 def zero( 445 cls, 446 energies: Sequence[Real, ...], 447 features: Sequence[Any, ...], 448 *, 449 d_energy: Real | None = None, 450 ) -> Self: 451 r"""Zero resolved energy spectrum. 452 453 Args: 454 energies: Resolved spectrum energies [$\mathrm{eV}$]. 455 features: Resolved spectrum features. 456 d_energy: Target energy increment [$\mathrm{eV}$]. 457 458 Returns: 459 Zero resolved energy spectrum. 460 """ 461 rsv_values = {feat: np.zeros_like(energies) for feat in features} 462 return cls(energies, rsv_values, d_energy=d_energy)
Resolved energy spectrum.
233 def __init__( 234 self, 235 energies: Sequence[Real, ...], 236 rsv_values: Mapping[Any, Sequence[Real, ...]], 237 *, 238 d_energy: Real | None = None, 239 ) -> None: 240 r"""Initialize ResolvedEnergySpectrum object. 241 242 Args: 243 energies: Spectrum energies [$\mathrm{eV}$]. 244 rsv_values: Mapping of feature into spectrum values. 245 d_energy: Target energy increment [$\mathrm{eV}$]. 246 247 Raises: 248 ValueError: If band index is non-integer. 249 ValueError: If cell role is not in {'v', 'i', 'c'}. 250 """ 251 self._rsv_values = {} 252 self._rsv_integr = {} 253 self._total = type(self).total.zero(energies, d_energy=d_energy) 254 255 for feat, values in rsv_values.items(): 256 if not isinstance(self, PairResolvedEnergySpectrum): 257 258 if "Band" in type(self).__name__: 259 if not isinstance(feat, Integral): 260 raise ValueError("Non-integer band index!") 261 feat = int(feat) 262 263 if "Cell" in type(self).__name__: 264 if not feat in {"v", "i", "c"}: 265 raise ValueError("Cell role not in {'v', 'i', 'c'}") 266 267 spectrum = type(self).total(energies, values, d_energy=d_energy) 268 self._rsv_values[feat] = spectrum.values 269 self._rsv_integr[feat] = np.trapz( 270 spectrum.values, x=spectrum.energies 271 ) 272 self._total += spectrum 273 274 if ( 275 not isinstance(self, PairResolvedEnergySpectrum) 276 and "Cell" in type(self).__name__ 277 ): 278 rsv_values_sorted = {} 279 for feat in ["v", "i", "c"]: 280 if feat in self._rsv_values: 281 rsv_values_sorted.update({feat: self._rsv_values[feat]}) 282 self._rsv_values = rsv_values_sorted 283 284 self._energies = spectrum.energies 285 286 self._rsv_sp_weight = {} 287 for feat, values in rsv_values.items(): 288 sp_weight = self._rsv_integr[feat] / self._total.integr 289 self._rsv_sp_weight[feat] = sp_weight 290 291 self._rsv_sp_select = {} 292 for feat, values in rsv_values.items(): 293 sp_select = 1.0 - np.trapz( 294 values * (self._total.values - values), x=self._energies 295 ) / np.sqrt( 296 np.trapz(values**2, x=self._energies) 297 * np.trapz((self._total.values - values) ** 2, x=self._energies) 298 ) 299 self._rsv_sp_select[feat] = sp_select
Initialize ResolvedEnergySpectrum object.
Arguments:
- energies: Spectrum energies [$\mathrm{eV}$].
- rsv_values: Mapping of feature into spectrum values.
- d_energy: Target energy increment [$\mathrm{eV}$].
Raises:
- ValueError: If band index is non-integer.
- ValueError: If cell role is not in {'v', 'i', 'c'}.
327 @property 328 def energies(self) -> np.ndarray[float]: 329 r"""Spectrum energies [$\mathrm{eV}$].""" 330 return self._energies
Spectrum energies [$\mathrm{eV}$].
332 @property 333 def rsv_values(self) -> dict[Any, np.ndarray[float]]: 334 r"""Resolved spectrum values.""" 335 return self._rsv_values
Resolved spectrum values.
337 @property 338 def rsv_integr(self) -> dict[Any, float]: 339 r"""Resolved spectrum integrated values.""" 340 return self._rsv_integr
Resolved spectrum integrated values.
342 @property 343 def rsv_sp_weight(self) -> dict[Any, float]: 344 r"""Resolved spectral weight. 345 346 Note: 347 The spectral weight of feature $x$ is defined as: 348 349 $$ 350 \frac{\int \mathrm{d}(\hbar \, \omega) \, \Sigma_{x}(\hbar \, \omega)}{\int \mathrm{d}(\hbar \, \omega) \, \Sigma_{\text{tot}}(\hbar \, \omega)} 351 $$ 352 353 where $\hbar \, \omega$ is the photon energy, $\Sigma_{x}$ is the 354 partial spectrum og feature $x$, and $\Sigma_{\text{tot}}$ is the 355 total spectrum. 356 """ 357 return self._rsv_sp_weight
Resolved spectral weight.
Note:
The spectral weight of feature $x$ is defined as:
$$ \frac{\int \mathrm{d}(\hbar \, \omega) \, \Sigma_{x}(\hbar \, \omega)}{\int \mathrm{d}(\hbar \, \omega) \, \Sigma_{\text{tot}}(\hbar \, \omega)} $$
where $\hbar \, \omega$ is the photon energy, $\Sigma_{x}$ is the partial spectrum og feature $x$, and $\Sigma_{\text{tot}}$ is the total spectrum.
359 @property 360 def rsv_sp_select(self) -> dict[Any, float]: 361 r"""Resolved spectral selectivity. 362 363 Note: 364 The spectral selectivity of feature $x$ is defined as: 365 366 $$ 367 1 - \frac{\int \mathrm{d}(\hbar \, \omega) \, \Sigma_{x}(\hbar \, \omega) \, \left[ \Sigma_{\text{tot}}(\hbar \, \omega) - \Sigma_{x}(\hbar \, \omega) \right]}{\sqrt{\int \mathrm{d}(\hbar \, \omega) \, \Sigma_{x}^{2}(\hbar \, \omega) \, \int \mathrm{d}(\hbar \, \omega) \, \left[ \Sigma_{\text{tot}}(\hbar \, \omega) - \Sigma_{x}(\hbar \, \omega)\right]^{2}}} 368 $$ 369 370 where $\hbar \, \omega$ is the photon energy, $\Sigma_{x}$ is the 371 partial spectrum og feature $x$, and $\Sigma_{\text{tot}}$ is the total 372 spectrum. 373 """ 374 return self._rsv_sp_select
Resolved spectral selectivity.
Note:
The spectral selectivity of feature $x$ is defined as:
$$ 1 - \frac{\int \mathrm{d}(\hbar \, \omega) \, \Sigma_{x}(\hbar \, \omega) \, \left[ \Sigma_{\text{tot}}(\hbar \, \omega) - \Sigma_{x}(\hbar \, \omega) \right]}{\sqrt{\int \mathrm{d}(\hbar \, \omega) \, \Sigma_{x}^{2}(\hbar \, \omega) \, \int \mathrm{d}(\hbar \, \omega) \, \left[ \Sigma_{\text{tot}}(\hbar \, \omega) - \Sigma_{x}(\hbar \, \omega)\right]^{2}}} $$
where $\hbar \, \omega$ is the photon energy, $\Sigma_{x}$ is the partial spectrum og feature $x$, and $\Sigma_{\text{tot}}$ is the total spectrum.
376 @property 377 def total(self) -> EnergySpectrum: 378 r"""Total energy spectrum.""" 379 return self._total
Total energy spectrum.
381 def ret_zero(self) -> Self: 382 r"""Return zero resolved energy spectrum.""" 383 return type(self).zero(self.energies, self.rsv_values)
Return zero resolved energy spectrum.
385 def ret_smeared(self, *, sigma: Real) -> Self: 386 r"""Return smeared resolved energy spectrum. 387 388 Args: 389 sigma: Standard deviation for Gaussian kernel. 390 391 Returns: 392 Smeared resolved energy spectrum. 393 """ 394 rsv_values = { 395 feat: gaussian_filter1d(values, sigma) 396 for feat, values in self.rsv_values.items() 397 } 398 return type(self)(self.energies, rsv_values)
Return smeared resolved energy spectrum.
Arguments:
- sigma: Standard deviation for Gaussian kernel.
Returns:
Smeared resolved energy spectrum.
400 def plot( 401 self, 402 *, 403 ax: plt.Axes | None = None, 404 stack: bool = False, 405 ) -> plt.Figure: 406 r"""Plot resolved energy spectrum. 407 408 Args: 409 ax: Plot axes; if None, new figure and axes are created. 410 stack: If True, a stackplot is generated. 411 412 Returns: 413 Plot figure. 414 415 Raises: 416 TypeError: If `stack` is not boolean. 417 """ 418 if ax is None: 419 ax = type(self).total.std_axes 420 421 if stack is True: 422 ax.stackplot( 423 self.energies, 424 self.rsv_values.values(), 425 labels=self.rsv_values.keys(), 426 ) 427 elif stack is False: 428 for feat, values in self.rsv_values.items(): 429 ax.plot(self.energies, values, label=feat) 430 else: 431 raise TypeError("`stack` must be boolean!") 432 433 ax.plot( 434 self.energies, 435 self.total.values, 436 linestyle="--", 437 color="k", 438 label="total", 439 ) 440 ax.legend(loc="upper left", bbox_to_anchor=[1.0, 1.0], frameon=False) 441 return ax.get_figure()
Plot resolved energy spectrum.
Arguments:
- ax: Plot axes; if None, new figure and axes are created.
- stack: If True, a stackplot is generated.
Returns:
Plot figure.
Raises:
- TypeError: If
stackis not boolean.
443 @classmethod 444 def zero( 445 cls, 446 energies: Sequence[Real, ...], 447 features: Sequence[Any, ...], 448 *, 449 d_energy: Real | None = None, 450 ) -> Self: 451 r"""Zero resolved energy spectrum. 452 453 Args: 454 energies: Resolved spectrum energies [$\mathrm{eV}$]. 455 features: Resolved spectrum features. 456 d_energy: Target energy increment [$\mathrm{eV}$]. 457 458 Returns: 459 Zero resolved energy spectrum. 460 """ 461 rsv_values = {feat: np.zeros_like(energies) for feat in features} 462 return cls(energies, rsv_values, d_energy=d_energy)
Zero resolved energy spectrum.
Arguments:
- energies: Resolved spectrum energies [$\mathrm{eV}$].
- features: Resolved spectrum features.
- d_energy: Target energy increment [$\mathrm{eV}$].
Returns:
Zero resolved energy spectrum.
465class BandResolvedEnergySpectrum(ResolvedEnergySpectrum): 466 r"""Band resolved energy spectrum. 467 468 Note: 469 'Band' is hereby intended as a range of electron energies with no zero 470 electron density of states values such that any larger range contains 471 zero electron density of states values. 472 To distinguish, a band of a dispersion relation (e.g. the electronic 473 band structure of a crystalline solid) is hereby referred as 'branch'. 474 """
Band resolved energy spectrum.
Note:
'Band' is hereby intended as a range of electron energies with no zero electron density of states values such that any larger range contains zero electron density of states values. To distinguish, a band of a dispersion relation (e.g. the electronic band structure of a crystalline solid) is hereby referred as 'branch'.
477class CellResolvedEnergySpectrum(ResolvedEnergySpectrum): 478 r"""Cell resolved energy spectrum. 479 480 Note: 481 The energy spectrum is resolved according to the components role in a 482 solar cells. 483 The following convention is adopted: 484 485 - 'v': valence bands; 486 - 'i': intermediate bands; 487 - 'c': conduction bands. 488 489 See 490 [Shockley & Queisser (1961)](https://dx.doi.org/10.1063%2F1.1736034) 491 and 492 [Levy & Honsberg (2008)](http://dx.doi.org/10.1103%2FPhysRevB.78.165122) 493 for details. 494 """
Cell resolved energy spectrum.
Note:
The energy spectrum is resolved according to the components role in a solar cells. The following convention is adopted:
- 'v': valence bands;
- 'i': intermediate bands;
- 'c': conduction bands.
See Shockley & Queisser (1961) and Levy & Honsberg (2008) for details.
497class PairResolvedEnergySpectrum(ResolvedEnergySpectrum): 498 r"""Pair resolved energy spectrum.""" 499 500 def __init__( 501 self, 502 energies: Sequence[Real, ...], 503 rsv_values: Mapping[Sequence[Any, Any], Sequence[Real, ...]], 504 *, 505 d_energy: Real | None = None, 506 ) -> None: 507 r"""Initialize ResolvedEnergySpectrum object. 508 509 Args: 510 energies: Spectrum energies [$\mathrm{eV}$]. 511 rsv_values: Mapping of feature pair into spectrum values. 512 d_energy: Target energy increment [$\mathrm{eV}$]. 513 514 Raises: 515 TypeError: If a feature pair is not a sequence. 516 ValueError: If a feature pair is not a pair. 517 ValueError: If a band index pair is not a pair of integers. 518 ValueError: If a cell role pair is not in {'ii', 'vi', 'ic', 'vc'}. 519 """ 520 rsv_values_new = {} 521 for pair, values in rsv_values.items(): 522 if not isinstance(pair, Sequence): 523 raise TypeError("Not a sequence!") 524 if not len(pair) == 2: 525 raise ValueError("Not a pair!") 526 527 if "Band" in type(self).__name__: 528 i, j = pair 529 if not isinstance(i, Integral) or not isinstance(j, Integral): 530 raise ValueError("Non-integer band pair index!") 531 pair = tuple([int(i), int(j)]) 532 533 elif "Cell" in type(self).__name__: 534 if pair not in {"ii", "vi", "ic", "vc"}: 535 raise ValueError( 536 "Cell role pair not in {'ii', 'vi', 'ic', 'vc'}!" 537 ) 538 539 else: 540 pair = tuple(pair) 541 542 spectrum = type(self).total(energies, values, d_energy=d_energy) 543 rsv_values_new[pair] = spectrum.values 544 545 if "Cell" in type(self).__name__: 546 rsv_values_new_sorted = {} 547 for pair in ["ii", "vi", "ic", "vc"]: 548 if pair in rsv_values_new: 549 rsv_values_new_sorted.update({pair: rsv_values_new[pair]}) 550 rsv_values_new = rsv_values_new_sorted 551 552 super().__init__(energies, rsv_values_new, d_energy=d_energy) 553 554 def plot( 555 self, 556 *, 557 ax: plt.Axes | None = None, 558 stack: bool = False, 559 ) -> plt.Figure: 560 r"""Plot pair resolved energy spectrum. 561 562 Args: 563 ax: Plot axes; if None, new figure and axes are created. 564 stack: If True, a stackplot is generated. 565 566 Returns: 567 Plot figure. 568 569 Raises: 570 TypeError: If `stack` is not a boolean. 571 """ 572 if ax is None: 573 ax = type(self).total.std_axes 574 575 if stack is True: 576 ax.stackplot( 577 self.energies, 578 self.rsv_values.values(), 579 labels=[f"{pair[0]} → {pair[1]}" for pair in self.rsv_values], 580 ) 581 elif stack is False: 582 for pair, values in self.rsv_values.items(): 583 ax.plot(self.energies, values, label=f"{pair[0]} → {pair[1]}") 584 else: 585 raise TypeError("`stack` must be boolean!") 586 587 ax.plot( 588 self.energies, 589 self.total.values, 590 linestyle="--", 591 color="k", 592 label="total", 593 ) 594 ax.legend(loc="upper left", bbox_to_anchor=[1.0, 1.0], frameon=False) 595 return ax.get_figure()
Pair resolved energy spectrum.
500 def __init__( 501 self, 502 energies: Sequence[Real, ...], 503 rsv_values: Mapping[Sequence[Any, Any], Sequence[Real, ...]], 504 *, 505 d_energy: Real | None = None, 506 ) -> None: 507 r"""Initialize ResolvedEnergySpectrum object. 508 509 Args: 510 energies: Spectrum energies [$\mathrm{eV}$]. 511 rsv_values: Mapping of feature pair into spectrum values. 512 d_energy: Target energy increment [$\mathrm{eV}$]. 513 514 Raises: 515 TypeError: If a feature pair is not a sequence. 516 ValueError: If a feature pair is not a pair. 517 ValueError: If a band index pair is not a pair of integers. 518 ValueError: If a cell role pair is not in {'ii', 'vi', 'ic', 'vc'}. 519 """ 520 rsv_values_new = {} 521 for pair, values in rsv_values.items(): 522 if not isinstance(pair, Sequence): 523 raise TypeError("Not a sequence!") 524 if not len(pair) == 2: 525 raise ValueError("Not a pair!") 526 527 if "Band" in type(self).__name__: 528 i, j = pair 529 if not isinstance(i, Integral) or not isinstance(j, Integral): 530 raise ValueError("Non-integer band pair index!") 531 pair = tuple([int(i), int(j)]) 532 533 elif "Cell" in type(self).__name__: 534 if pair not in {"ii", "vi", "ic", "vc"}: 535 raise ValueError( 536 "Cell role pair not in {'ii', 'vi', 'ic', 'vc'}!" 537 ) 538 539 else: 540 pair = tuple(pair) 541 542 spectrum = type(self).total(energies, values, d_energy=d_energy) 543 rsv_values_new[pair] = spectrum.values 544 545 if "Cell" in type(self).__name__: 546 rsv_values_new_sorted = {} 547 for pair in ["ii", "vi", "ic", "vc"]: 548 if pair in rsv_values_new: 549 rsv_values_new_sorted.update({pair: rsv_values_new[pair]}) 550 rsv_values_new = rsv_values_new_sorted 551 552 super().__init__(energies, rsv_values_new, d_energy=d_energy)
Initialize ResolvedEnergySpectrum object.
Arguments:
- energies: Spectrum energies [$\mathrm{eV}$].
- rsv_values: Mapping of feature pair into spectrum values.
- d_energy: Target energy increment [$\mathrm{eV}$].
Raises:
- TypeError: If a feature pair is not a sequence.
- ValueError: If a feature pair is not a pair.
- ValueError: If a band index pair is not a pair of integers.
- ValueError: If a cell role pair is not in {'ii', 'vi', 'ic', 'vc'}.
554 def plot( 555 self, 556 *, 557 ax: plt.Axes | None = None, 558 stack: bool = False, 559 ) -> plt.Figure: 560 r"""Plot pair resolved energy spectrum. 561 562 Args: 563 ax: Plot axes; if None, new figure and axes are created. 564 stack: If True, a stackplot is generated. 565 566 Returns: 567 Plot figure. 568 569 Raises: 570 TypeError: If `stack` is not a boolean. 571 """ 572 if ax is None: 573 ax = type(self).total.std_axes 574 575 if stack is True: 576 ax.stackplot( 577 self.energies, 578 self.rsv_values.values(), 579 labels=[f"{pair[0]} → {pair[1]}" for pair in self.rsv_values], 580 ) 581 elif stack is False: 582 for pair, values in self.rsv_values.items(): 583 ax.plot(self.energies, values, label=f"{pair[0]} → {pair[1]}") 584 else: 585 raise TypeError("`stack` must be boolean!") 586 587 ax.plot( 588 self.energies, 589 self.total.values, 590 linestyle="--", 591 color="k", 592 label="total", 593 ) 594 ax.legend(loc="upper left", bbox_to_anchor=[1.0, 1.0], frameon=False) 595 return ax.get_figure()
Plot pair resolved energy spectrum.
Arguments:
- ax: Plot axes; if None, new figure and axes are created.
- stack: If True, a stackplot is generated.
Returns:
Plot figure.
Raises:
- TypeError: If
stackis not a boolean.