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 +28 -0
- md_exafs-0.2.0/PKG-INFO +274 -0
- md_exafs-0.2.0/README.md +230 -0
- md_exafs-0.2.0/md_exafs/__init__.py +56 -0
- md_exafs-0.2.0/md_exafs/__main__.py +7 -0
- md_exafs-0.2.0/md_exafs/cache_utils.py +84 -0
- md_exafs-0.2.0/md_exafs/cli.py +2450 -0
- md_exafs-0.2.0/md_exafs/constants.py +21 -0
- md_exafs-0.2.0/md_exafs/debye_waller.py +2808 -0
- md_exafs-0.2.0/md_exafs/debye_waller_core.py +7 -0
- md_exafs-0.2.0/md_exafs/exafs_data.py +2619 -0
- md_exafs-0.2.0/md_exafs/execution.py +466 -0
- md_exafs-0.2.0/md_exafs/experimental.py +168 -0
- md_exafs-0.2.0/md_exafs/feff_configs/nscf.yaml +28 -0
- md_exafs-0.2.0/md_exafs/feff_configs/publication.yaml +26 -0
- md_exafs-0.2.0/md_exafs/feff_configs/quick.yaml +25 -0
- md_exafs-0.2.0/md_exafs/feff_input.py +464 -0
- md_exafs-0.2.0/md_exafs/feff_utils.py +1595 -0
- md_exafs-0.2.0/md_exafs/hdf5.py +568 -0
- md_exafs-0.2.0/md_exafs/hdf5_store.py +1632 -0
- md_exafs-0.2.0/md_exafs/marimo_utils.py +72 -0
- md_exafs-0.2.0/md_exafs/paths.py +384 -0
- md_exafs-0.2.0/md_exafs/pipeline.py +1494 -0
- md_exafs-0.2.0/md_exafs/potentials.py +201 -0
- md_exafs-0.2.0/md_exafs/py.typed +0 -0
- md_exafs-0.2.0/md_exafs/selection.py +234 -0
- md_exafs-0.2.0/md_exafs/spectra.py +171 -0
- md_exafs-0.2.0/md_exafs/styles/__init__.py +20 -0
- md_exafs-0.2.0/md_exafs/styles/exafs_presentation.mplstyle +64 -0
- md_exafs-0.2.0/md_exafs/styles/exafs_publication.mplstyle +66 -0
- md_exafs-0.2.0/md_exafs/viz.py +187 -0
- md_exafs-0.2.0/pyproject.toml +212 -0
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.
|
md_exafs-0.2.0/PKG-INFO
ADDED
|
@@ -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
|
+
[](https://www.python.org/)
|
|
48
|
+
[](LICENSE)
|
|
49
|
+
[](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.
|
md_exafs-0.2.0/README.md
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# MD-EXAFS (`md-exafs`)
|
|
2
|
+
|
|
3
|
+
[](https://www.python.org/)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
[](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,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
|
+
)
|