xarpes 0.3.2__py3-none-any.whl → 0.3.4__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 CHANGED
@@ -1,6 +1,6 @@
1
- __version__ = '0.3.2'
1
+ __version__ = '0.3.4'
2
2
 
3
3
  from .spectral import *
4
4
  from .distributions import *
5
5
  from .functions import *
6
- from .plotting import *
6
+ from .plotting import *
xarpes/functions.py CHANGED
@@ -254,65 +254,119 @@ def download_examples():
254
254
  import shutil
255
255
  import io
256
256
  import jupytext
257
+ import tempfile
258
+ import re
257
259
 
258
- # Main xARPES repo (examples now live in /examples here)
259
- repo_url = 'https://github.com/xARPES/xARPES'
260
- output_dir = '.' # Directory from which the function is called
260
+ # Main xARPES repo (examples live under /examples there)
261
+ repo_url = "https://github.com/xARPES/xARPES"
262
+ output_dir = "." # Directory from which the function is called
261
263
 
262
264
  # Target 'examples' directory in the user's current location
263
- final_examples_path = os.path.join(output_dir, 'examples')
265
+ final_examples_path = os.path.join(output_dir, "examples")
264
266
  if os.path.exists(final_examples_path):
265
267
  print("Warning: 'examples' folder already exists. "
266
268
  "No download will be performed.")
267
269
  return 1 # Exit the function if 'examples' directory exists
268
270
 
269
- # Proceed with download if 'examples' directory does not exist
270
- repo_parts = repo_url.replace('https://github.com/', '').rstrip('/')
271
- zip_url = f'https://github.com/{repo_parts}/archive/refs/heads/main.zip'
272
-
273
- # Make the HTTP request to download the zip file
274
- print(f'Downloading {zip_url}')
275
- response = requests.get(zip_url)
276
- if response.status_code == 200:
277
- zip_file_bytes = io.BytesIO(response.content)
278
-
279
- with zipfile.ZipFile(zip_file_bytes, 'r') as zip_ref:
280
- zip_ref.extractall(output_dir)
281
-
282
- # Path to the extracted main folder (e.g. xARPES-main)
283
- main_folder_path = os.path.join(
284
- output_dir,
285
- repo_parts.split('/')[-1] + '-main'
286
- )
287
- examples_path = os.path.join(main_folder_path, 'examples')
288
-
289
- # Move the 'examples' directory to the target location
290
- if os.path.exists(examples_path):
291
- shutil.move(examples_path, final_examples_path)
292
- print(f"'examples' subdirectory moved to {final_examples_path}")
293
-
294
- # Convert all .Rmd files in the examples directory to .ipynb
295
- # and delete the .Rmd files
296
- for dirpath, dirnames, filenames in os.walk(final_examples_path):
297
- for filename in filenames:
298
- if filename.endswith('.Rmd'):
299
- full_path = os.path.join(dirpath, filename)
300
- jupytext.write(
301
- jupytext.read(full_path),
302
- full_path.replace('.Rmd', '.ipynb')
303
- )
304
- os.remove(full_path) # Deletes .Rmd file afterwards
305
- print(f'Converted and deleted {full_path}')
306
-
307
- # Remove the rest of the extracted content
308
- shutil.rmtree(main_folder_path)
309
- print(f'Cleaned up temporary files in {main_folder_path}')
310
- return 0
271
+ # --- Determine version from xarpes.__init__.__version__ -----------------
272
+ try:
273
+ # Import inside the function, avoiding circular imports at import time
274
+ import xarpes as _xarpes
275
+ raw_version = getattr(_xarpes, "__version__", None)
276
+ except Exception as exc:
277
+ print(f"Warning: could not import xarpes to determine version: {exc}")
278
+ raw_version = None
279
+
280
+ tag_version = None
281
+ if raw_version is not None:
282
+ raw_version = str(raw_version)
283
+ # Strip dev/local suffixes so that '0.3.3.dev1' or '0.3.3+0.gHASH'
284
+ # maps to the tag 'v0.3.3'. If you use plain '0.3.3' already, this is
285
+ # a no-op.
286
+ m = re.match(r"(\d+\.\d+\.\d+)", raw_version)
287
+ if m:
288
+ tag_version = m.group(1)
289
+ else:
290
+ tag_version = raw_version
291
+
292
+ print(f"Determined xARPES version from __init__: {raw_version} "
293
+ f"(using tag version '{tag_version}').")
311
294
  else:
312
- print('Failed to download the repository. Status code: '
313
- f'{response.status_code}')
295
+ print("Warning: xarpes.__version__ is not defined; will skip "
296
+ "tag-based download and try the main branch only.")
297
+
298
+ # --- Build refs and use for–else to try them in order -------------------
299
+ repo_parts = repo_url.replace("https://github.com/", "").rstrip("/")
300
+
301
+ refs_to_try = []
302
+ if tag_version is not None:
303
+ refs_to_try.append(f"tags/v{tag_version}") # version-matched examples
304
+ refs_to_try.append("heads/main") # fallback: latest examples
305
+
306
+ response = None
307
+ for ref in refs_to_try:
308
+ zip_url = f"https://github.com/{repo_parts}/archive/refs/{ref}.zip"
309
+ print(f"Attempting to download examples from '{ref}':\n {zip_url}")
310
+ response = requests.get(zip_url)
311
+
312
+ if response.status_code == 200:
313
+ if ref.startswith("tags/"):
314
+ print(f"Successfully downloaded examples from tagged release "
315
+ f"'v{tag_version}'.")
316
+ else:
317
+ print("Tagged release not available; using latest examples "
318
+ "from the 'main' branch instead.")
319
+ break
320
+ else:
321
+ print("Failed to download from this ref. HTTP status code: "
322
+ f"{response.status_code}")
323
+ else:
324
+ # for–else: only executed if we never hit 'break'
325
+ print("Error: could not download examples from any ref "
326
+ f"(tried: {', '.join(refs_to_try)}).")
314
327
  return 1
315
328
 
329
+ # At this point, 'response' holds a successful download
330
+ zip_file_bytes = io.BytesIO(response.content)
331
+
332
+ # --- Extract into a temporary directory to avoid polluting CWD ----------
333
+ with tempfile.TemporaryDirectory() as tmpdir:
334
+ with zipfile.ZipFile(zip_file_bytes, "r") as zip_ref:
335
+ zip_ref.extractall(tmpdir)
336
+ # First member gives us the top-level directory in the archive,
337
+ # typically something like 'xARPES-0.3.3/' or 'xARPES-main/'.
338
+ first_member = zip_ref.namelist()[0]
339
+
340
+ top_level_dir = first_member.split("/")[0]
341
+ main_folder_path = os.path.join(tmpdir, top_level_dir)
342
+ examples_path = os.path.join(main_folder_path, "examples")
343
+
344
+ if not os.path.exists(examples_path):
345
+ print("Error: downloaded archive does not contain an 'examples' "
346
+ "directory.")
347
+ return 1
348
+
349
+ # Move the 'examples' directory to the target location in the CWD
350
+ shutil.move(examples_path, final_examples_path)
351
+ print(f"'examples' subdirectory moved to {final_examples_path}")
352
+
353
+ # Convert all .Rmd files in the examples directory to .ipynb
354
+ # and delete the .Rmd files
355
+ for dirpath, dirnames, filenames in os.walk(final_examples_path):
356
+ for filename in filenames:
357
+ if filename.endswith(".Rmd"):
358
+ full_path = os.path.join(dirpath, filename)
359
+ jupytext.write(
360
+ jupytext.read(full_path),
361
+ full_path.replace(".Rmd", ".ipynb")
362
+ )
363
+ os.remove(full_path) # Deletes .Rmd file afterwards
364
+ print(f"Converted and deleted {full_path}")
365
+
366
+ # Temporary directory is cleaned up automatically
367
+ print("Cleaned up temporary files.")
368
+ return 0
369
+
316
370
 
317
371
  def set_script_dir():
318
372
  r"""This function sets the directory such that the xARPES code can be
xarpes/spectral.py CHANGED
@@ -16,7 +16,7 @@ from igor2 import binarywave
16
16
  from .plotting import get_ax_fig_plt, add_fig_kwargs
17
17
  from .functions import fit_leastsq, extend_function
18
18
  from .distributions import FermiDirac, Linear
19
- from .constants import uncr, pref, dtor, kilo
19
+ from .constants import uncr, pref, dtor, kilo, stdv
20
20
 
21
21
  class BandMap:
22
22
  r"""
@@ -349,24 +349,17 @@ class BandMap:
349
349
 
350
350
  @add_fig_kwargs
351
351
  def plot(self, abscissa='momentum', ordinate='electron_energy',
352
- self_energies=None, ax=None, **kwargs):
352
+ self_energies=None, ax=None, markersize=None, **kwargs):
353
353
  r"""
354
354
  Plot the band map. Optionally attach a collection of self-energies,
355
355
  e.g. a CreateSelfEnergies instance or any iterable of self-energy
356
356
  objects. They are stored on `self` for later overlay plotting.
357
- """
358
357
 
359
- # Optionally store self-energies on the instance
360
- if self_energies is not None:
361
- # You can wrap here if you like, e.g.:
362
- # from .containers import CreateSelfEnergies
363
- # if not isinstance(self_energies, CreateSelfEnergies):
364
- # self_energies = CreateSelfEnergies(self_energies)
365
- self._self_energies = self_energies
366
- elif not hasattr(self, "_self_energies"):
367
- self._self_energies = None
358
+ When self-energies are present and ``abscissa='momentum'``, their
359
+ MDC maxima are overlaid with 95 % confidence intervals.
360
+ """
361
+ import warnings
368
362
 
369
- # Validate options early
370
363
  valid_abscissa = ('angle', 'momentum')
371
364
  valid_ordinate = ('kinetic_energy', 'electron_energy')
372
365
 
@@ -381,52 +374,109 @@ class BandMap:
381
374
  f"Valid options: {valid_ordinate}"
382
375
  )
383
376
 
377
+ # Optionally store self-energies on the instance
378
+ if self_energies is not None:
379
+
380
+ # MDC maxima are defined in momentum space, not angle space
381
+ if abscissa == 'angle' and isinstance(
382
+ self_energies, (list, tuple, CreateSelfEnergies)
383
+ ):
384
+ raise ValueError( "MDC maxima cannot be plotted against "
385
+ "angles; they are defined in momentum space. Use " \
386
+ "abscissa='momentum' when passing a list of self-energies.")
387
+
388
+ if not isinstance(self_energies, CreateSelfEnergies):
389
+ self_energies = CreateSelfEnergies(self_energies)
390
+
391
+ self._self_energies = self_energies
392
+ elif not hasattr(self, "_self_energies"):
393
+ self._self_energies = None
394
+
384
395
  ax, fig, plt = get_ax_fig_plt(ax=ax)
396
+ # Below, **kwargs is
385
397
 
386
398
  Angl, Ekin = np.meshgrid(self.angles, self.ekin)
387
- mesh = None # sentinel to detect missing branch
388
-
399
+
389
400
  if abscissa == 'angle':
390
401
  ax.set_xlabel('Angle ($\\degree$)')
391
402
  if ordinate == 'kinetic_energy':
392
- mesh = ax.pcolormesh(
393
- Angl, Ekin, self.intensities, shading='auto',
394
- cmap=plt.get_cmap('bone').reversed(), **kwargs
395
- )
403
+ mesh = ax.pcolormesh(Angl, Ekin, self.intensities,
404
+ shading='auto', cmap=plt.get_cmap('bone').reversed(),
405
+ **kwargs)
396
406
  ax.set_ylabel('$E_{\\mathrm{kin}}$ (eV)')
397
407
  elif ordinate == 'electron_energy':
398
408
  Enel = Ekin - self.hnuminphi
399
- mesh = ax.pcolormesh(
400
- Angl, Enel, self.intensities, shading='auto',
401
- cmap=plt.get_cmap('bone').reversed(), **kwargs
402
- )
409
+ mesh = ax.pcolormesh(Angl, Enel, self.intensities,
410
+ shading='auto', cmap=plt.get_cmap('bone').reversed(),
411
+ **kwargs)
403
412
  ax.set_ylabel('$E-\\mu$ (eV)')
404
413
 
405
414
  elif abscissa == 'momentum':
406
- Mome = np.sqrt(Ekin / pref) * np.sin(Angl * dtor)
407
415
  ax.set_xlabel(r'$k_{//}$ ($\mathrm{\AA}^{-1}$)')
408
- if ordinate == 'kinetic_energy':
409
- mesh = ax.pcolormesh(
410
- Mome, Ekin, self.intensities, shading='auto',
411
- cmap=plt.get_cmap('bone').reversed(), **kwargs
412
- )
413
- ax.set_ylabel('$E_{\\mathrm{kin}}$ (eV)')
414
- elif ordinate == 'electron_energy':
415
- Enel = Ekin - self.hnuminphi
416
- mesh = ax.pcolormesh(
417
- Mome, Enel, self.intensities, shading='auto',
418
- cmap=plt.get_cmap('bone').reversed(), **kwargs
419
- )
420
- ax.set_ylabel('$E-\\mu$ (eV)')
421
416
 
422
- # If no branch set 'mesh', fail with a clear message
423
- if mesh is None:
424
- raise RuntimeError(
425
- "No plot produced for the combination: "
426
- f"abscissa='{abscissa}', ordinate='{ordinate}'. "
427
- f"Valid abscissa: {valid_abscissa}; "
428
- f"valid ordinate: {valid_ordinate}."
429
- )
417
+ with warnings.catch_warnings(record=True) as wlist:
418
+ warnings.filterwarnings("always",
419
+ message=("The input coordinates to pcolormesh are "
420
+ "interpreted as cell centers, but are not "
421
+ "monotonically increasing or decreasing."),
422
+ category=UserWarning)
423
+
424
+ Mome = np.sqrt(Ekin / pref) * np.sin(Angl * dtor)
425
+
426
+ if ordinate == 'kinetic_energy':
427
+ mesh = ax.pcolormesh(Mome, Ekin, self.intensities,
428
+ shading='auto', cmap=plt.get_cmap('bone').reversed(),
429
+ **kwargs)
430
+ ax.set_ylabel('$E_{\\mathrm{kin}}$ (eV)')
431
+
432
+ elif ordinate == 'electron_energy':
433
+ Enel = Ekin - self.hnuminphi
434
+ mesh = ax.pcolormesh(Mome, Enel, self.intensities,
435
+ shading='auto', cmap=plt.get_cmap('bone').reversed(),
436
+ **kwargs)
437
+ ax.set_ylabel('$E-\\mu$ (eV)')
438
+
439
+ if any("cell centers" in str(w.message) for w in wlist):
440
+ warnings.warn("Conversion from angle to momenta causes warping "
441
+ "of the cell centers. \n Cell edges of the "
442
+ "mesh plot may look irregular.", UserWarning,
443
+ stacklevel=2)
444
+
445
+ if abscissa == 'momentum' and self._self_energies is not None:
446
+ for self_energy in self._self_energies:
447
+ mdc_maxima = getattr(self_energy, "mdc_maxima", None)
448
+
449
+ # If this self-energy doesn't contain maxima, don't plot them
450
+ if mdc_maxima is None:
451
+ continue
452
+
453
+ peak_sigma = getattr(self_energy, "peak_positions_sigma",
454
+ None)
455
+ xerr = stdv * peak_sigma if peak_sigma is not None else None
456
+
457
+ if ordinate == 'kinetic_energy':
458
+ y_vals = self_energy.ekin_range
459
+ else: # electron energy
460
+ y_vals = self_energy.enel_range
461
+
462
+ x_vals = mdc_maxima
463
+ label = getattr(self_energy, "label", None)
464
+
465
+ if xerr is not None:
466
+ ax.errorbar(x_vals, y_vals, xerr=xerr, fmt='o',
467
+ linestyle='', label=label,
468
+ markersize=markersize)
469
+ else:
470
+ ax.plot(x_vals, y_vals, linestyle='', marker='o',
471
+ label=label, markersize=markersize)
472
+
473
+ handles, labels = ax.get_legend_handles_labels()
474
+ if any(labels):
475
+ ax.legend()
476
+
477
+ handles, labels = ax.get_legend_handles_labels()
478
+ if any(labels):
479
+ ax.legend()
430
480
 
431
481
  plt.colorbar(mesh, ax=ax, label='counts (-)')
432
482
  return fig
@@ -1233,14 +1283,25 @@ class MDCs:
1233
1283
  label = getattr(dist, 'label', str(dist))
1234
1284
  individual_labels.append(label)
1235
1285
 
1236
- # ---- collect parameters for this distribution
1286
+ # ---- collect parameters for this distribution
1237
1287
  # (Aggregated over slices)
1238
1288
  cls = getattr(dist, 'class_name', None)
1239
1289
  wanted = param_spec.get(cls, ())
1240
1290
 
1241
1291
  # ensure dicts exist
1242
1292
  label_bucket = aggregated_properties.setdefault(label, {})
1243
- class_bucket = label_bucket.setdefault(cls, {'label': label, '_class': cls})
1293
+ class_bucket = label_bucket.setdefault(
1294
+ cls, {'label': label, '_class': cls}
1295
+ )
1296
+
1297
+ # store center_wavevector (scalar) for SpectralQuadratic
1298
+ if (
1299
+ cls == 'SpectralQuadratic'
1300
+ and hasattr(dist, 'center_wavevector')
1301
+ ):
1302
+ class_bucket.setdefault(
1303
+ 'center_wavevector', dist.center_wavevector
1304
+ )
1244
1305
 
1245
1306
  # ensure keys for both values and sigmas
1246
1307
  for pname in wanted:
@@ -1560,8 +1621,8 @@ class MDCs:
1560
1621
  return final_result
1561
1622
 
1562
1623
 
1563
- def expose_parameters(self, select_label, fermi_wavevector=None,
1564
- fermi_velocity=None, bare_mass=None, side=None):
1624
+ def expose_parameters(self, select_label, fermi_wavevector=None,
1625
+ fermi_velocity=None, bare_mass=None, side=None):
1565
1626
  r"""
1566
1627
  Select and return fitted parameters for a given component label, plus a
1567
1628
  flat export dictionary containing values **and** 1σ uncertainties.
@@ -1575,7 +1636,8 @@ class MDCs:
1575
1636
  fermi_velocity : float, optional
1576
1637
  Optional Fermi velocity to include.
1577
1638
  bare_mass : float, optional
1578
- Optional bare mass to include (used for SpectralQuadratic dispersions).
1639
+ Optional bare mass to include (used for SpectralQuadratic
1640
+ dispersions).
1579
1641
  side : {'left','right'}, optional
1580
1642
  Optional side selector for SpectralQuadratic dispersions.
1581
1643
 
@@ -1588,30 +1650,41 @@ class MDCs:
1588
1650
  label : str
1589
1651
  Label of the selected distribution.
1590
1652
  selected_properties : dict or list of dict
1591
- Nested dictionary (or list thereof) containing <param> and
1592
- <param>_sigma arrays.
1653
+ Nested dictionary (or list thereof) containing <param> and
1654
+ <param>_sigma arrays. For SpectralQuadratic components, a
1655
+ scalar `center_wavevector` is also present.
1593
1656
  exported_parameters : dict
1594
- Flat dictionary of parameters and their uncertainties, plus optional
1595
- Fermi quantities and `side`.
1657
+ Flat dictionary of parameters and their uncertainties, plus
1658
+ optional Fermi quantities and `side`. For SpectralQuadratic
1659
+ components, `center_wavevector` is included and taken directly
1660
+ from the fitted distribution.
1596
1661
  """
1597
1662
 
1598
1663
  if self._ekin_range is None:
1599
- raise AttributeError("ekin_range not yet set. Run `.fit_selection()` first.")
1664
+ raise AttributeError(
1665
+ "ekin_range not yet set. Run `.fit_selection()` first."
1666
+ )
1600
1667
 
1601
1668
  store = getattr(self, "_individual_properties", None)
1602
1669
  if not store or select_label not in store:
1603
- all_labels = (sorted(store.keys()) if isinstance(store, dict) else [])
1670
+ all_labels = (sorted(store.keys())
1671
+ if isinstance(store, dict) else [])
1604
1672
  raise ValueError(
1605
- f"Label '{select_label}' not found in available labels: {all_labels}"
1673
+ f"Label '{select_label}' not found in available labels: "
1674
+ f"{all_labels}"
1606
1675
  )
1607
1676
 
1608
- # Convert lists → numpy arrays within the selected label’s classes
1677
+ # Convert lists → numpy arrays within the selected label’s classes.
1678
+ # Keep scalar center_wavevector as a scalar.
1609
1679
  per_class_dicts = []
1610
1680
  for cls, bucket in store[select_label].items():
1611
1681
  dct = {}
1612
1682
  for k, v in bucket.items():
1613
1683
  if k in ("label", "_class"):
1614
1684
  dct[k] = v
1685
+ elif k == "center_wavevector":
1686
+ # keep scalar as-is, do not wrap in np.asarray
1687
+ dct[k] = v
1615
1688
  else:
1616
1689
  dct[k] = np.asarray(v)
1617
1690
  per_class_dicts.append(dct)
@@ -1628,20 +1701,23 @@ class MDCs:
1628
1701
  "side": side,
1629
1702
  }
1630
1703
 
1631
- # Collect parameters without prefixing by class
1704
+ # Collect parameters without prefixing by class. This will also include
1705
+ # center_wavevector from the fitted SpectralQuadratic class, and since
1706
+ # there is no function argument with that name, it cannot be overridden.
1632
1707
  if isinstance(selected_properties, dict):
1633
1708
  for key, val in selected_properties.items():
1634
1709
  if key not in ("label", "_class"):
1635
1710
  exported_parameters[key] = val
1636
1711
  else:
1637
- # If multiple classes, merge sequentially (last overwrites same-name keys)
1712
+ # If multiple classes, merge sequentially
1713
+ # (last overwrites same-name keys).
1638
1714
  for cls_bucket in selected_properties:
1639
1715
  for key, val in cls_bucket.items():
1640
1716
  if key not in ("label", "_class"):
1641
1717
  exported_parameters[key] = val
1642
1718
 
1643
- return self._ekin_range, self.hnuminphi, select_label, \
1644
- selected_properties, exported_parameters
1719
+ return (self._ekin_range, self.hnuminphi, select_label,
1720
+ selected_properties, exported_parameters)
1645
1721
 
1646
1722
 
1647
1723
  class SelfEnergy:
@@ -1697,6 +1773,7 @@ class SelfEnergy:
1697
1773
  self._peak_sigma = self._properties.get("peak_sigma")
1698
1774
  self._broadening = self._properties.get("broadening")
1699
1775
  self._broadening_sigma = self._properties.get("broadening_sigma")
1776
+ self._center_wavevector = self._properties.get("center_wavevector")
1700
1777
 
1701
1778
  # lazy caches
1702
1779
  self._peak_positions = None
@@ -1763,6 +1840,7 @@ class SelfEnergy:
1763
1840
  self._peak_positions = None
1764
1841
  self._real = None
1765
1842
  self._real_sigma = None
1843
+ self._mdc_maxima = None
1766
1844
 
1767
1845
  @property
1768
1846
  def fermi_wavevector(self):
@@ -1838,6 +1916,7 @@ class SelfEnergy:
1838
1916
  # invalidate dependent cache
1839
1917
  self._peak_positions = None
1840
1918
  self._real = None
1919
+ self._mdc_maxima = None
1841
1920
 
1842
1921
  @property
1843
1922
  def peak_sigma(self):
@@ -1870,6 +1949,11 @@ class SelfEnergy:
1870
1949
  self._properties["broadening_sigma"] = x
1871
1950
  self._imag_sigma = None
1872
1951
 
1952
+ @property
1953
+ def center_wavevector(self):
1954
+ """Read-only center wavevector (SpectralQuadratic, if present)."""
1955
+ return self._center_wavevector
1956
+
1873
1957
  # ---------------- derived outputs ----------------
1874
1958
  @property
1875
1959
  def peak_positions(self):
@@ -1888,7 +1972,7 @@ class SelfEnergy:
1888
1972
  np.sin(np.abs(self._peak) * dtor)
1889
1973
  self._peak_positions = (-1.0 if self._side == "left" \
1890
1974
  else 1.0) * kpar_mag
1891
- else: # SpectralLinear
1975
+ else:
1892
1976
  self._peak_positions = np.sqrt(self._ekin_range / pref) \
1893
1977
  * np.sin(self._peak * dtor)
1894
1978
  return self._peak_positions
@@ -1900,7 +1984,7 @@ class SelfEnergy:
1900
1984
  if self._peak_sigma is None or self._ekin_range is None:
1901
1985
  return None
1902
1986
  self._peak_positions_sigma = (np.sqrt(self._ekin_range / pref)
1903
- * np.cos(self._peak * dtor)
1987
+ * np.abs(np.cos(self._peak * dtor))
1904
1988
  * self._peak_sigma * dtor)
1905
1989
  return self._peak_positions_sigma
1906
1990
 
@@ -1914,9 +1998,9 @@ class SelfEnergy:
1914
1998
  if self._fermi_velocity is None:
1915
1999
  raise AttributeError("Cannot compute `imag` "
1916
2000
  "(SpectralLinear): set `fermi_velocity` first.")
1917
- self._imag = self._fermi_velocity * np.sqrt(self._ekin_range \
2001
+ self._imag = np.abs(self._fermi_velocity) * np.sqrt(self._ekin_range \
1918
2002
  / pref) * self._broadening
1919
- else: # SpectralQuadratic
2003
+ else:
1920
2004
  if self._bare_mass is None:
1921
2005
  raise AttributeError("Cannot compute `imag` "
1922
2006
  "(SpectralQuadratic): set `bare_mass` first.")
@@ -1934,9 +2018,9 @@ class SelfEnergy:
1934
2018
  if self._fermi_velocity is None:
1935
2019
  raise AttributeError("Cannot compute `imag_sigma` "
1936
2020
  "(SpectralLinear): set `fermi_velocity` first.")
1937
- self._imag_sigma = self._fermi_velocity * \
2021
+ self._imag_sigma = np.abs(self._fermi_velocity) * \
1938
2022
  np.sqrt(self._ekin_range / pref) * self._broadening_sigma
1939
- else: # SpectralQuadratic
2023
+ else:
1940
2024
  if self._bare_mass is None:
1941
2025
  raise AttributeError("Cannot compute `imag_sigma` "
1942
2026
  "(SpectralQuadratic): set `bare_mass` first.")
@@ -1957,13 +2041,13 @@ class SelfEnergy:
1957
2041
  "`fermi_wavevector` first.")
1958
2042
  self._real = self.enel_range - self._fermi_velocity * \
1959
2043
  (self.peak_positions - self._fermi_wavevector)
1960
- else: # SpectralQuadratic
2044
+ else:
1961
2045
  if self._bare_mass is None or self._fermi_wavevector is None:
1962
2046
  raise AttributeError("Cannot compute `real` "
1963
2047
  "(SpectralQuadratic): set `bare_mass` and " \
1964
2048
  "`fermi_wavevector` first.")
1965
2049
  self._real = self.enel_range - (pref / \
1966
- np.abs(self._bare_mass)) * (self.peak_positions**2 \
2050
+ self._bare_mass) * (self.peak_positions**2 \
1967
2051
  - self._fermi_wavevector**2)
1968
2052
  return self._real
1969
2053
 
@@ -1977,8 +2061,8 @@ class SelfEnergy:
1977
2061
  if self._fermi_velocity is None:
1978
2062
  raise AttributeError("Cannot compute `real_sigma` "
1979
2063
  "(SpectralLinear): set `fermi_velocity` first.")
1980
- self._real_sigma = self._fermi_velocity * self.peak_positions_sigma
1981
- else: # SpectralQuadratic
2064
+ self._real_sigma = np.abs(self._fermi_velocity) * self.peak_positions_sigma
2065
+ else:
1982
2066
  if self._bare_mass is None or self._fermi_wavevector is None:
1983
2067
  raise AttributeError("Cannot compute `real_sigma` "
1984
2068
  "(SpectralQuadratic): set `bare_mass` and " \
@@ -1987,6 +2071,30 @@ class SelfEnergy:
1987
2071
  * np.abs(self.peak_positions / self._bare_mass)
1988
2072
  return self._real_sigma
1989
2073
 
2074
+ @property
2075
+ def mdc_maxima(self):
2076
+ """
2077
+ MDC maxima (lazy).
2078
+
2079
+ SpectralLinear:
2080
+ identical to peak_positions
2081
+
2082
+ SpectralQuadratic:
2083
+ peak_positions + center_wavevector
2084
+ """
2085
+ if getattr(self, "_mdc_maxima", None) is None:
2086
+ if self.peak_positions is None:
2087
+ return None
2088
+
2089
+ if self._class == "SpectralLinear":
2090
+ self._mdc_maxima = self.peak_positions
2091
+ elif self._class == "SpectralQuadratic":
2092
+ self._mdc_maxima = (
2093
+ self.peak_positions + self._center_wavevector
2094
+ )
2095
+
2096
+ return self._mdc_maxima
2097
+
1990
2098
 
1991
2099
  class CreateSelfEnergies:
1992
2100
  r"""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xarpes
3
- Version: 0.3.2
3
+ Version: 0.3.4
4
4
  Summary: Extraction from angle resolved photoemission spectra
5
5
  Author: xARPES Developers
6
6
  Requires-Python: >=3.7.0
@@ -16,7 +16,8 @@ Requires-Dist: numpy
16
16
  Requires-Dist: scipy
17
17
  Requires-Dist: lmfit
18
18
  Requires-Dist: pyqt5
19
- Requires-Dist: ipympl
19
+ Requires-Dist: ipympl>=0.9.3
20
+ Requires-Dist: ipywidgets>=8.1.5
20
21
  Requires-Dist: ipykernel<6.32.0
21
22
  Project-URL: Documentation, https://xarpes.github.io
22
23
 
@@ -32,7 +33,7 @@ This project is currently undergoing **beta testing**. Some of the functionaliti
32
33
 
33
34
  # Contributing
34
35
 
35
- Contributions to the code are most welcome. xARPES is intended to co-develop alongside the increasing complexity of experimental ARPES data sets. Contributions can be made by forking the code and creating a pull request. Importing of file formats from different beamlines is particularly encouraged.
36
+ Contributions to the code are most welcome. xARPES is intended to co-develop alongside the increasing complexity of experimental ARPES data sets. Contributions can be made by forking the code and creating a pull request. Importing of file formats from different beamlines is particularly encouraged. Files useful for developers can be found in `/dev_tools`, such as the `Rmd2py.py` script for the development of examples.
36
37
 
37
38
  # Installation
38
39
 
@@ -41,6 +42,8 @@ xARPES installation can be divided into graphical package manager instructions,
41
42
  - via conda-forge, out-of-the-box or editable installation, sourcing the [conda-forge package](https://anaconda.org/conda-forge/xarpes).
42
43
  - via Pip, out-of-the-box or editable installation, sourcing the [PyPI package](https://pypi.org/project/xarpes).
43
44
 
45
+ We strongly recommend installing xARPES in a (conda/pip) virtual environment, and to activate the environment each time before activating xARPES.
46
+
44
47
  ## Graphical package manager installation
45
48
 
46
49
  Most IDEs and scientific Python distributions include a GUI-based package manager.
@@ -83,11 +86,13 @@ Example for Linux:
83
86
  wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh
84
87
  bash Miniconda3-latest-Linux-x86_64.sh
85
88
 
86
- Create and activate an environment:
89
+ Answer `y` to questions. Create and activate a new environment:
87
90
 
88
91
  conda create -n <my_env> -c defaults -c conda-forge
89
92
  conda activate <my_env>
90
93
 
94
+ Where `<my_env>` must be replaced by your desired name.
95
+
91
96
  ### Installing xARPES
92
97
 
93
98
  #### Option A — Out-of-the-box installation (from conda-forge)
@@ -117,8 +122,8 @@ Install venv if necessary:
117
122
 
118
123
  Create and activate a virtual environment:
119
124
 
120
- python3 -m venv <my_venv>
121
- source <my_venv>/bin/activate
125
+ python3 -m venv <my_env>
126
+ source <my_env>/bin/activate
122
127
 
123
128
  Upgrade pip:
124
129
 
@@ -147,6 +152,8 @@ After installation of xARPES, the `examples/` folder can be downloaded to the cu
147
152
 
148
153
  python -c "import xarpes; xarpes.download_examples()"
149
154
 
155
+ This attempts to download the examples from the version corresponding encountered in `__init__.py`. If no corresponding tagged version can be downloaded, the code attempts to download the latest examples instead.
156
+
150
157
  # Execution
151
158
 
152
159
  It is recommended to use JupyterLab to analyse data. JupyterLab is launched using:
@@ -0,0 +1,11 @@
1
+ xarpes/__init__.py,sha256=WAZKbz4gEW1H7Yb24GVBAryDrMsQiAn0UNmeJwEM_no,124
2
+ xarpes/constants.py,sha256=vQxxFeCdGIxMpdh5XGbeRbn7-HF1d5snWkR09d8spGc,587
3
+ xarpes/distributions.py,sha256=svzhvf994_5gndJA1M04SW4MVfHEVwiAumbhO5Jj22s,23434
4
+ xarpes/functions.py,sha256=4s2atkWyPUb1ipJApRDVMsow_47kt25wvSwLtfqD2fs,14261
5
+ xarpes/plotting.py,sha256=W-5WaKjBtg8PIxTypqja2R29mgWkQ844lgRWci0nhn0,5679
6
+ xarpes/spectral.py,sha256=GT_1GUMy9Md4X8W7UM0l8ir_WRaPAaUbgvS5Ep0uSY8,82863
7
+ xarpes-0.3.4.dist-info/entry_points.txt,sha256=917UR-cqFTMMI_vMqIbk7boYSuFX_zHwQlXKcj9vlCE,79
8
+ xarpes-0.3.4.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
9
+ xarpes-0.3.4.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
10
+ xarpes-0.3.4.dist-info/METADATA,sha256=FLClCjMjfV2D_MYH3o9o3PlFG2KsDJLZLyNUzwD2FG4,6369
11
+ xarpes-0.3.4.dist-info/RECORD,,
@@ -1,11 +0,0 @@
1
- xarpes/__init__.py,sha256=Wk8mY-vjImFwBLPPzg2ot_LCJ5sA0yrffNQy3Ik-hq8,125
2
- xarpes/constants.py,sha256=vQxxFeCdGIxMpdh5XGbeRbn7-HF1d5snWkR09d8spGc,587
3
- xarpes/distributions.py,sha256=svzhvf994_5gndJA1M04SW4MVfHEVwiAumbhO5Jj22s,23434
4
- xarpes/functions.py,sha256=gE76z-Y9UI1KNUUtADyLziLU1UJ43E1CHLDi_khj0bc,12007
5
- xarpes/plotting.py,sha256=W-5WaKjBtg8PIxTypqja2R29mgWkQ844lgRWci0nhn0,5679
6
- xarpes/spectral.py,sha256=qw0qSeYfaCc9C28cGe32QrI3mK64KNcoNTDkS5EplmM,78637
7
- xarpes-0.3.2.dist-info/entry_points.txt,sha256=917UR-cqFTMMI_vMqIbk7boYSuFX_zHwQlXKcj9vlCE,79
8
- xarpes-0.3.2.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
9
- xarpes-0.3.2.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
10
- xarpes-0.3.2.dist-info/METADATA,sha256=jDXUPvkEx1obDPPHmlSbR32gZ3oTJnd5V6MlPu6HgSk,5763
11
- xarpes-0.3.2.dist-info/RECORD,,
File without changes