md-exafs 0.2.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.
md_exafs-0.2.0/LICENSE ADDED
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2025, Scientific Computing Department, Science and Technology Facilities Council
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,274 @@
1
+ Metadata-Version: 2.4
2
+ Name: md-exafs
3
+ Version: 0.2.0
4
+ Summary: Core engine and CLI for ensemble-averaged EXAFS spectroscopy from MD trajectories
5
+ Keywords: xafs,exafs,spectroscopy,feff,molecular-dynamics,materials-science
6
+ Author: Kane Shenton
7
+ Author-email: Kane Shenton <jkshenton@gmail.com>
8
+ License-Expression: BSD-3-Clause
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Science/Research
12
+ Classifier: Topic :: Scientific/Engineering :: Physics
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Requires-Dist: ase>=3.25,<4.0
19
+ Requires-Dist: numpy>=1.22,<3.0.0
20
+ Requires-Dist: scipy>=1.11
21
+ Requires-Dist: xraylarch>=0.9.47
22
+ Requires-Dist: pymatgen>=2024.1,<2026.0
23
+ Requires-Dist: h5py>=3.0
24
+ Requires-Dist: typer>=0.17.3,<1.0.0 ; extra == 'cli'
25
+ Requires-Dist: rich>=13.9.1,<14.0.0 ; extra == 'cli'
26
+ Requires-Dist: matplotlib>=3.5 ; extra == 'cli'
27
+ Requires-Dist: sphinx>=7.0 ; extra == 'docs'
28
+ Requires-Dist: sphinx-book-theme>=1.1.0 ; extra == 'docs'
29
+ Requires-Dist: myst-parser>=3.0 ; extra == 'docs'
30
+ Requires-Dist: sphinx-copybutton>=0.5 ; extra == 'docs'
31
+ Requires-Dist: sphinx-design>=0.5 ; extra == 'docs'
32
+ Requires-Dist: marimo>=0.16.4 ; extra == 'notebooks'
33
+ Requires-Dist: weas-widget>=0.1.26 ; extra == 'notebooks'
34
+ Requires-Dist: altair>=5.0 ; extra == 'notebooks'
35
+ Requires-Python: >=3.10
36
+ Project-URL: Bug Tracker, https://github.com/stfc/alc-dls-exafs/issues
37
+ Project-URL: Documentation, https://github.com/stfc/alc-dls-exafs#readme
38
+ Project-URL: Homepage, https://github.com/stfc/alc-dls-exafs
39
+ Project-URL: Repository, https://github.com/stfc/alc-dls-exafs
40
+ Provides-Extra: cli
41
+ Provides-Extra: docs
42
+ Provides-Extra: notebooks
43
+ Description-Content-Type: text/markdown
44
+
45
+ # MD-EXAFS (`md-exafs`)
46
+
47
+ [![Python Version](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/)
48
+ [![License](https://img.shields.io/badge/license-BSD--3--Clause-green.svg)](LICENSE)
49
+ [![Tests](https://img.shields.io/badge/tests-passing-brightgreen.svg)](tests/)
50
+
51
+ `md-exafs` provides the core scientific engine and command-line interface for calculating ensemble-averaged Extended X-ray Absorption Fine Structure (EXAFS) spectra from molecular dynamics trajectories.
52
+
53
+ The package sits at the base of the MD-EXAFS software ecosystem:
54
+ - **MD-EXAFS Core and CLI (`md-exafs`)**: Trajectory analysis, Debye–Waller extraction, FEFF card and input generation, path reconstruction, and streaming batch execution.
55
+ - **[AiiDA-FEFF](https://github.com/stfc/aiida-feff)**: AiiDA plugin for provenance tracking, multi-node HPC daemon scheduling and batch orchestration.
56
+ - **[AiiDAlab-EXAFS](https://github.com/stfc/aiidalab-exafs)**: Interactive browser interface inside Jupyter and AiiDAlab.
57
+
58
+ ---
59
+
60
+ ## Capabilities
61
+
62
+ The package implements the physical and numerical routines required to bridge thermal configurations and experimental absorption spectra:
63
+
64
+ - **Exact EXAFS reconstruction**: Evaluates the EXAFS equation for individual scattering paths using the complex electron momentum $p = \mathrm{rep} + i/\lambda$, matching Larch and FEFF conventions.
65
+ - **Shard-and-stream batch execution**: Runs FEFF in bounded chunks across available CPU cores, streams calculated spectra and per-path parameters directly into HDF5 shards (`batch_shard.h5`), and prunes scratch directories immediately to keep disk usage within fixed bounds.
66
+ - **Hybrid path storage**: Stores raw 6-column FEFF scattering parameters on native grids for per-frame paths to permit post-hoc adjustment of $S_0^2$, $\sigma^2$, or $\Delta E_0$, while pre-evaluating fine-grid $\chi(k)$ and $|\tilde{\chi}(R)|$ for the dominant composite ensemble paths.
67
+ - **Debye–Waller and ADP extraction**: Extracts path variances ($\sigma^2$), third and fourth cumulants ($C_3, C_4$), autocorrelation sample corrections ($N_\text{eff}$), and atomic displacement parameter (ADP) CIF models directly from trajectories, with support for non-orthogonal periodic cells.
68
+ - **Per-frame absorber resolution**: Supports element labels, explicit index lists, and element-relative indexing (`"Cu:0,2"`), validating chemical species consistency across static and permuted topologies.
69
+ - **Experimental alignment**: Imports beamline ASCII columns and Athena projects (`.prj`), applying $\Delta E_0$ threshold shifts and $S_0^2$ scaling on common wavenumber grids.
70
+ - **Headless visualisation**: Generates standard Altair spectral charts and 3D scattering vectors for WEAS-widget without framework dependencies.
71
+
72
+ ---
73
+
74
+ ## Installation
75
+
76
+ ### From source
77
+
78
+ ```bash
79
+ git clone https://github.com/stfc/alc-dls-exafs.git
80
+ cd alc-dls-exafs
81
+
82
+ # Core engine (zero CLI or plotting dependencies)
83
+ pip install -e .
84
+
85
+ # With command-line interface (typer, rich, matplotlib)
86
+ pip install -e ".[cli]"
87
+
88
+ # Full installation including interactive notebooks (marimo, weas-widget, altair)
89
+ pip install -e ".[cli,notebooks]"
90
+ ```
91
+
92
+ Using `uv`:
93
+ ```bash
94
+ uv pip install -e ".[cli,notebooks]"
95
+ ```
96
+
97
+ ---
98
+
99
+ ## Command-line interface
100
+
101
+ The CLI is available as `md-exafs`. The legacy alias `larch-cli` remains available for transition.
102
+
103
+ ### 1. Full pipeline execution
104
+ The `pipeline` command coordinates input generation, parallel FEFF execution, HDF5 archiving, and ensemble spectral averaging in a single run.
105
+
106
+ ```bash
107
+ # Single structure (K-edge, first absorber site)
108
+ md-exafs pipeline structure.cif Cu
109
+
110
+ # Trajectory run: process all Cu sites across all frames into HDF5
111
+ md-exafs pipeline trajectory.xyz Cu --all-sites --all-frames --hdf5 --keep-paths
112
+
113
+ # Precompute potentials on the representative structure to accelerate trajectory runs
114
+ md-exafs pipeline trajectory.xyz Cu --all-sites --all-frames --hdf5 --reuse-potentials --parallel --workers 8
115
+
116
+ # Sub-sample trajectory frames (e.g. every 5th frame)
117
+ md-exafs pipeline trajectory.xyz Cu --ase-kwargs '{"index": "::5"}'
118
+ ```
119
+
120
+ ### 2. Spectral and path analysis
121
+ The `analyze` command evaluates and plots simulated spectra and path contributions from an existing HDF5 archive (`results.h5` or `ensemble_results.h5`):
122
+
123
+ ```bash
124
+ # Plot ensemble average and path decomposition
125
+ md-exafs analyze results.h5 --plot-include average,paths --show
126
+
127
+ # Prune negligible paths (e.g. keep paths with at least 5% of peak amplitude)
128
+ md-exafs analyze results.h5 --plot-include average,paths --min-cw-ratio 5.0 --max-paths 20
129
+
130
+ # Compare against experimental data with an energy shift and amplitude factor
131
+ md-exafs analyze results.h5 --experimental measurement.dat --e0-shift 2.5 --s02 0.85
132
+ ```
133
+
134
+ ### 3. Debye–Waller extraction
135
+ Extract path variances ($\sigma^2$), cumulants ($C_3, C_4$), and ADP tensors directly from molecular dynamics configurations:
136
+
137
+ ```bash
138
+ # Calculate MSRD up to 5.0 Å from Cu absorbing sites
139
+ md-exafs debye-waller trajectory.xyz --prefix cu_dw --site-spec Cu --cutoff 5.0
140
+
141
+ # Export an average CIF structure with anisotropic thermal ellipsoids
142
+ md-exafs debye-waller trajectory.xyz --prefix cu_dw --adp-cif
143
+ ```
144
+
145
+ ### 4. Modular workflow steps
146
+
147
+ ```bash
148
+ # Step 1: Generate FEFF inputs only
149
+ md-exafs generate structure.cif Cu --output feff_inputs/ --radius 6.0 --edge K
150
+
151
+ # Step 2: Run FEFF across generated input directories
152
+ md-exafs run-feff feff_inputs/ --parallel --workers 4
153
+
154
+ # Step 3: Analyse results
155
+ md-exafs analyze feff_inputs/ --output plots/ --plot-include sites --show
156
+ ```
157
+
158
+ ---
159
+
160
+ ## Python API quickstart
161
+
162
+ The `md_exafs` package can be imported directly in Python scripts and computational notebooks.
163
+
164
+ ### Building FEFF inputs
165
+ ```python
166
+ from ase.build import bulk
167
+ from md_exafs.feff_input import FeffConfig, build_feff_inp
168
+
169
+ atoms = bulk("Cu", "fcc", a=3.61)
170
+ config = FeffConfig.from_preset("quick")
171
+ config.radius = 5.5
172
+
173
+ feff_inp = build_feff_inp(atoms, config=config, absorber_idx=0)
174
+ ```
175
+
176
+ ### Evaluating individual scattering paths
177
+ ```python
178
+ import numpy as np
179
+ from md_exafs.paths import path_chi
180
+
181
+ # Calculate chi(k) from raw 6-column FEFF scattering factors
182
+ k_out = np.linspace(2.0, 16.0, 141)
183
+ chi = path_chi(
184
+ k_native=k_coarse,
185
+ feff_data=feff_data_6cols,
186
+ r_eff=2.55,
187
+ degeneracy=12.0,
188
+ k_out=k_out,
189
+ sigma2=0.005,
190
+ s02=0.9,
191
+ e0_shift=1.5,
192
+ )
193
+ ```
194
+
195
+ ### Batch execution and shard aggregation
196
+ ```python
197
+ from pathlib import Path
198
+ from md_exafs.execution import BatchExecutor, FeffTask, merge_shards
199
+ from md_exafs.hdf5 import ArchiveReader
200
+
201
+ tasks = [
202
+ FeffTask(frame_idx=0, site_idx=0, input_dir=Path("run_0000"), absorber_element="Cu"),
203
+ FeffTask(frame_idx=0, site_idx=1, input_dir=Path("run_0001"), absorber_element="Cu"),
204
+ ]
205
+
206
+ # Run chunked execution into a self-contained shard
207
+ executor = BatchExecutor(tasks, output_h5="batch_shard_0.h5", chunk_size=128, n_workers=4)
208
+ executor.run()
209
+
210
+ # Merge multiple shards into an ensemble archive
211
+ ensemble_file = merge_shards(
212
+ shard_paths=["batch_shard_0.h5", "batch_shard_1.h5"],
213
+ ensemble_path="ensemble_results.h5",
214
+ )
215
+
216
+ # Inspect results through the unified archive reader
217
+ reader = ArchiveReader(ensemble_file)
218
+ print(f"Grand average chi points: {len(reader.chi)}")
219
+ ```
220
+
221
+ ### In-memory Debye–Waller screening
222
+ ```python
223
+ from ase.io import read
224
+ from md_exafs.debye_waller import calculate_grouped_msrd
225
+
226
+ trajectory = read("trajectory.xyz", index=":")
227
+ msrd_results = calculate_grouped_msrd(trajectory, site_spec="Cu", max_reff=5.0)
228
+
229
+ for path in msrd_results["paths"][:5]:
230
+ print(f"{path['label']}: R_eff = {path['r_eff']:.3f} Å, σ² = {path['sigma2']:.5f} Ų")
231
+ ```
232
+
233
+ ---
234
+
235
+ ## Documentation
236
+
237
+ - [System architecture](docs/architecture.md): Description of the shard-and-stream and hybrid storage models.
238
+ - [CLI reference](docs/cli.md): Command documentation and parameter descriptions.
239
+ - [Python API reference](docs/python_api.md): Function and class signatures for library modules.
240
+ - [Ecosystem integration](docs/ecosystem.md): Details on coupling with `AiiDA-FEFF` and `AiiDAlab-EXAFS`.
241
+
242
+ ---
243
+
244
+ ## Citation
245
+
246
+ If you use `md-exafs` in your research, please cite:
247
+
248
+ ```bibtex
249
+ @software{md_exafs,
250
+ author = {Kane Shenton and Joshua Elliott and Alin M. Elena},
251
+ title = {MD-EXAFS: Ensemble-Averaged EXAFS Spectroscopy from Molecular Dynamics Trajectories},
252
+ year = {2026},
253
+ publisher = {GitHub},
254
+ journal = {GitHub repository},
255
+ howpublished = {\url{https://github.com/stfc/alc-dls-exafs}}
256
+ }
257
+ ```
258
+
259
+ See [`CITATION.cff`](CITATION.cff) for full citation metadata.
260
+
261
+ ---
262
+
263
+ ## License
264
+
265
+ This project is licensed under the BSD-3-Clause License. See [LICENSE](LICENSE) for details.
266
+
267
+ ---
268
+
269
+ ## Acknowledgments
270
+
271
+ - Built on top of the excellent [Larch](https://xraypy.github.io/xraylarch/) project
272
+ - FEFF calculations powered by the [FEFF Project](https://feff.phys.washington.edu/). Specifically, the Open Source version of FEFF8 (FEFF8L) is used by default.
273
+ - Structure handling via [ASE](https://wiki.fysik.dtu.dk/ase/) and [pymatgen](https://pymatgen.org/)
274
+ - Trajectory-based ensemble analysis inspired by EDACA, which pioneered the software methodology and served as an invaluable reference during pipeline validation.
@@ -0,0 +1,230 @@
1
+ # MD-EXAFS (`md-exafs`)
2
+
3
+ [![Python Version](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/)
4
+ [![License](https://img.shields.io/badge/license-BSD--3--Clause-green.svg)](LICENSE)
5
+ [![Tests](https://img.shields.io/badge/tests-passing-brightgreen.svg)](tests/)
6
+
7
+ `md-exafs` provides the core scientific engine and command-line interface for calculating ensemble-averaged Extended X-ray Absorption Fine Structure (EXAFS) spectra from molecular dynamics trajectories.
8
+
9
+ The package sits at the base of the MD-EXAFS software ecosystem:
10
+ - **MD-EXAFS Core and CLI (`md-exafs`)**: Trajectory analysis, Debye–Waller extraction, FEFF card and input generation, path reconstruction, and streaming batch execution.
11
+ - **[AiiDA-FEFF](https://github.com/stfc/aiida-feff)**: AiiDA plugin for provenance tracking, multi-node HPC daemon scheduling and batch orchestration.
12
+ - **[AiiDAlab-EXAFS](https://github.com/stfc/aiidalab-exafs)**: Interactive browser interface inside Jupyter and AiiDAlab.
13
+
14
+ ---
15
+
16
+ ## Capabilities
17
+
18
+ The package implements the physical and numerical routines required to bridge thermal configurations and experimental absorption spectra:
19
+
20
+ - **Exact EXAFS reconstruction**: Evaluates the EXAFS equation for individual scattering paths using the complex electron momentum $p = \mathrm{rep} + i/\lambda$, matching Larch and FEFF conventions.
21
+ - **Shard-and-stream batch execution**: Runs FEFF in bounded chunks across available CPU cores, streams calculated spectra and per-path parameters directly into HDF5 shards (`batch_shard.h5`), and prunes scratch directories immediately to keep disk usage within fixed bounds.
22
+ - **Hybrid path storage**: Stores raw 6-column FEFF scattering parameters on native grids for per-frame paths to permit post-hoc adjustment of $S_0^2$, $\sigma^2$, or $\Delta E_0$, while pre-evaluating fine-grid $\chi(k)$ and $|\tilde{\chi}(R)|$ for the dominant composite ensemble paths.
23
+ - **Debye–Waller and ADP extraction**: Extracts path variances ($\sigma^2$), third and fourth cumulants ($C_3, C_4$), autocorrelation sample corrections ($N_\text{eff}$), and atomic displacement parameter (ADP) CIF models directly from trajectories, with support for non-orthogonal periodic cells.
24
+ - **Per-frame absorber resolution**: Supports element labels, explicit index lists, and element-relative indexing (`"Cu:0,2"`), validating chemical species consistency across static and permuted topologies.
25
+ - **Experimental alignment**: Imports beamline ASCII columns and Athena projects (`.prj`), applying $\Delta E_0$ threshold shifts and $S_0^2$ scaling on common wavenumber grids.
26
+ - **Headless visualisation**: Generates standard Altair spectral charts and 3D scattering vectors for WEAS-widget without framework dependencies.
27
+
28
+ ---
29
+
30
+ ## Installation
31
+
32
+ ### From source
33
+
34
+ ```bash
35
+ git clone https://github.com/stfc/alc-dls-exafs.git
36
+ cd alc-dls-exafs
37
+
38
+ # Core engine (zero CLI or plotting dependencies)
39
+ pip install -e .
40
+
41
+ # With command-line interface (typer, rich, matplotlib)
42
+ pip install -e ".[cli]"
43
+
44
+ # Full installation including interactive notebooks (marimo, weas-widget, altair)
45
+ pip install -e ".[cli,notebooks]"
46
+ ```
47
+
48
+ Using `uv`:
49
+ ```bash
50
+ uv pip install -e ".[cli,notebooks]"
51
+ ```
52
+
53
+ ---
54
+
55
+ ## Command-line interface
56
+
57
+ The CLI is available as `md-exafs`. The legacy alias `larch-cli` remains available for transition.
58
+
59
+ ### 1. Full pipeline execution
60
+ The `pipeline` command coordinates input generation, parallel FEFF execution, HDF5 archiving, and ensemble spectral averaging in a single run.
61
+
62
+ ```bash
63
+ # Single structure (K-edge, first absorber site)
64
+ md-exafs pipeline structure.cif Cu
65
+
66
+ # Trajectory run: process all Cu sites across all frames into HDF5
67
+ md-exafs pipeline trajectory.xyz Cu --all-sites --all-frames --hdf5 --keep-paths
68
+
69
+ # Precompute potentials on the representative structure to accelerate trajectory runs
70
+ md-exafs pipeline trajectory.xyz Cu --all-sites --all-frames --hdf5 --reuse-potentials --parallel --workers 8
71
+
72
+ # Sub-sample trajectory frames (e.g. every 5th frame)
73
+ md-exafs pipeline trajectory.xyz Cu --ase-kwargs '{"index": "::5"}'
74
+ ```
75
+
76
+ ### 2. Spectral and path analysis
77
+ The `analyze` command evaluates and plots simulated spectra and path contributions from an existing HDF5 archive (`results.h5` or `ensemble_results.h5`):
78
+
79
+ ```bash
80
+ # Plot ensemble average and path decomposition
81
+ md-exafs analyze results.h5 --plot-include average,paths --show
82
+
83
+ # Prune negligible paths (e.g. keep paths with at least 5% of peak amplitude)
84
+ md-exafs analyze results.h5 --plot-include average,paths --min-cw-ratio 5.0 --max-paths 20
85
+
86
+ # Compare against experimental data with an energy shift and amplitude factor
87
+ md-exafs analyze results.h5 --experimental measurement.dat --e0-shift 2.5 --s02 0.85
88
+ ```
89
+
90
+ ### 3. Debye–Waller extraction
91
+ Extract path variances ($\sigma^2$), cumulants ($C_3, C_4$), and ADP tensors directly from molecular dynamics configurations:
92
+
93
+ ```bash
94
+ # Calculate MSRD up to 5.0 Å from Cu absorbing sites
95
+ md-exafs debye-waller trajectory.xyz --prefix cu_dw --site-spec Cu --cutoff 5.0
96
+
97
+ # Export an average CIF structure with anisotropic thermal ellipsoids
98
+ md-exafs debye-waller trajectory.xyz --prefix cu_dw --adp-cif
99
+ ```
100
+
101
+ ### 4. Modular workflow steps
102
+
103
+ ```bash
104
+ # Step 1: Generate FEFF inputs only
105
+ md-exafs generate structure.cif Cu --output feff_inputs/ --radius 6.0 --edge K
106
+
107
+ # Step 2: Run FEFF across generated input directories
108
+ md-exafs run-feff feff_inputs/ --parallel --workers 4
109
+
110
+ # Step 3: Analyse results
111
+ md-exafs analyze feff_inputs/ --output plots/ --plot-include sites --show
112
+ ```
113
+
114
+ ---
115
+
116
+ ## Python API quickstart
117
+
118
+ The `md_exafs` package can be imported directly in Python scripts and computational notebooks.
119
+
120
+ ### Building FEFF inputs
121
+ ```python
122
+ from ase.build import bulk
123
+ from md_exafs.feff_input import FeffConfig, build_feff_inp
124
+
125
+ atoms = bulk("Cu", "fcc", a=3.61)
126
+ config = FeffConfig.from_preset("quick")
127
+ config.radius = 5.5
128
+
129
+ feff_inp = build_feff_inp(atoms, config=config, absorber_idx=0)
130
+ ```
131
+
132
+ ### Evaluating individual scattering paths
133
+ ```python
134
+ import numpy as np
135
+ from md_exafs.paths import path_chi
136
+
137
+ # Calculate chi(k) from raw 6-column FEFF scattering factors
138
+ k_out = np.linspace(2.0, 16.0, 141)
139
+ chi = path_chi(
140
+ k_native=k_coarse,
141
+ feff_data=feff_data_6cols,
142
+ r_eff=2.55,
143
+ degeneracy=12.0,
144
+ k_out=k_out,
145
+ sigma2=0.005,
146
+ s02=0.9,
147
+ e0_shift=1.5,
148
+ )
149
+ ```
150
+
151
+ ### Batch execution and shard aggregation
152
+ ```python
153
+ from pathlib import Path
154
+ from md_exafs.execution import BatchExecutor, FeffTask, merge_shards
155
+ from md_exafs.hdf5 import ArchiveReader
156
+
157
+ tasks = [
158
+ FeffTask(frame_idx=0, site_idx=0, input_dir=Path("run_0000"), absorber_element="Cu"),
159
+ FeffTask(frame_idx=0, site_idx=1, input_dir=Path("run_0001"), absorber_element="Cu"),
160
+ ]
161
+
162
+ # Run chunked execution into a self-contained shard
163
+ executor = BatchExecutor(tasks, output_h5="batch_shard_0.h5", chunk_size=128, n_workers=4)
164
+ executor.run()
165
+
166
+ # Merge multiple shards into an ensemble archive
167
+ ensemble_file = merge_shards(
168
+ shard_paths=["batch_shard_0.h5", "batch_shard_1.h5"],
169
+ ensemble_path="ensemble_results.h5",
170
+ )
171
+
172
+ # Inspect results through the unified archive reader
173
+ reader = ArchiveReader(ensemble_file)
174
+ print(f"Grand average chi points: {len(reader.chi)}")
175
+ ```
176
+
177
+ ### In-memory Debye–Waller screening
178
+ ```python
179
+ from ase.io import read
180
+ from md_exafs.debye_waller import calculate_grouped_msrd
181
+
182
+ trajectory = read("trajectory.xyz", index=":")
183
+ msrd_results = calculate_grouped_msrd(trajectory, site_spec="Cu", max_reff=5.0)
184
+
185
+ for path in msrd_results["paths"][:5]:
186
+ print(f"{path['label']}: R_eff = {path['r_eff']:.3f} Å, σ² = {path['sigma2']:.5f} Ų")
187
+ ```
188
+
189
+ ---
190
+
191
+ ## Documentation
192
+
193
+ - [System architecture](docs/architecture.md): Description of the shard-and-stream and hybrid storage models.
194
+ - [CLI reference](docs/cli.md): Command documentation and parameter descriptions.
195
+ - [Python API reference](docs/python_api.md): Function and class signatures for library modules.
196
+ - [Ecosystem integration](docs/ecosystem.md): Details on coupling with `AiiDA-FEFF` and `AiiDAlab-EXAFS`.
197
+
198
+ ---
199
+
200
+ ## Citation
201
+
202
+ If you use `md-exafs` in your research, please cite:
203
+
204
+ ```bibtex
205
+ @software{md_exafs,
206
+ author = {Kane Shenton and Joshua Elliott and Alin M. Elena},
207
+ title = {MD-EXAFS: Ensemble-Averaged EXAFS Spectroscopy from Molecular Dynamics Trajectories},
208
+ year = {2026},
209
+ publisher = {GitHub},
210
+ journal = {GitHub repository},
211
+ howpublished = {\url{https://github.com/stfc/alc-dls-exafs}}
212
+ }
213
+ ```
214
+
215
+ See [`CITATION.cff`](CITATION.cff) for full citation metadata.
216
+
217
+ ---
218
+
219
+ ## License
220
+
221
+ This project is licensed under the BSD-3-Clause License. See [LICENSE](LICENSE) for details.
222
+
223
+ ---
224
+
225
+ ## Acknowledgments
226
+
227
+ - Built on top of the excellent [Larch](https://xraypy.github.io/xraylarch/) project
228
+ - FEFF calculations powered by the [FEFF Project](https://feff.phys.washington.edu/). Specifically, the Open Source version of FEFF8 (FEFF8L) is used by default.
229
+ - Structure handling via [ASE](https://wiki.fysik.dtu.dk/ase/) and [pymatgen](https://pymatgen.org/)
230
+ - Trajectory-based ensemble analysis inspired by EDACA, which pioneered the software methodology and served as an invaluable reference during pipeline validation.
@@ -0,0 +1,56 @@
1
+ """MD-EXAFS Core Engine - Trajectory analysis and path-resolved EXAFS reconstruction."""
2
+
3
+ from pathlib import Path
4
+
5
+ from .constants import ETOK, HBAR2_OVER_2M_EV_ANGSTROM2
6
+ from .exafs_data import PathAggregator, PathContribution
7
+ from .feff_input import FeffConfig, build_feff_inp, normalize_tag
8
+ from .hdf5 import (
9
+ ArchiveReader,
10
+ BatchShardReader,
11
+ BatchShardWriter,
12
+ EnsembleReader,
13
+ EnsembleWriter,
14
+ ExafsHDF5Store,
15
+ )
16
+ from .paths import PathResult, make_path_key, path_chi
17
+ from .potentials import PotentialsManager
18
+ from .selection import (
19
+ parse_frame_slice,
20
+ resolve_frame_absorbers,
21
+ resolve_trajectory_absorbers,
22
+ )
23
+ from .spectra import average_chi_arrays, format_chi_ascii, xftf_arrays
24
+
25
+ __version__ = "0.2.0"
26
+
27
+ # Default cache directory for FEFF calculations and results
28
+ DEFAULT_CACHE_DIR = Path.home() / ".larch_cache"
29
+
30
+ __all__ = [
31
+ "__version__",
32
+ "DEFAULT_CACHE_DIR",
33
+ "HBAR2_OVER_2M_EV_ANGSTROM2",
34
+ "ETOK",
35
+ "FeffConfig",
36
+ "build_feff_inp",
37
+ "normalize_tag",
38
+ "PathResult",
39
+ "path_chi",
40
+ "make_path_key",
41
+ "xftf_arrays",
42
+ "average_chi_arrays",
43
+ "format_chi_ascii",
44
+ "resolve_frame_absorbers",
45
+ "resolve_trajectory_absorbers",
46
+ "parse_frame_slice",
47
+ "PotentialsManager",
48
+ "BatchShardWriter",
49
+ "BatchShardReader",
50
+ "EnsembleWriter",
51
+ "EnsembleReader",
52
+ "ArchiveReader",
53
+ "ExafsHDF5Store",
54
+ "PathAggregator",
55
+ "PathContribution",
56
+ ]
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env python3
2
+ """Entry point for the larch CLI wrapper."""
3
+
4
+ from larch_cli_wrapper.cli import app
5
+
6
+ if __name__ == "__main__":
7
+ app()
@@ -0,0 +1,84 @@
1
+ """Cache utilities for EXAFS processing pipeline."""
2
+
3
+ import hashlib
4
+ import pickle
5
+ import warnings
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+ from ase import Atoms
10
+
11
+
12
+ def get_structure_hash(atoms: Atoms) -> str:
13
+ """Generate a hash for atomic structure for caching purposes."""
14
+ return hashlib.md5( # noqa: S324
15
+ str(atoms.get_positions()).encode() + str(atoms.get_chemical_symbols()).encode()
16
+ ).hexdigest()[:16]
17
+
18
+
19
+ def load_from_cache(
20
+ cache_key: str, cache_dir: str | Path | None, force_recalculate: bool = False
21
+ ) -> tuple[Any, Any] | None:
22
+ """Load cached results if available and not forcing recalculation.
23
+
24
+ Args:
25
+ cache_key: Unique identifier for the cached data
26
+ cache_dir: Directory where cache files are stored
27
+ force_recalculate: If True, ignore existing cache and return None
28
+
29
+ Returns:
30
+ Tuple of (k, chi) arrays if cache hit, None if cache miss or error
31
+
32
+ Note:
33
+ The returned chi array is complex (EXAFS chi(k) data) and k array
34
+ is real-valued k-space values.
35
+ """
36
+ if not cache_dir:
37
+ return None
38
+ cache_file = Path(cache_dir) / f"{cache_key}.pkl"
39
+ if cache_file.exists() and not force_recalculate:
40
+ try:
41
+ with open(cache_file, "rb") as f:
42
+ cached_data = pickle.load(f) # noqa: S301 - controlled usage
43
+ return cached_data["k"], cached_data["chi"]
44
+ except (EOFError, pickle.UnpicklingError, KeyError):
45
+ try:
46
+ cache_file.unlink()
47
+ except OSError:
48
+ # If we can't delete the cache file, just let the user know
49
+ warnings.warn(
50
+ f"Failed to delete cache file {cache_file}",
51
+ UserWarning,
52
+ stacklevel=2,
53
+ )
54
+ return None
55
+
56
+
57
+ def save_to_cache(
58
+ cache_key: str, chi: Any, k: Any, cache_dir: str | Path | None
59
+ ) -> None:
60
+ """Save processing results to cache for future use.
61
+
62
+ Args:
63
+ cache_key: Unique identifier for the cached data
64
+ chi: EXAFS chi(k) data (complex numpy array)
65
+ k: k-space values (real numpy array)
66
+ cache_dir: Directory where cache files are stored
67
+
68
+ Note:
69
+ Always stores complex chi data directly. Silently continues on cache
70
+ write failures with a warning.
71
+ """
72
+ if not cache_dir:
73
+ return
74
+ cache_file = Path(cache_dir) / f"{cache_key}.pkl"
75
+ cache_file.parent.mkdir(parents=True, exist_ok=True)
76
+ cached_data = {"chi": chi, "k": k}
77
+ try:
78
+ with open(cache_file, "wb") as f:
79
+ pickle.dump(cached_data, f)
80
+ except OSError:
81
+ # If we can't write to cache, continue without caching but inform the user
82
+ warnings.warn(
83
+ f"Failed to write cache file {cache_file}", UserWarning, stacklevel=2
84
+ )