xarpes 0.4.0__tar.gz → 0.6.0__tar.gz

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.
Files changed (64) hide show
  1. {xarpes-0.4.0 → xarpes-0.6.0}/.gitignore +7 -1
  2. {xarpes-0.4.0 → xarpes-0.6.0}/PKG-INFO +14 -8
  3. {xarpes-0.4.0 → xarpes-0.6.0}/README.md +11 -4
  4. xarpes-0.6.0/dev_tools/Rmd2ipynb.py +109 -0
  5. xarpes-0.4.0/dev_tools/Rmd2py.py → xarpes-0.6.0/dev_tools/ipynb2Rmd2py.py +167 -19
  6. {xarpes-0.4.0 → xarpes-0.6.0}/doc/conf.py +2 -1
  7. xarpes-0.6.0/doc/index.rst +29 -0
  8. xarpes-0.6.0/doc/modules/bandmap.rst +5 -0
  9. xarpes-0.6.0/doc/modules/mdcs.rst +5 -0
  10. xarpes-0.6.0/doc/modules/selfenergies.rst +5 -0
  11. xarpes-0.6.0/doc/modules/settings_parameters.rst +7 -0
  12. xarpes-0.6.0/doc/modules/settings_plots.rst +8 -0
  13. xarpes-0.6.0/doc/notebooks/graphene.ipynb +993 -0
  14. xarpes-0.6.0/doc/notebooks/srtio3.ipynb +1121 -0
  15. xarpes-0.6.0/doc/notebooks/verification.ipynb +1567 -0
  16. {xarpes-0.4.0 → xarpes-0.6.0}/doc/requirements.txt +2 -1
  17. xarpes-0.6.0/examples/graphene/data_sets/graphene_152_angles.npy +0 -0
  18. xarpes-0.6.0/examples/graphene/data_sets/graphene_152_ekin.npy +0 -0
  19. xarpes-0.6.0/examples/graphene/data_sets/graphene_152_intensities.npy +0 -0
  20. xarpes-0.6.0/examples/graphene/graphene.Rmd +359 -0
  21. xarpes-0.6.0/examples/graphene/graphene.py +292 -0
  22. xarpes-0.6.0/examples/srtio3/data_sets/STO_2_0010STO_2_angles.npy +0 -0
  23. xarpes-0.6.0/examples/srtio3/data_sets/STO_2_0010STO_2_ekin.npy +0 -0
  24. xarpes-0.6.0/examples/srtio3/data_sets/STO_2_0010STO_2_intensities.npy +0 -0
  25. xarpes-0.6.0/examples/srtio3/srtio3.Rmd +329 -0
  26. xarpes-0.6.0/examples/srtio3/srtio3.py +262 -0
  27. xarpes-0.6.0/examples/verification/data_sets/verification_angles.npy +0 -0
  28. xarpes-0.6.0/examples/verification/data_sets/verification_intensities.npy +0 -0
  29. xarpes-0.6.0/examples/verification/data_sets/verification_kinergies.npy +0 -0
  30. xarpes-0.6.0/examples/verification/verification.Rmd +186 -0
  31. xarpes-0.6.0/examples/verification/verification.py +142 -0
  32. {xarpes-0.4.0 → xarpes-0.6.0}/pyproject.toml +1 -1
  33. xarpes-0.6.0/xarpes/__init__.py +36 -0
  34. xarpes-0.6.0/xarpes/bandmap.py +897 -0
  35. xarpes-0.6.0/xarpes/constants.py +13 -0
  36. {xarpes-0.4.0 → xarpes-0.6.0}/xarpes/distributions.py +45 -43
  37. {xarpes-0.4.0 → xarpes-0.6.0}/xarpes/functions.py +247 -7
  38. xarpes-0.6.0/xarpes/mdcs.py +1078 -0
  39. {xarpes-0.4.0 → xarpes-0.6.0}/xarpes/plotting.py +1 -47
  40. xarpes-0.6.0/xarpes/selfenergies.py +1816 -0
  41. xarpes-0.6.0/xarpes/settings_parameters.py +75 -0
  42. xarpes-0.6.0/xarpes/settings_plots.py +54 -0
  43. xarpes-0.4.0/doc/index.rst +0 -17
  44. xarpes-0.4.0/doc/modules/spectral.rst +0 -5
  45. xarpes-0.4.0/examples/graphene/graphene.Rmd +0 -431
  46. xarpes-0.4.0/examples/graphene/graphene.py +0 -348
  47. xarpes-0.4.0/examples/srtio3/srtio3.Rmd +0 -530
  48. xarpes-0.4.0/examples/srtio3/srtio3.py +0 -429
  49. xarpes-0.4.0/xarpes/__init__.py +0 -6
  50. xarpes-0.4.0/xarpes/constants.py +0 -12
  51. xarpes-0.4.0/xarpes/spectral.py +0 -2476
  52. {xarpes-0.4.0 → xarpes-0.6.0}/.readthedocs.yaml +0 -0
  53. {xarpes-0.4.0 → xarpes-0.6.0}/LICENSE +0 -0
  54. {xarpes-0.4.0 → xarpes-0.6.0}/doc/Makefile +0 -0
  55. {xarpes-0.4.0 → xarpes-0.6.0}/doc/README.md +0 -0
  56. {xarpes-0.4.0 → xarpes-0.6.0}/doc/_static/xarpes_small.svg +0 -0
  57. {xarpes-0.4.0 → xarpes-0.6.0}/doc/modules/distributions.rst +0 -0
  58. {xarpes-0.4.0 → xarpes-0.6.0}/doc/modules/functions.rst +0 -0
  59. {xarpes-0.4.0 → xarpes-0.6.0}/doc/modules/plotting.rst +0 -0
  60. {xarpes-0.4.0 → xarpes-0.6.0}/examples/graphene/data_sets/graphene_152.ibw +0 -0
  61. {xarpes-0.4.0 → xarpes-0.6.0}/examples/srtio3/data_sets/STO_2_0010STO_2_.ibw +0 -0
  62. {xarpes-0.4.0 → xarpes-0.6.0}/logo/Makefile +0 -0
  63. {xarpes-0.4.0 → xarpes-0.6.0}/logo/exubi.svg +0 -0
  64. {xarpes-0.4.0 → xarpes-0.6.0}/logo/xarpes.svg +0 -0
@@ -81,6 +81,10 @@ target/
81
81
  .ipynb_checkpoints
82
82
  *.ipynb
83
83
 
84
+ # --- allow tracked notebooks in documentation ---
85
+ !doc/notebooks/
86
+ !doc/notebooks/*.ipynb
87
+
84
88
  # IPython
85
89
  profile_default/
86
90
  ipython_config.py
@@ -173,5 +177,7 @@ cython_debug/
173
177
  # VS Code
174
178
  .vscode/
175
179
 
176
- # Ignore Rmd2py.py script in examples folder
180
+ # Ignore conversion scripts if placed in examples folder
181
+ examples/Rmd2ipynb.py
182
+ examples/ipynb2Rmd2py.py
177
183
  examples/Rmd2py.py
@@ -1,17 +1,16 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.1
2
2
  Name: xarpes
3
- Version: 0.4.0
3
+ Version: 0.6.0
4
4
  Summary: Extraction from angle resolved photoemission spectra
5
5
  Author: xARPES Developers
6
6
  Requires-Python: >=3.7.0
7
7
  Description-Content-Type: text/markdown
8
8
  Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
9
9
  Classifier: Programming Language :: Python :: 3
10
- License-File: LICENSE
11
10
  Requires-Dist: igor2>=0.5.8
12
11
  Requires-Dist: jupyterlab
13
12
  Requires-Dist: jupytext
14
- Requires-Dist: matplotlib
13
+ Requires-Dist: matplotlib<3.10.0
15
14
  Requires-Dist: numpy
16
15
  Requires-Dist: scipy
17
16
  Requires-Dist: lmfit
@@ -33,7 +32,9 @@ This project is currently undergoing **beta testing**. Some of the functionaliti
33
32
 
34
33
  # Contributing
35
34
 
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.
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. For development of the examples, the following scripts in `/dev_tools` could be useful:
36
+ - The `Rmd2ipynb.py` file, to generate the `.ipynb` from which the examples can conveniently be developed; to be executed after cloning/pulling updated `.Rmd` and `.py` files, or if you prefer developing with `.Rmd` files.
37
+ - The `ipynb2Rmd2py.py` file, to synchronise the `.Rmd` and `.py` files from the `.ipynb` file; to be executed right before pushing modifications to the repository. Note that this script resets some metadata in the `.Rmd` to prevent the tracking of their changes due to local virtual environments, etc.
37
38
 
38
39
  # Installation
39
40
 
@@ -91,7 +92,7 @@ Answer `y` to questions. Create and activate a new environment:
91
92
  conda create -n <my_env> -c conda-forge
92
93
  conda activate <my_env>
93
94
 
94
- Where `<my_env>` must be replaced by your desired name. Package compatibility ssues may arise if conda installs from different channels. This can be prevented by appending `--strict-channel-priority` to the creation command.
95
+ Where `<my_env>` must be replaced by your desired name. Package compatibility issues may arise if conda installs from different channels. This can be prevented by appending `--strict-channel-priority` to the creation command.
95
96
 
96
97
  ### Installing xARPES
97
98
 
@@ -148,12 +149,17 @@ Then perform editable installation:
148
149
 
149
150
  # Examples
150
151
 
151
- After installation of xARPES, the `examples/` folder can be downloaded to the current directory:
152
+ After installation of xARPES, the `examples/` folder can be downloaded from the terminal into the current directory:
152
153
 
153
- python -c "import xarpes; xarpes.download_examples()"
154
+ xarpes_download_examples
154
155
 
155
156
  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
157
 
158
+ The examples can also be installed by executing `.py`or `.ipynb` executable files containing the following:
159
+
160
+ import xarpes
161
+ xarpes.download_examples()
162
+
157
163
  # Execution
158
164
 
159
165
  It is recommended to use JupyterLab to analyse data. JupyterLab is launched using:
@@ -10,7 +10,9 @@ This project is currently undergoing **beta testing**. Some of the functionaliti
10
10
 
11
11
  # Contributing
12
12
 
13
- 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.
13
+ 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. For development of the examples, the following scripts in `/dev_tools` could be useful:
14
+ - The `Rmd2ipynb.py` file, to generate the `.ipynb` from which the examples can conveniently be developed; to be executed after cloning/pulling updated `.Rmd` and `.py` files, or if you prefer developing with `.Rmd` files.
15
+ - The `ipynb2Rmd2py.py` file, to synchronise the `.Rmd` and `.py` files from the `.ipynb` file; to be executed right before pushing modifications to the repository. Note that this script resets some metadata in the `.Rmd` to prevent the tracking of their changes due to local virtual environments, etc.
14
16
 
15
17
  # Installation
16
18
 
@@ -68,7 +70,7 @@ Answer `y` to questions. Create and activate a new environment:
68
70
  conda create -n <my_env> -c conda-forge
69
71
  conda activate <my_env>
70
72
 
71
- Where `<my_env>` must be replaced by your desired name. Package compatibility ssues may arise if conda installs from different channels. This can be prevented by appending `--strict-channel-priority` to the creation command.
73
+ Where `<my_env>` must be replaced by your desired name. Package compatibility issues may arise if conda installs from different channels. This can be prevented by appending `--strict-channel-priority` to the creation command.
72
74
 
73
75
  ### Installing xARPES
74
76
 
@@ -125,12 +127,17 @@ Then perform editable installation:
125
127
 
126
128
  # Examples
127
129
 
128
- After installation of xARPES, the `examples/` folder can be downloaded to the current directory:
130
+ After installation of xARPES, the `examples/` folder can be downloaded from the terminal into the current directory:
129
131
 
130
- python -c "import xarpes; xarpes.download_examples()"
132
+ xarpes_download_examples
131
133
 
132
134
  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.
133
135
 
136
+ The examples can also be installed by executing `.py`or `.ipynb` executable files containing the following:
137
+
138
+ import xarpes
139
+ xarpes.download_examples()
140
+
134
141
  # Execution
135
142
 
136
143
  It is recommended to use JupyterLab to analyse data. JupyterLab is launched using:
@@ -0,0 +1,109 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Generate .ipynb notebooks from .Rmd files using Jupytext.
4
+
5
+ - For every .Rmd (excluding hidden folders and .ipynb_checkpoints),
6
+ create or overwrite a sibling <notebook>.ipynb using Jupytext.
7
+
8
+ Dependencies:
9
+ pip install jupytext
10
+
11
+ Usage:
12
+ Place Rmd2ipynb.py in the /examples directory, where it is .gitignored.
13
+ $ python Rmd2ipynb.py # run from anywhere; it operates where it exists.
14
+ """
15
+
16
+ import os
17
+ import sys
18
+ from typing import Optional
19
+
20
+
21
+ def find_base_dir() -> str:
22
+ """Return the directory where this script lives (or CWD in a REPL)."""
23
+ return (os.path.dirname(os.path.abspath(__file__))
24
+ if "__file__" in globals() else os.getcwd())
25
+
26
+
27
+ def is_hidden(name: str) -> bool:
28
+ """Return True if a file or directory name is considered hidden."""
29
+ return name.startswith(".")
30
+
31
+
32
+ def get_jupytext() -> Optional[object]:
33
+ """
34
+ Try to import jupytext and return the module, or None if unavailable.
35
+
36
+ Prints a single warning if jupytext is not installed.
37
+ """
38
+ try:
39
+ import jupytext # type: ignore
40
+ return jupytext
41
+ except ImportError:
42
+ print(
43
+ "[WARN] 'jupytext' is not installed. "
44
+ "Install it with 'pip install jupytext' to enable "
45
+ ".Rmd -> .ipynb conversion.",
46
+ file=sys.stderr,
47
+ )
48
+ return None
49
+
50
+
51
+ def convert_rmd_to_ipynb(rmd_path: str, jupytext) -> None:
52
+ """
53
+ Convert a single .Rmd file to a .ipynb notebook using Jupytext.
54
+
55
+ The .ipynb file is written next to the .Rmd file. Existing notebooks
56
+ are overwritten.
57
+ """
58
+ base, ext = os.path.splitext(rmd_path)
59
+ if ext.lower() != ".rmd":
60
+ return
61
+
62
+ ipynb_path = base + ".ipynb"
63
+
64
+ try:
65
+ # Let Jupytext auto-detect the format from the extension
66
+ nb = jupytext.read(rmd_path)
67
+ jupytext.write(nb, ipynb_path)
68
+ print(f"Converted: {rmd_path} -> {ipynb_path}")
69
+ except Exception as exc:
70
+ print(
71
+ f"[ERROR] Failed to convert '{rmd_path}' to .ipynb: {exc}",
72
+ file=sys.stderr,
73
+ )
74
+
75
+
76
+ def main() -> None:
77
+ base_dir = find_base_dir()
78
+ jupytext = get_jupytext()
79
+ if jupytext is None:
80
+ # Nothing to do if we don't have jupytext
81
+ return
82
+
83
+ converted_any = False
84
+
85
+ for path, folders, files in os.walk(base_dir, topdown=True):
86
+ # Skip hidden folders and notebook checkpoint caches
87
+ folders[:] = [
88
+ name for name in folders
89
+ if not is_hidden(name) and name != ".ipynb_checkpoints"
90
+ ]
91
+
92
+ for name in files:
93
+ if is_hidden(name):
94
+ continue
95
+ if not name.endswith(".Rmd"):
96
+ continue
97
+ if ".ipynb_checkpoints" in path:
98
+ continue
99
+
100
+ rmd = os.path.join(path, name)
101
+ convert_rmd_to_ipynb(rmd, jupytext)
102
+ converted_any = True
103
+
104
+ if not converted_any:
105
+ print("No .Rmd files found to convert.")
106
+
107
+
108
+ if __name__ == "__main__":
109
+ main()
@@ -15,17 +15,23 @@ Usage:
15
15
  """
16
16
 
17
17
  import os
18
+ import re
18
19
  import shutil
19
20
  import subprocess
20
21
  import sys
21
22
 
23
+
22
24
  def find_base_dir() -> str:
23
25
  # Safe base directory (works both when run as script or in REPL)
24
- return os.path.dirname(os.path.abspath(__file__)) if "__file__" in globals() else os.getcwd()
26
+ return (os.path.dirname(os.path.abspath(__file__))
27
+ if "__file__" in globals()
28
+ else os.getcwd())
29
+
25
30
 
26
31
  def is_hidden(name: str) -> bool:
27
32
  return name.startswith(".")
28
33
 
34
+
29
35
  def run_jupytext_ipynb_to_rmd(ipynb_path: str) -> bool:
30
36
  """
31
37
  Use the jupytext CLI to convert .ipynb -> .Rmd next to it.
@@ -33,29 +39,40 @@ def run_jupytext_ipynb_to_rmd(ipynb_path: str) -> bool:
33
39
  """
34
40
  jupytext = shutil.which("jupytext")
35
41
  if not jupytext:
36
- print("[WARN] 'jupytext' not found on PATH. Skipping .ipynb -> .Rmd step for:",
42
+ print("[WARN] 'jupytext' not found on PATH. "
43
+ "Skipping .ipynb -> .Rmd step for:",
37
44
  ipynb_path, file=sys.stderr)
38
45
  return False
39
46
 
40
47
  cmd = [jupytext, "--to", "rmarkdown", ipynb_path]
41
48
  try:
42
- res = subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
49
+ res = subprocess.run(
50
+ cmd,
51
+ check=True,
52
+ stdout=subprocess.PIPE,
53
+ stderr=subprocess.STDOUT,
54
+ text=True,
55
+ )
43
56
  # Optional: uncomment to see jupytext output
44
57
  # print(res.stdout, end="")
45
58
  return True
46
59
  except subprocess.CalledProcessError as e:
47
- print(f"[ERROR] jupytext failed for {ipynb_path}\n{e.stdout}", file=sys.stderr)
60
+ print(f"[ERROR] jupytext failed for {ipynb_path}\n{e.stdout}",
61
+ file=sys.stderr)
48
62
  return False
49
63
 
64
+
50
65
  def convert_rmd_to_py(rmd_path: str) -> None:
51
66
  """
52
67
  Convert a single .Rmd file to a .py script:
53
68
  - YAML front matter is skipped
54
69
  - Markdown outside code fences is commented with '# '
55
- - Code inside fences (``` or ~~~) is written verbatim (with existing magic handling)
70
+ - Code inside fences (``` or ~~~) is written verbatim (with existing magic
71
+ handling)
56
72
  """
57
73
  py_path = rmd_path[:-3] + "py" # replace .Rmd with .py
58
- with open(rmd_path, "r", encoding="utf-8") as lines, open(py_path, "w", encoding="utf-8") as text:
74
+ with open(rmd_path, "r", encoding="utf-8") as lines, \
75
+ open(py_path, "w", encoding="utf-8") as text:
59
76
  text.write("#!/usr/bin/env python3\n")
60
77
  first_magic_comment = True
61
78
  in_yaml = False
@@ -64,31 +81,33 @@ def convert_rmd_to_py(rmd_path: str) -> None:
64
81
  for raw in lines:
65
82
  line = raw
66
83
 
67
- # ---- YAML front matter ------------------------------------------------
84
+ # ---- YAML front matter --------------------------------------------
68
85
  if line.startswith("---") and not in_code:
69
86
  in_yaml = not in_yaml
70
87
  continue
71
88
  if in_yaml:
72
89
  continue
73
90
 
74
- # ---- Fence open/close (```... or ~~~...) ------------------------------
91
+ # ---- Fence open/close (```... or ~~~...) --------------------------
75
92
  # Rmd/Quarto code chunks: ```{python, echo=FALSE} ... ```
76
- if (line.lstrip().startswith("```") or line.lstrip().startswith("~~~")) and not in_yaml:
93
+ if (line.lstrip().startswith("```") or
94
+ line.lstrip().startswith("~~~")) and not in_yaml:
77
95
  in_code = not in_code
78
96
  # Do not emit the fence line itself
79
97
  continue
80
98
 
81
99
  if in_code:
82
- # ---- Inside code fence: keep code, apply your filters --------------
100
+ # ---- Inside code fence: keep code, apply your filters ---------
83
101
  # Skip lines marked as "Jupyter only"
84
102
  if "Jupyter only" in line:
85
103
  continue
86
104
 
87
105
  # Remove IPython magics
88
- if "%matplotlib widget" in line or "%matplotlib inline" in line:
106
+ if ("%matplotlib widget" in line or
107
+ "%matplotlib inline" in line):
89
108
  continue
90
109
 
91
- # Remove global Jupyter hooks (e.g. get_ipython().events.register(...))
110
+ # Remove global Jupyter hooks (e.g. get_ipython().events...)
92
111
  if "get_ipython" in line:
93
112
  continue
94
113
 
@@ -104,22 +123,104 @@ def convert_rmd_to_py(rmd_path: str) -> None:
104
123
  text.write(line)
105
124
 
106
125
  else:
107
- # ---- Outside code fence: Markdown -> Python comments ---------------
126
+ # ---- Outside code fence: Markdown -> Python comments ----------
108
127
  if line.strip() == "":
109
128
  # Preserve blank lines (safe in Python)
110
129
  text.write("\n")
111
130
  else:
112
- # Comment any Markdown/text line so it doesn't break execution
131
+ # Comment any Markdown/text line so it doesn't break
132
+ # execution
113
133
  text.write("# " + line)
114
134
 
135
+
136
+ def normalise_rmd_metadata(rmd_path: str) -> None:
137
+ """
138
+ Standardise the YAML metadata of the Rmd file (string-based, no yaml lib):
139
+
140
+ - jupytext_version: 1.15.2
141
+ - kernelspec.display_name: Python 3 (ipykernel)
142
+ - kernelspec.name: python3
143
+
144
+ Only modifies existing lines in the YAML header. If the keys do not exist,
145
+ nothing is added.
146
+ """
147
+ try:
148
+ with open(rmd_path, "r", encoding="utf-8") as f:
149
+ lines = f.readlines()
150
+ except OSError:
151
+ return
152
+
153
+ if not lines:
154
+ return
155
+
156
+ # Find first two '---' lines that delimit the YAML front matter.
157
+ if not lines[0].startswith("---"):
158
+ return
159
+
160
+ yaml_start = 0
161
+ yaml_end = None
162
+ for i in range(1, len(lines)):
163
+ if lines[i].startswith("---"):
164
+ yaml_end = i
165
+ break
166
+
167
+ if yaml_end is None:
168
+ # Malformed header, bail out.
169
+ return
170
+
171
+ header_lines = lines[yaml_start + 1:yaml_end]
172
+ header = "".join(header_lines)
173
+
174
+ # Replace jupytext_version
175
+ header = re.sub(
176
+ r'^(\s*jupytext_version:\s*).*$',
177
+ r'\g<1>1.15.2',
178
+ header,
179
+ flags=re.MULTILINE,
180
+ )
181
+
182
+ # Replace kernelspec.display_name
183
+ header = re.sub(
184
+ r'^(\s*display_name:\s*).*$',
185
+ r'\g<1>Python 3 (ipykernel)',
186
+ header,
187
+ flags=re.MULTILINE,
188
+ )
189
+
190
+ # Replace kernelspec.name
191
+ header = re.sub(
192
+ r'^(\s*name:\s*).*$',
193
+ r'\g<1>python3',
194
+ header,
195
+ flags=re.MULTILINE,
196
+ )
197
+
198
+ new_header_lines = header.splitlines(keepends=True)
199
+ new_lines = (
200
+ lines[:yaml_start + 1] +
201
+ new_header_lines +
202
+ lines[yaml_end:]
203
+ )
204
+
205
+ try:
206
+ with open(rmd_path, "w", encoding="utf-8") as f:
207
+ f.writelines(new_lines)
208
+ except OSError:
209
+ # If we can't write, silently skip; script should still continue.
210
+ return
211
+
212
+
115
213
  def main() -> None:
116
214
  base_dir = find_base_dir()
117
215
 
118
- # ---- Pass 1: .ipynb -> .Rmd via jupytext ---------------------------------
216
+ # ---- Pass 1: .ipynb -> .Rmd via jupytext -------------------------------
119
217
  converted_any = False
120
218
  for path, folders, files in os.walk(base_dir, topdown=True):
121
219
  # Skip hidden folders and notebook checkpoint caches
122
- folders[:] = [name for name in folders if not is_hidden(name) and name != ".ipynb_checkpoints"]
220
+ folders[:] = [
221
+ name for name in folders
222
+ if not is_hidden(name) and name != ".ipynb_checkpoints"
223
+ ]
123
224
 
124
225
  for name in files:
125
226
  if is_hidden(name):
@@ -130,14 +231,18 @@ def main() -> None:
130
231
  continue
131
232
 
132
233
  ipynb = os.path.join(path, name)
234
+
235
+ copy_ipynb_to_docs(ipynb, base_dir)
236
+
133
237
  ok = run_jupytext_ipynb_to_rmd(ipynb)
134
238
  converted_any = converted_any or ok
135
239
 
136
240
  if not converted_any:
137
- # Not an error—maybe there are no ipynb files, or jupytext isn't installed.
241
+ # Not an error—maybe there are no ipynb files, or jupytext isn't
242
+ # installed.
138
243
  pass
139
244
 
140
- # ---- Pass 2: .Rmd -> .py (your existing logic) ---------------------------
245
+ # ---- Pass 2: .Rmd -> .py ----------------------------------------------
141
246
  for path, folders, files in os.walk(base_dir, topdown=True):
142
247
  folders[:] = [name for name in folders if not is_hidden(name)]
143
248
  for name in files:
@@ -147,7 +252,50 @@ def main() -> None:
147
252
  continue
148
253
 
149
254
  rmd = os.path.join(path, name)
255
+
256
+ # Normalise jupytext/kernelspec metadata in YAML header
257
+ normalise_rmd_metadata(rmd)
258
+
259
+ # Convert .Rmd -> .py
150
260
  convert_rmd_to_py(rmd)
151
261
 
262
+
263
+ def copy_ipynb_to_docs(ipynb_path: str, base_dir: str) -> None:
264
+ """
265
+ Copy ipynb into <repo>/doc/notebooks, flattening any subfolders.
266
+
267
+ If two notebooks share the same filename, disambiguate by appending a
268
+ suffix derived from their relative directory.
269
+ """
270
+ repo_root = os.path.dirname(base_dir)
271
+ dst_dir = os.path.join(repo_root, "doc", "notebooks")
272
+ os.makedirs(dst_dir, exist_ok=True)
273
+
274
+ name = os.path.basename(ipynb_path)
275
+ dst = os.path.join(dst_dir, name)
276
+
277
+ if os.path.exists(dst):
278
+ # Disambiguate collisions by using the relative folder path.
279
+ rel_dir = os.path.relpath(os.path.dirname(ipynb_path), base_dir)
280
+ rel_dir = "" if rel_dir == "." else rel_dir
281
+ suffix = re.sub(r"[^A-Za-z0-9]+", "_", rel_dir).strip("_")
282
+ root, ext = os.path.splitext(name)
283
+ new_name = f"{root}__{suffix}{ext}" if suffix else name
284
+ dst = os.path.join(dst_dir, new_name)
285
+
286
+ # If *still* colliding, add a numeric suffix.
287
+ if os.path.exists(dst):
288
+ i = 2
289
+ while True:
290
+ new_name = f"{root}__{suffix}__{i}{ext}" if suffix else \
291
+ f"{root}__{i}{ext}"
292
+ dst = os.path.join(dst_dir, new_name)
293
+ if not os.path.exists(dst):
294
+ break
295
+ i += 1
296
+
297
+ shutil.copy2(ipynb_path, dst)
298
+
299
+
152
300
  if __name__ == "__main__":
153
- main()
301
+ main()
@@ -24,6 +24,7 @@ extensions = [
24
24
  'sphinx.ext.viewcode',
25
25
  'numpydoc',
26
26
  'myst_parser',
27
+ 'nbsphinx',
27
28
  ]
28
29
 
29
30
  exclude_patterns = ['README.md']
@@ -40,4 +41,4 @@ html_theme = 'sphinx_rtd_theme'
40
41
  # The following setting specifies the order in which members are documented
41
42
  autodoc_member_order = 'bysource'
42
43
 
43
- numpydoc_show_class_members = False
44
+ numpydoc_show_class_members = False
@@ -0,0 +1,29 @@
1
+ .. include:: ../README.md
2
+ :parser: myst_parser.sphinx_
3
+
4
+ .. toctree::
5
+ :caption: Modules
6
+ :hidden:
7
+
8
+ modules/bandmap
9
+ modules/mdcs
10
+ modules/selfenergies
11
+ modules/distributions
12
+ modules/functions
13
+ modules/plotting
14
+ modules/settings_parameters
15
+ modules/settings_plots
16
+
17
+ .. toctree::
18
+ :caption: Tutorials
19
+ :hidden:
20
+
21
+ notebooks/verification
22
+ notebooks/srtio3
23
+ notebooks/graphene
24
+
25
+ .. toctree::
26
+ :caption: More
27
+ :hidden:
28
+
29
+ genindex
@@ -0,0 +1,5 @@
1
+ BandMap
2
+ ========
3
+
4
+ .. automodule:: xarpes.bandmap
5
+ :members:
@@ -0,0 +1,5 @@
1
+ MDCs
2
+ ========
3
+
4
+ .. automodule:: xarpes.mdcs
5
+ :members:
@@ -0,0 +1,5 @@
1
+ SelfEnergies
2
+ ============
3
+
4
+ .. automodule:: xarpes.selfenergies
5
+ :members:
@@ -0,0 +1,7 @@
1
+ Settings_parameters
2
+ ===================
3
+
4
+ .. automodule:: xarpes.settings_parameters
5
+ :members:
6
+ :undoc-members:
7
+ :show-inheritance:
@@ -0,0 +1,8 @@
1
+ Settings_plots
2
+ ==============
3
+
4
+ .. automodule:: xarpes.settings_plots
5
+ :members:
6
+ :private-members:
7
+ :undoc-members:
8
+ :show-inheritance: