aiida-feff 0.1.0a1__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.
- aiida_feff-0.1.0a1/LICENSE +28 -0
- aiida_feff-0.1.0a1/PKG-INFO +587 -0
- aiida_feff-0.1.0a1/README.md +543 -0
- aiida_feff-0.1.0a1/pyproject.toml +193 -0
- aiida_feff-0.1.0a1/setup.cfg +4 -0
- aiida_feff-0.1.0a1/src/aiida_feff/__init__.py +14 -0
- aiida_feff-0.1.0a1/src/aiida_feff/calcfunctions/__init__.py +0 -0
- aiida_feff-0.1.0a1/src/aiida_feff/calcfunctions/archive.py +129 -0
- aiida_feff-0.1.0a1/src/aiida_feff/calcfunctions/exafs.py +111 -0
- aiida_feff-0.1.0a1/src/aiida_feff/calcfunctions/experimental.py +200 -0
- aiida_feff-0.1.0a1/src/aiida_feff/calcfunctions/larch.py +102 -0
- aiida_feff-0.1.0a1/src/aiida_feff/calcfunctions/path_contributions.py +192 -0
- aiida_feff-0.1.0a1/src/aiida_feff/calculations/__init__.py +0 -0
- aiida_feff-0.1.0a1/src/aiida_feff/calculations/_aggregate_paths.py +424 -0
- aiida_feff-0.1.0a1/src/aiida_feff/calculations/_run_batch.py +465 -0
- aiida_feff-0.1.0a1/src/aiida_feff/calculations/feff.py +506 -0
- aiida_feff-0.1.0a1/src/aiida_feff/calculations/feff_batch.py +482 -0
- aiida_feff-0.1.0a1/src/aiida_feff/cli.py +130 -0
- aiida_feff-0.1.0a1/src/aiida_feff/constants.py +19 -0
- aiida_feff-0.1.0a1/src/aiida_feff/data/__init__.py +0 -0
- aiida_feff-0.1.0a1/src/aiida_feff/data/archive.py +163 -0
- aiida_feff-0.1.0a1/src/aiida_feff/data/parameters.py +250 -0
- aiida_feff-0.1.0a1/src/aiida_feff/data/pathcontributions.py +288 -0
- aiida_feff-0.1.0a1/src/aiida_feff/data/xasdata.py +129 -0
- aiida_feff-0.1.0a1/src/aiida_feff/parsers/__init__.py +0 -0
- aiida_feff-0.1.0a1/src/aiida_feff/parsers/feff.py +241 -0
- aiida_feff-0.1.0a1/src/aiida_feff/parsers/feff_batch.py +165 -0
- aiida_feff-0.1.0a1/src/aiida_feff/utils.py +139 -0
- aiida_feff-0.1.0a1/src/aiida_feff/versions.py +67 -0
- aiida_feff-0.1.0a1/src/aiida_feff/visualise.py +225 -0
- aiida_feff-0.1.0a1/src/aiida_feff/workflows/__init__.py +0 -0
- aiida_feff-0.1.0a1/src/aiida_feff/workflows/ensemble.py +993 -0
- aiida_feff-0.1.0a1/src/aiida_feff.egg-info/PKG-INFO +587 -0
- aiida_feff-0.1.0a1/src/aiida_feff.egg-info/SOURCES.txt +51 -0
- aiida_feff-0.1.0a1/src/aiida_feff.egg-info/dependency_links.txt +1 -0
- aiida_feff-0.1.0a1/src/aiida_feff.egg-info/entry_points.txt +19 -0
- aiida_feff-0.1.0a1/src/aiida_feff.egg-info/requires.txt +24 -0
- aiida_feff-0.1.0a1/src/aiida_feff.egg-info/top_level.txt +1 -0
- aiida_feff-0.1.0a1/tests/test_aggregate_paths.py +287 -0
- aiida_feff-0.1.0a1/tests/test_batch.py +557 -0
- aiida_feff-0.1.0a1/tests/test_calculations.py +350 -0
- aiida_feff-0.1.0a1/tests/test_cli.py +72 -0
- aiida_feff-0.1.0a1/tests/test_data.py +411 -0
- aiida_feff-0.1.0a1/tests/test_ensemble.py +123 -0
- aiida_feff-0.1.0a1/tests/test_ensemble_run.py +763 -0
- aiida_feff-0.1.0a1/tests/test_exafs.py +227 -0
- aiida_feff-0.1.0a1/tests/test_experimental.py +188 -0
- aiida_feff-0.1.0a1/tests/test_larch_ft.py +180 -0
- aiida_feff-0.1.0a1/tests/test_parsers.py +260 -0
- aiida_feff-0.1.0a1/tests/test_path_contributions.py +554 -0
- aiida_feff-0.1.0a1/tests/test_utils.py +103 -0
- aiida_feff-0.1.0a1/tests/test_versions.py +67 -0
- aiida_feff-0.1.0a1/tests/test_visualise.py +137 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, UKRI 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,587 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aiida-feff
|
|
3
|
+
Version: 0.1.0a1
|
|
4
|
+
Summary: AiiDA plugin for FEFF: provenance-tracked EXAFS calculations
|
|
5
|
+
Author-email: "J. Kane Shenton" <kane.shenton@stfc.ac.uk>
|
|
6
|
+
License-Expression: BSD-3-Clause
|
|
7
|
+
Project-URL: Home, https://github.com/stfc/aiida-feff
|
|
8
|
+
Project-URL: Documentation, https://github.com/stfc/aiida-feff
|
|
9
|
+
Project-URL: Source, https://github.com/stfc/aiida-feff
|
|
10
|
+
Project-URL: Tracker, https://github.com/stfc/aiida-feff/issues
|
|
11
|
+
Keywords: aiida,feff,exafs,xas,md-exafs,larch,xraylarch,ase,pymatgen
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Framework :: AiiDA
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Chemistry
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: aiida-core<3,>=2.3
|
|
24
|
+
Requires-Dist: numpy>=1.21
|
|
25
|
+
Requires-Dist: pymatgen<2026,>=2024.1
|
|
26
|
+
Requires-Dist: ase>=3.22
|
|
27
|
+
Requires-Dist: xraylarch>=0.9.80
|
|
28
|
+
Requires-Dist: h5py>=3.0
|
|
29
|
+
Requires-Dist: md-exafs<0.4,>=0.3.0
|
|
30
|
+
Provides-Extra: plots
|
|
31
|
+
Requires-Dist: matplotlib>=3.6; extra == "plots"
|
|
32
|
+
Provides-Extra: testing
|
|
33
|
+
Requires-Dist: pytest>=7.0; extra == "testing"
|
|
34
|
+
Requires-Dist: pytest-cov; extra == "testing"
|
|
35
|
+
Requires-Dist: coveralls; extra == "testing"
|
|
36
|
+
Provides-Extra: pre-commit
|
|
37
|
+
Requires-Dist: pre-commit>=3.0; extra == "pre-commit"
|
|
38
|
+
Requires-Dist: ruff>=0.6.0; extra == "pre-commit"
|
|
39
|
+
Requires-Dist: mypy>=1.10; extra == "pre-commit"
|
|
40
|
+
Provides-Extra: docs
|
|
41
|
+
Requires-Dist: sphinx>=5; extra == "docs"
|
|
42
|
+
Requires-Dist: sphinx-rtd-theme; extra == "docs"
|
|
43
|
+
Dynamic: license-file
|
|
44
|
+
|
|
45
|
+
[](https://github.com/stfc/aiida-feff/releases)
|
|
46
|
+
[](https://pypi.org/project/aiida-feff/)
|
|
47
|
+
[](https://github.com/stfc/aiida-feff/actions)
|
|
48
|
+
[](https://coveralls.io/github/stfc/aiida-feff?branch=main)
|
|
49
|
+
|
|
50
|
+
# aiida-feff
|
|
51
|
+
|
|
52
|
+
An [AiiDA](https://www.aiida.net) plugin for the
|
|
53
|
+
[FEFF](http://feffproject.org) real-space multiple-scattering code,
|
|
54
|
+
enabling fully provenance-tracked EXAFS calculations —
|
|
55
|
+
including ensemble averaging over MD snapshots via
|
|
56
|
+
[larch](https://xraypy.github.io/xraylarch/).
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Features
|
|
61
|
+
|
|
62
|
+
| Component | Description |
|
|
63
|
+
|-----------|-------------|
|
|
64
|
+
| `FeffCalculation` | CalcJob wrapping a single FEFF run; builds `feff.inp` from `StructureData` + `FeffParameters` |
|
|
65
|
+
| `FeffParser` | Parses `chi.dat` into `XasData` output nodes |
|
|
66
|
+
| `FeffBatchCalculation` | CalcJob that runs *N* FEFF instances in one Slurm job (one per core); for HPC ensemble runs |
|
|
67
|
+
| `FeffBatchParser` | Parses all per-snapshot outputs from a batch job into a dynamic namespace of `XasData` nodes |
|
|
68
|
+
| `FeffParameters` | Validated `Dict` subclass for FEFF control cards |
|
|
69
|
+
| `XasData` | `ArrayData` subclass storing χ(k), plus μ(E) on experimental imports |
|
|
70
|
+
| `EnsembleExafsWorkChain` | Fan-out over MD snapshots → one `ExafsArchiveData` and ensemble-averaged χ(k); single-job and batch modes |
|
|
71
|
+
| `calcfunctions` | Larch post-processing (FT), archive projection, experimental imports |
|
|
72
|
+
| `visualise` | Matplotlib helpers for χ(k) and χ(R) plots |
|
|
73
|
+
|
|
74
|
+
## Installation
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
pip install aiida-feff
|
|
78
|
+
verdi plugin list aiida.calculations # should show feff.feff and feff.feff_batch
|
|
79
|
+
verdi plugin list aiida.workflows # should show feff.ensemble
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
For plotting (optional):
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
pip install aiida-feff[plots]
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Quick start
|
|
89
|
+
|
|
90
|
+
### Experimental spectrum import
|
|
91
|
+
|
|
92
|
+
`aiida_feff.calcfunctions.experimental.import_experimental_spectrum` adapts
|
|
93
|
+
Larch's readers to a provenance-tracked `XasData` node. Pass a stored
|
|
94
|
+
`SinglefileData` upload and a `Dict` of Larch import options, such as plain
|
|
95
|
+
text column labels or a selected Athena group. The raw file remains an input
|
|
96
|
+
node; the plugin does not implement a competing file parser.
|
|
97
|
+
|
|
98
|
+
`scale_simulated_spectrum` stores a separate $S_0^2$/ΔE₀-adjusted simulated
|
|
99
|
+
`XasData` node for reproducible experimental comparisons.
|
|
100
|
+
|
|
101
|
+
### 1. Register your FEFF code
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
verdi code setup \
|
|
105
|
+
--label feff \
|
|
106
|
+
--computer localhost \
|
|
107
|
+
--input-plugin feff.feff \
|
|
108
|
+
--remote-abs-path /path/to/feff
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### 2. Run a single-site EXAFS calculation
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
from aiida import load_profile, orm
|
|
115
|
+
from aiida.engine import run
|
|
116
|
+
from aiida_feff.calculations.feff import FeffCalculation
|
|
117
|
+
from aiida_feff.data.parameters import FeffParameters
|
|
118
|
+
|
|
119
|
+
load_profile()
|
|
120
|
+
|
|
121
|
+
structure = orm.StructureData(cell=[[2.87,0,0],[0,2.87,0],[0,0,2.87]])
|
|
122
|
+
structure.append_atom(position=(0,0,0), symbols='Fe')
|
|
123
|
+
structure.append_atom(position=(1.435,1.435,1.435), symbols='Fe')
|
|
124
|
+
|
|
125
|
+
params = FeffParameters(dict={
|
|
126
|
+
"edge": "K",
|
|
127
|
+
"spectrum_type": "EXAFS",
|
|
128
|
+
"radius": 5.5, # FEFF RPATH / cluster radius in Å
|
|
129
|
+
"s02": 1.0,
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
result = run(FeffCalculation,
|
|
133
|
+
code=orm.load_code('feff@localhost'),
|
|
134
|
+
structure=structure,
|
|
135
|
+
parameters=params,
|
|
136
|
+
metadata={"options": {
|
|
137
|
+
"resources": {"num_machines": 1},
|
|
138
|
+
"max_wallclock_seconds": 300,
|
|
139
|
+
}},
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
xas = result['xas_data']
|
|
143
|
+
print(f"chi(k) shape: {xas.chi_k.shape}")
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### 3. Ensemble EXAFS from an MD trajectory (localhost / dev)
|
|
147
|
+
|
|
148
|
+
> **Recommended environment:** the DevContainer in `.devcontainer/` gives you
|
|
149
|
+
> a full IDE against a *real* AiiDA profile: SQLite storage, a RabbitMQ broker,
|
|
150
|
+
> a running daemon, and both `feff@localhost` and `python3@localhost`
|
|
151
|
+
> registered. Calculations you run there produce real nodes and a real
|
|
152
|
+
> provenance graph, so `verdi` works as it would anywhere else.
|
|
153
|
+
>
|
|
154
|
+
> It runs on the host's own architecture, so it is equally at home in
|
|
155
|
+
> **GitHub Codespaces** (Code → Codespaces → Create) and on a local Docker or
|
|
156
|
+
> Podman. Verified end to end on Apple Silicon: `post-create.sh` completes,
|
|
157
|
+
> FEFF8L runs, and the full test suite passes inside the container.
|
|
158
|
+
>
|
|
159
|
+
> FEFF8L is not downloaded; it ships inside the `xraylarch` dependency and the
|
|
160
|
+
> container points a code at it. On arm64 those x86_64 binaries run through
|
|
161
|
+
> the runtime's qemu handler, for which post-create installs the x86_64
|
|
162
|
+
> loader. No PostgreSQL is involved: storage is `core.sqlite_dos`, the same
|
|
163
|
+
> backend the tests use.
|
|
164
|
+
>
|
|
165
|
+
> **Podman users:** set `dockerComposeFile` in
|
|
166
|
+
> `.devcontainer/devcontainer.json` to
|
|
167
|
+
> `["docker-compose.yml", "docker-compose.podman.yml"]`. The override adds
|
|
168
|
+
> `userns_mode: keep-id`, which Docker Engine rejects and which therefore
|
|
169
|
+
> cannot live in the base file.
|
|
170
|
+
>
|
|
171
|
+
> *Outside the container you need a broker and daemon running, a working FEFF
|
|
172
|
+
> executable, and a Python interpreter registered as an installed code
|
|
173
|
+
> (`verdi code create core.code.installed ...`, e.g. `python3@localhost`) for
|
|
174
|
+
> path aggregation.*
|
|
175
|
+
|
|
176
|
+
The synthetic example covers the whole pipeline and doubles as the smoke test
|
|
177
|
+
CI runs:
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
# Serial: one scheduler job per snapshot, with per-path contributions stored
|
|
181
|
+
uv run python examples/example_ensemble_synthetic.py \
|
|
182
|
+
--code feff@localhost --python-code python3@localhost \
|
|
183
|
+
--n-snapshots 6 --store-paths --plot-file ensemble.png
|
|
184
|
+
|
|
185
|
+
# Batch: one scheduler job per chunk, the mode intended for HPC. Produces a
|
|
186
|
+
# consolidated ExafsArchiveData instead of per-snapshot shards.
|
|
187
|
+
uv run python examples/example_ensemble_synthetic.py \
|
|
188
|
+
--code feff@localhost --python-code python3@localhost \
|
|
189
|
+
--n-snapshots 12 --batch-size 4 --plot-file ensemble.png
|
|
190
|
+
|
|
191
|
+
# Reuse one set of scattering potentials across every snapshot
|
|
192
|
+
uv run python examples/example_ensemble_synthetic.py \
|
|
193
|
+
--code feff@localhost --n-snapshots 6 --precompute-potentials
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
The ensemble runs need roughly 4 GB of RAM; below that the daemon is killed
|
|
197
|
+
mid-run and the only symptom is exit 137.
|
|
198
|
+
|
|
199
|
+
Pass a real `TrajectoryData` node, or use the synthetic-trajectory helper
|
|
200
|
+
included in `examples/` to run a quick end-to-end test without any MD data:
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
# Quick self-contained demo (generates a synthetic BCC-Fe trajectory)
|
|
204
|
+
uv run python examples/example_ensemble_synthetic.py \
|
|
205
|
+
--code feff@localhost \
|
|
206
|
+
--n-snapshots 6 --sigma 0.06 \
|
|
207
|
+
--plot-file ensemble_exafs.png
|
|
208
|
+
|
|
209
|
+
# Optional: also store and merge per-path FEFF contributions
|
|
210
|
+
uv run python examples/example_ensemble_synthetic.py \
|
|
211
|
+
--code feff@localhost \
|
|
212
|
+
--n-snapshots 6 --sigma 0.06 \
|
|
213
|
+
--store-paths --python-code python3@localhost --path-cw-threshold 5 \
|
|
214
|
+
--plot-file ensemble_exafs.png
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
For a real trajectory, pass it via Python:
|
|
218
|
+
|
|
219
|
+
```python
|
|
220
|
+
from aiida_feff.workflows.ensemble import EnsembleExafsWorkChain
|
|
221
|
+
|
|
222
|
+
wc = submit(EnsembleExafsWorkChain,
|
|
223
|
+
trajectory=trajectory_node, # TrajectoryData already in DB
|
|
224
|
+
sample_interval=orm.Int(5), # use every 5th frame
|
|
225
|
+
parameters=params,
|
|
226
|
+
code=orm.load_code('feff@localhost'),
|
|
227
|
+
options=orm.Dict({"resources": {"num_machines": 1}, "max_wallclock_seconds": 600}),
|
|
228
|
+
group_label=orm.Str("md-exafs/Fe-300K"), # optional: bundle into a named group
|
|
229
|
+
)
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### 4. Ensemble EXAFS on an HPC cluster (batch mode)
|
|
233
|
+
|
|
234
|
+
For ensembles of hundreds of snapshots, submitting one Slurm job per
|
|
235
|
+
`(frame, site)` pair is inefficient: most HPC centres cap queued jobs per user,
|
|
236
|
+
and the scheduler overhead for many short serial jobs is significant.
|
|
237
|
+
|
|
238
|
+
**Batch mode** solves this by packing many FEFF calculations into a single Slurm
|
|
239
|
+
job. The `EnsembleExafsWorkChain` accepts a `batch_size` input; when set, it
|
|
240
|
+
groups all `(frame, site)` pairs into chunks of that size and submits one
|
|
241
|
+
`FeffBatchCalculation` per chunk instead of one `FeffCalculation` per pair.
|
|
242
|
+
With `batch_size = num_cores_per_node`, the whole ensemble runs in as few Slurm
|
|
243
|
+
jobs as `ceil(N_frames × N_sites / num_cores)`.
|
|
244
|
+
|
|
245
|
+
#### Design choices
|
|
246
|
+
|
|
247
|
+
**Why one CalcJob per chunk, not a Slurm array?**
|
|
248
|
+
AiiDA maps each `CalcJob` 1:1 to a scheduler job. There is no native
|
|
249
|
+
job-array fan-out in AiiDA. The batch CalcJob is the standard pattern for
|
|
250
|
+
running many serial tasks inside one allocation.
|
|
251
|
+
|
|
252
|
+
**How does FEFF's module load get applied?**
|
|
253
|
+
The `feff_code` input is an `InstalledCode` whose `prepend_text` typically
|
|
254
|
+
contains `module load feff/8.5` (or similar). At submission time,
|
|
255
|
+
`prepare_for_submission` reads `feff_code.prepend_text` and
|
|
256
|
+
`feff_code.filepath_executable` and writes them into `batch_config.json`.
|
|
257
|
+
On the compute node, the Python driver generates a small `_run_feff.sh`
|
|
258
|
+
wrapper per run directory that sources the environment and calls FEFF —
|
|
259
|
+
so the module load is applied correctly for every instance.
|
|
260
|
+
|
|
261
|
+
**Why does batch mode require `python_code`?**
|
|
262
|
+
The `python_code` input (a Python interpreter on the HPC) doubles as
|
|
263
|
+
the **runner** for the batch driver script. The driver script is a pure-Python
|
|
264
|
+
file with no AiiDA dependency that uses `concurrent.futures.ProcessPoolExecutor`
|
|
265
|
+
to launch FEFF instances in parallel. If you are not storing path
|
|
266
|
+
contributions, the aggregation step is skipped, but the Python interpreter is
|
|
267
|
+
still needed to run the driver.
|
|
268
|
+
|
|
269
|
+
**How are precomputed potentials distributed?**
|
|
270
|
+
`precompute_potentials=True` triggers the existing potentials-only step —
|
|
271
|
+
one `FeffCalculation` per absorber site (same as non-batch mode). Their
|
|
272
|
+
`RemoteData` outputs are then passed to the batch CalcJob as a dynamic input
|
|
273
|
+
namespace (`remote_potentials.site_0000`, `remote_potentials.site_0001`, …).
|
|
274
|
+
`prepare_for_submission` populates `remote_copy_list` to copy each site's
|
|
275
|
+
potential files (`pot.pad`, `phase.pad`, etc.) to `potentials/site_XXXX/` in
|
|
276
|
+
the working directory. The driver copies from there into each run directory
|
|
277
|
+
before calling FEFF, so the SCF step is skipped for every snapshot.
|
|
278
|
+
|
|
279
|
+
**Partial failures are isolated.**
|
|
280
|
+
If an individual FEFF run crashes, the driver logs the error and continues with
|
|
281
|
+
the remaining runs. The batch job exits 0. The parser detects missing
|
|
282
|
+
`chi.dat` files and skips those pairs; the workchain counts them as failures
|
|
283
|
+
and produces a partial average (exit code 301) rather than aborting entirely.
|
|
284
|
+
|
|
285
|
+
#### Setup: register codes on the HPC
|
|
286
|
+
|
|
287
|
+
Register the FEFF executable. The `--prepend-text` is the environment setup
|
|
288
|
+
that must run before FEFF can be called:
|
|
289
|
+
|
|
290
|
+
```bash
|
|
291
|
+
verdi code create core.code.installed \
|
|
292
|
+
--label feff \
|
|
293
|
+
--computer hpc \
|
|
294
|
+
--filepath-executable /path/to/feff8l \
|
|
295
|
+
--prepend-text "module load feff/8.5" \
|
|
296
|
+
--default-calc-job-plugin feff.feff
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
Register the Python interpreter. This is used both as the batch runner and
|
|
300
|
+
(when `path_cw_threshold >= 0`) for path aggregation. It must have `larch`,
|
|
301
|
+
`numpy`, and `h5py` installed:
|
|
302
|
+
|
|
303
|
+
```bash
|
|
304
|
+
verdi code create core.code.installed \
|
|
305
|
+
--label python3 \
|
|
306
|
+
--computer hpc \
|
|
307
|
+
--filepath-executable /path/to/venv/bin/python3 \
|
|
308
|
+
--default-calc-job-plugin feff.feff_batch
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
Verify:
|
|
312
|
+
|
|
313
|
+
```bash
|
|
314
|
+
verdi code list
|
|
315
|
+
# feff@hpc (feff.feff)
|
|
316
|
+
# python3@hpc (feff.feff_batch)
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
#### Running a batched ensemble
|
|
320
|
+
|
|
321
|
+
```python
|
|
322
|
+
from aiida import orm
|
|
323
|
+
from aiida.engine import submit
|
|
324
|
+
from aiida_feff.workflows.ensemble import EnsembleExafsWorkChain
|
|
325
|
+
from aiida_feff.data.parameters import FeffParameters
|
|
326
|
+
|
|
327
|
+
load_profile()
|
|
328
|
+
|
|
329
|
+
params = FeffParameters(dict={
|
|
330
|
+
"edge": "K",
|
|
331
|
+
"spectrum_type": "EXAFS",
|
|
332
|
+
"radius": 6.0,
|
|
333
|
+
"absorbing_atoms": "Cu", # all Cu sites; or e.g. "Cu:0,1" for a subset
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
# One Slurm job per node; each job runs 64 FEFF instances in parallel.
|
|
337
|
+
# Set batch_size = number of cores you want to allocate per job.
|
|
338
|
+
CORES_PER_NODE = 64
|
|
339
|
+
|
|
340
|
+
wc = submit(
|
|
341
|
+
EnsembleExafsWorkChain,
|
|
342
|
+
trajectory=trajectory_node, # TrajectoryData in DB
|
|
343
|
+
sample_interval=orm.Int(1),
|
|
344
|
+
parameters=params,
|
|
345
|
+
code=orm.load_code("feff@hpc"),
|
|
346
|
+
python_code=orm.load_code("python3@hpc"),
|
|
347
|
+
precompute_potentials=orm.Bool(True),
|
|
348
|
+
batch_size=orm.Int(CORES_PER_NODE),
|
|
349
|
+
n_workers=orm.Int(CORES_PER_NODE), # omit to fall back to $SLURM_CPUS_ON_NODE
|
|
350
|
+
options=orm.Dict({
|
|
351
|
+
"resources": {
|
|
352
|
+
"num_machines": 1,
|
|
353
|
+
"num_mpiprocs_per_machine": CORES_PER_NODE,
|
|
354
|
+
},
|
|
355
|
+
"max_wallclock_seconds": 3600,
|
|
356
|
+
"queue_name": "regular",
|
|
357
|
+
}),
|
|
358
|
+
group_label=orm.Str("md-exafs/Cu-300K"),
|
|
359
|
+
)
|
|
360
|
+
print(f"Submitted workchain pk={wc.pk}")
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
**With path contributions** (stores per-path FEFF scattering factors, enables
|
|
364
|
+
later σ² fitting):
|
|
365
|
+
|
|
366
|
+
```python
|
|
367
|
+
wc = submit(
|
|
368
|
+
EnsembleExafsWorkChain,
|
|
369
|
+
trajectory=trajectory_node,
|
|
370
|
+
sample_interval=orm.Int(1),
|
|
371
|
+
parameters=params,
|
|
372
|
+
code=orm.load_code("feff@hpc"),
|
|
373
|
+
python_code=orm.load_code("python3@hpc"),
|
|
374
|
+
precompute_potentials=orm.Bool(True),
|
|
375
|
+
path_cw_threshold=orm.Float(5.0), # keep paths with ≥5% peak amplitude
|
|
376
|
+
path_r_bin=orm.Float(0.15),
|
|
377
|
+
batch_size=orm.Int(CORES_PER_NODE),
|
|
378
|
+
n_workers=orm.Int(CORES_PER_NODE),
|
|
379
|
+
options=orm.Dict({
|
|
380
|
+
"resources": {
|
|
381
|
+
"num_machines": 1,
|
|
382
|
+
"num_mpiprocs_per_machine": CORES_PER_NODE,
|
|
383
|
+
},
|
|
384
|
+
"max_wallclock_seconds": 7200,
|
|
385
|
+
"queue_name": "regular",
|
|
386
|
+
}),
|
|
387
|
+
)
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
#### Sizing guide
|
|
391
|
+
|
|
392
|
+
| N\_frames × N\_sites | Recommended `batch_size` | Slurm jobs |
|
|
393
|
+
|---|---|---|
|
|
394
|
+
| ≤ 64 | = total pairs | 1 |
|
|
395
|
+
| 65 – 512 | = cores per node (e.g. 64 or 128) | 2 – 8 |
|
|
396
|
+
| > 512 | = cores per node | `ceil(N / cores)` |
|
|
397
|
+
|
|
398
|
+
If `n_workers` is omitted, the driver reads `$SLURM_CPUS_ON_NODE` at runtime,
|
|
399
|
+
then falls back to `$SLURM_NTASKS`, then 1. Set it explicitly if your HPC
|
|
400
|
+
uses `--cpus-per-task` instead of `--ntasks`.
|
|
401
|
+
|
|
402
|
+
#### Checking status
|
|
403
|
+
|
|
404
|
+
```bash
|
|
405
|
+
verdi process status <pk>
|
|
406
|
+
# EnsembleExafsWorkChain (pk=<pk>) [ProcessState.RUNNING] [3:submit_batch_calculations]
|
|
407
|
+
# ├── FeffCalculation (pk=...) [FINISHED] ← potentials-only, 1 per site
|
|
408
|
+
# ├── FeffBatchCalculation (pk=...) [RUNNING] ← batch 0, 64 pairs
|
|
409
|
+
# └── FeffBatchCalculation (pk=...) [CREATED] ← batch 1, 64 pairs
|
|
410
|
+
|
|
411
|
+
verdi process report <pk> # human-readable log
|
|
412
|
+
verdi calcjob outputcat <pk> batch.log # driver stdout for a batch CalcJob
|
|
413
|
+
verdi calcjob outputcat <pk> batch_err.log # per-run FEFF pass/fail summary
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
#### Retrieving results (same as non-batch mode)
|
|
417
|
+
|
|
418
|
+
```python
|
|
419
|
+
from aiida.orm import load_node
|
|
420
|
+
|
|
421
|
+
wc = load_node(<pk>)
|
|
422
|
+
grand_average = wc.outputs.averaged_xas.all # XasData
|
|
423
|
+
per_site_avg = wc.outputs.averaged_xas.site_0000 # XasData, one per absorber
|
|
424
|
+
n_failed = wc.outputs.n_failed.value # int
|
|
425
|
+
path_contrib = wc.outputs.path_contributions # PathContributionsData (if stored)
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
### 5. Post-processing with larch (optional)
|
|
429
|
+
|
|
430
|
+
```python
|
|
431
|
+
from aiida_feff.calcfunctions.larch import chi_k_to_r
|
|
432
|
+
from aiida.orm import Dict
|
|
433
|
+
|
|
434
|
+
# Fourier transform (provenance-tracked)
|
|
435
|
+
chir = chi_k_to_r(xas_data=xas, ft_params=Dict({"kmin":3, "kmax":14, "kweight":2}))
|
|
436
|
+
# Defaults, including window="kaiser", are in aiida_feff.calcfunctions.larch.FT_DEFAULTS.
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
### 6. Plotting (optional)
|
|
440
|
+
|
|
441
|
+
```python
|
|
442
|
+
from aiida_feff.visualise import plot_chi_k, plot_chi_r
|
|
443
|
+
|
|
444
|
+
# k²χ(k)
|
|
445
|
+
fig = plot_chi_k(xas, kweight=2)
|
|
446
|
+
|
|
447
|
+
# χ(R) — pass the output of chi_k_to_r directly (FT already tracked)
|
|
448
|
+
fig = plot_chi_r(chir)
|
|
449
|
+
|
|
450
|
+
# Overlay multiple spectra on one axes
|
|
451
|
+
import matplotlib.pyplot as plt
|
|
452
|
+
fig, ax = plt.subplots()
|
|
453
|
+
for node in ensemble_xas_nodes:
|
|
454
|
+
plot_chi_k(node, ax=ax, label=node.label, plot_envelope=True)
|
|
455
|
+
plt.show()
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
### 7. Debye-Waller σ² from an MD trajectory (optional)
|
|
459
|
+
|
|
460
|
+
Per-path MSRD (σ²) is computed directly from the trajectory by
|
|
461
|
+
[md-exafs](https://pypi.org/project/md-exafs/), which this plugin depends on.
|
|
462
|
+
|
|
463
|
+
**This step is deliberately not provenance-tracked.** σ² is cheap to recompute
|
|
464
|
+
and the trajectory it derives from is already a stored node, so recording the
|
|
465
|
+
result would add graph weight without adding recoverable information. The
|
|
466
|
+
`store_msrd` / `store_adp` calcfunction wrappers that earlier versions shipped
|
|
467
|
+
have been removed for that reason; call md-exafs directly.
|
|
468
|
+
|
|
469
|
+
```python
|
|
470
|
+
from md_exafs.debye_waller import calculate_grouped_msrd
|
|
471
|
+
|
|
472
|
+
from aiida_feff.utils import trajectory_to_structures
|
|
473
|
+
|
|
474
|
+
structures = [s.get_ase() for s in trajectory_to_structures(traj_node)]
|
|
475
|
+
|
|
476
|
+
# `cutoff` must stay below the inscribed-sphere radius of the cell, or the
|
|
477
|
+
# minimum-image convention picks the wrong neighbour and biases σ² low.
|
|
478
|
+
# Build a supercell rather than raising the cutoff.
|
|
479
|
+
two_body, three_body = calculate_grouped_msrd(
|
|
480
|
+
structures,
|
|
481
|
+
central_indices=[0], # zero-based absorber indices
|
|
482
|
+
central_label="Fe",
|
|
483
|
+
cutoff=3.5, # neighbour search radius in Å
|
|
484
|
+
cutoff_3body=3.0, # include 3-body paths (omit to skip)
|
|
485
|
+
)
|
|
486
|
+
|
|
487
|
+
for group in sorted(two_body, key=lambda g: g["reff"]):
|
|
488
|
+
print(f"{group['scatterer']}: reff={group['reff']:.3f} Å σ²={group['sigma2']:.5f} Ų")
|
|
489
|
+
# Fe: reff=2.481 Å σ²=0.00612 Ų
|
|
490
|
+
# Fe: reff=4.052 Å σ²=0.00891 Ų
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
The resulting σ² values can be passed straight to
|
|
494
|
+
`aiida_feff.calcfunctions.exafs.total_chi` as a `scatterer -> σ²` mapping.
|
|
495
|
+
|
|
496
|
+
Per-atom B-factors and full U tensors come from the same module via
|
|
497
|
+
`md_exafs.debye_waller.compute_adp_results`.
|
|
498
|
+
|
|
499
|
+
## CLI
|
|
500
|
+
|
|
501
|
+
```bash
|
|
502
|
+
verdi data feff list # list all FeffParameters / XasData nodes
|
|
503
|
+
verdi data feff export <PK> # preview feff.inp from a FeffParameters node
|
|
504
|
+
verdi data feff show <PK> # inspect arrays in an XasData node
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
## Architecture overview
|
|
508
|
+
|
|
509
|
+
### Single-job mode (localhost / small ensembles)
|
|
510
|
+
|
|
511
|
+
```
|
|
512
|
+
TrajectoryData + FeffParameters
|
|
513
|
+
│
|
|
514
|
+
▼
|
|
515
|
+
EnsembleExafsWorkChain
|
|
516
|
+
├─ [optional] FeffCalculation × N_sites ← potentials-only (CONTROL 1 1 1 0 0 0)
|
|
517
|
+
│
|
|
518
|
+
├─ FeffCalculation × (N_frames × N_sites) ← one Slurm job each
|
|
519
|
+
│ └─ FeffParser → XasData
|
|
520
|
+
│
|
|
521
|
+
├─ create_serial_shard (calcfunction) → one shard, as the batch driver writes
|
|
522
|
+
│
|
|
523
|
+
└─ merge_exafs_shards (calcfunction) → ExafsArchiveData (archive)
|
|
524
|
+
└─ archive_to_averaged_xas (calcfunction) → averaged XasData per site + grand average
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
### Batch mode (HPC, hundreds of calculations)
|
|
528
|
+
|
|
529
|
+
```
|
|
530
|
+
TrajectoryData + FeffParameters
|
|
531
|
+
│
|
|
532
|
+
▼
|
|
533
|
+
EnsembleExafsWorkChain (batch_size=64)
|
|
534
|
+
├─ FeffCalculation × N_sites ← potentials-only, one Slurm job each
|
|
535
|
+
│ └─ RemoteData (pot.pad, phase.pad, …)
|
|
536
|
+
│
|
|
537
|
+
├─ FeffBatchCalculation ← ONE Slurm job, 64 FEFF instances in parallel
|
|
538
|
+
│ ├─ snap_0000_site_0000/feff.inp
|
|
539
|
+
│ ├─ snap_0001_site_0000/feff.inp
|
|
540
|
+
│ │ … (up to batch_size pairs)
|
|
541
|
+
│ └─ _run_batch.py (driver, concurrent.futures, 1 worker/core)
|
|
542
|
+
│ └─ FeffBatchParser → xas_data.snap_FFFF_site_SSSS (dynamic namespace)
|
|
543
|
+
│
|
|
544
|
+
├─ FeffBatchCalculation ← next chunk, another Slurm job
|
|
545
|
+
│ └─ …
|
|
546
|
+
│
|
|
547
|
+
└─ merge_exafs_shards (calcfunction) → ExafsArchiveData (archive)
|
|
548
|
+
└─ archive_to_averaged_xas (calcfunction) → averaged XasData per site + grand average
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
Both routes end at the same merge, so the ensemble average has one
|
|
552
|
+
implementation and `archive` is always produced.
|
|
553
|
+
|
|
554
|
+
The FEFF environment (module loads, etc.) is read from `feff_code.prepend_text`
|
|
555
|
+
at AiiDA submission time and embedded in `batch_config.json`. On the compute
|
|
556
|
+
node, the driver generates a small `_run_feff.sh` bash wrapper per run
|
|
557
|
+
directory, so the environment is correctly applied for every FEFF instance
|
|
558
|
+
without requiring any extra code to be installed on the HPC.
|
|
559
|
+
|
|
560
|
+
## Development
|
|
561
|
+
|
|
562
|
+
```bash
|
|
563
|
+
git clone https://github.com/stfc/aiida-feff
|
|
564
|
+
cd aiida-feff
|
|
565
|
+
uv sync --locked --extra testing --extra plots
|
|
566
|
+
uv run pytest tests/
|
|
567
|
+
|
|
568
|
+
# Lint exactly as CI does
|
|
569
|
+
uv run pre-commit run --all-files
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
## Relationship to [larch-cli](https://github.com/stfc/alc-dls-exafs/)
|
|
573
|
+
|
|
574
|
+
This plugin is designed to supersede a CLI tool built around larch + FEFF.
|
|
575
|
+
The key differences:
|
|
576
|
+
|
|
577
|
+
| larch-cli | aiida-feff |
|
|
578
|
+
|-----------|------------|
|
|
579
|
+
| Linear script execution | DAG of provenance-tracked nodes |
|
|
580
|
+
| Manual file management | AiiDA handles staging to/from HPC |
|
|
581
|
+
| Results as files on disk | All inputs/outputs stored in the database |
|
|
582
|
+
| Manual batching (N workers per node) | `EnsembleExafsWorkChain(batch_size=N)` |
|
|
583
|
+
|
|
584
|
+
|
|
585
|
+
## License
|
|
586
|
+
|
|
587
|
+
BSD 3-Clause License. See [LICENSE](LICENSE) for details.
|