saddlemill 0.1.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.
- saddlemill-0.1.0/LICENSE +21 -0
- saddlemill-0.1.0/PKG-INFO +452 -0
- saddlemill-0.1.0/README.md +424 -0
- saddlemill-0.1.0/pyproject.toml +55 -0
- saddlemill-0.1.0/saddlemill/__main__.py +193 -0
- saddlemill-0.1.0/saddlemill/analyze_neb.py +1362 -0
- saddlemill-0.1.0/saddlemill/catsunami/autoframe.py +1688 -0
- saddlemill-0.1.0/saddlemill/catsunami/ocpneb.py +375 -0
- saddlemill-0.1.0/saddlemill/catsunami/reaction.py +99 -0
- saddlemill-0.1.0/saddlemill/config.py +1009 -0
- saddlemill-0.1.0/saddlemill/dimeropt.py +324 -0
- saddlemill-0.1.0/saddlemill/dimertools/kappa_dimer.py +201 -0
- saddlemill-0.1.0/saddlemill/dimertools/structure_edit.py +1067 -0
- saddlemill-0.1.0/saddlemill/geomopt.py +616 -0
- saddlemill-0.1.0/saddlemill/init_function.py +67 -0
- saddlemill-0.1.0/saddlemill/nebopt.py +647 -0
- saddlemill-0.1.0/saddlemill/nebtools/create_endpoints_for_MP_batteries.py +135 -0
- saddlemill-0.1.0/saddlemill/sellaopt.py +473 -0
- saddlemill-0.1.0/saddlemill/status.py +328 -0
- saddlemill-0.1.0/saddlemill/tools.py +1060 -0
- saddlemill-0.1.0/saddlemill/vasp_io.py +552 -0
- saddlemill-0.1.0/saddlemill.egg-info/PKG-INFO +452 -0
- saddlemill-0.1.0/saddlemill.egg-info/SOURCES.txt +43 -0
- saddlemill-0.1.0/saddlemill.egg-info/dependency_links.txt +1 -0
- saddlemill-0.1.0/saddlemill.egg-info/requires.txt +18 -0
- saddlemill-0.1.0/saddlemill.egg-info/top_level.txt +1 -0
- saddlemill-0.1.0/setup.cfg +4 -0
- saddlemill-0.1.0/tests/test_ase_vasp_incar.py +39 -0
- saddlemill-0.1.0/tests/test_config.py +829 -0
- saddlemill-0.1.0/tests/test_dimeropt_integration.py +242 -0
- saddlemill-0.1.0/tests/test_filter_neb_trajes.py +151 -0
- saddlemill-0.1.0/tests/test_geomopt_integration.py +321 -0
- saddlemill-0.1.0/tests/test_init_function.py +110 -0
- saddlemill-0.1.0/tests/test_kappa_changes.py +119 -0
- saddlemill-0.1.0/tests/test_main_integration.py +489 -0
- saddlemill-0.1.0/tests/test_nebopt_integration.py +274 -0
- saddlemill-0.1.0/tests/test_ocpneb.py +264 -0
- saddlemill-0.1.0/tests/test_sellaopt.py +848 -0
- saddlemill-0.1.0/tests/test_sp_resume.py +319 -0
- saddlemill-0.1.0/tests/test_sp_resume_modes.py +131 -0
- saddlemill-0.1.0/tests/test_spc_wrap_ordering.py +86 -0
- saddlemill-0.1.0/tests/test_status_csv_quoting.py +153 -0
- saddlemill-0.1.0/tests/test_structure_edit.py +676 -0
- saddlemill-0.1.0/tests/test_tools.py +773 -0
- saddlemill-0.1.0/tests/test_vasp_io.py +721 -0
saddlemill-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ilgar Baghishov, Sung Hoon Jung and Graeme Henkelman
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: saddlemill
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: High-throughput transition state search using neural network potentials or DFT
|
|
5
|
+
Author: Ilgar Baghishov, Sung Hoon Jung, Graeme Henkelman
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/SciLM-ai/SaddleMill
|
|
8
|
+
Project-URL: Repository, https://github.com/SciLM-ai/SaddleMill
|
|
9
|
+
Project-URL: Dataset, https://huggingface.co/datasets/SciLM/MaterialsSaddles
|
|
10
|
+
Requires-Python: >=3.10
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: ase>=3.26.0
|
|
14
|
+
Requires-Dist: fairchem-core>=2.19.0
|
|
15
|
+
Requires-Dist: numpy
|
|
16
|
+
Requires-Dist: scipy
|
|
17
|
+
Requires-Dist: matplotlib
|
|
18
|
+
Provides-Extra: test
|
|
19
|
+
Requires-Dist: pytest>=7.0; extra == "test"
|
|
20
|
+
Requires-Dist: pytest-timeout; extra == "test"
|
|
21
|
+
Provides-Extra: gpu
|
|
22
|
+
Requires-Dist: torch; extra == "gpu"
|
|
23
|
+
Provides-Extra: distributed
|
|
24
|
+
Requires-Dist: executorlib; extra == "distributed"
|
|
25
|
+
Provides-Extra: sella
|
|
26
|
+
Requires-Dist: sella>=2.5; extra == "sella"
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
# SaddleMill
|
|
30
|
+
|
|
31
|
+
High-throughput transition-state searches (Dimer, Sella, NEB) and minimizations with machine-learned potentials
|
|
32
|
+
or DFT, from one GPU to many nodes. It computed the
|
|
33
|
+
[MaterialsSaddles](https://huggingface.co/datasets/SciLM/MaterialsSaddles) dataset.
|
|
34
|
+
|
|
35
|
+
## Install with pip (one GPU, no Flux)
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install "saddlemill[sella]" # [sella] adds Sella for method = Sella; the other methods do not need it
|
|
39
|
+
python -c "import torch; print(torch.cuda.is_available())" # should print True
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**Linux on ARM (aarch64), e.g. NVIDIA GH200:** PyPI has only CPU builds of torch for aarch64, so install the CUDA
|
|
43
|
+
build first: `pip install torch==2.13.0 --index-url https://download.pytorch.org/whl/cu129` (`torch==2.8.0` from
|
|
44
|
+
the same index with fairchem-core 2.19–2.21).
|
|
45
|
+
|
|
46
|
+
This runs the jobs one after another in a single process: set `executorlib = False` in `config.ini` and run
|
|
47
|
+
`python -u -m saddlemill` (see [Running SaddleMill](#running-saddlemill)). UMA models are gated: accept the license
|
|
48
|
+
at [facebook/UMA](https://huggingface.co/facebook/UMA) and run `hf auth login`.
|
|
49
|
+
|
|
50
|
+
Running many jobs in parallel over GPUs and nodes needs executorlib and the Flux scheduler. `pip install
|
|
51
|
+
"saddlemill[distributed]"` adds executorlib, but Flux is not on PyPI (the PyPI package named `flux-core` is an
|
|
52
|
+
unrelated project): install it from conda-forge as in the HPC steps below.
|
|
53
|
+
|
|
54
|
+
## Installation on HPC (multi-node, with Flux)
|
|
55
|
+
|
|
56
|
+
### 1. Base Environment Setup
|
|
57
|
+
|
|
58
|
+
#### *3 A100 per node HPC*
|
|
59
|
+
|
|
60
|
+
Load necessary modules and create the base Conda environment.
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
module unload impi python3
|
|
64
|
+
module load cuda/12.8
|
|
65
|
+
|
|
66
|
+
# Enter idev to get a GPU node
|
|
67
|
+
idev -A YOUR_ALLOCATION -p gpu-a100-dev -t 02:00:00
|
|
68
|
+
|
|
69
|
+
# Create base environment
|
|
70
|
+
conda create --prefix $WORK/conda_envs/executorlib -c conda-forge python=3.12 flux-core flux-sched openmpi=5.0.5 "libhwloc=*=cuda*" executorlib
|
|
71
|
+
conda activate executorlib
|
|
72
|
+
|
|
73
|
+
find $CONDA_PREFIX -name "sched-fluxion-*.so" -path "*feedstock_root*" -exec cp {} $CONDA_PREFIX/lib/flux/modules/ \;
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
#### *GH200 HPC*
|
|
78
|
+
|
|
79
|
+
Load necessary modules and create the base Conda environment.
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
# Create base environment
|
|
83
|
+
conda create -n executorlib -c conda-forge python=3.12 flux-core flux-sched "openmpi=5.0.5" executorlib
|
|
84
|
+
conda activate executorlib
|
|
85
|
+
|
|
86
|
+
# Install hardware locality
|
|
87
|
+
conda install "libhwloc=*=cuda*" -c conda-forge
|
|
88
|
+
export LD_LIBRARY_PATH=/opt/apps/cuda/12.4/targets/sbsa-linux/lib/:$LD_LIBRARY_PATH
|
|
89
|
+
|
|
90
|
+
find $CONDA_PREFIX -name "sched-fluxion-*.so" -path "*feedstock_root*" -exec cp {} $CONDA_PREFIX/lib/flux/modules/ \;
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**(Optional) MPI Support:**
|
|
94
|
+
If `mpi4py` is required, use the following (experimental):
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
MPICC=$(which mpicc) pip install --no-binary=mpi4py mpi4py
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
#### *4 A100 per node HPC*
|
|
102
|
+
|
|
103
|
+
Necessary modules (should be loaded by default):
|
|
104
|
+
* `PrgEnv-gnu/8.5.0` (Compiler Suite)
|
|
105
|
+
* `cudatoolkit/12.4` (System CUDA headers)
|
|
106
|
+
* `craype-accel-nvidia80` (Links MPI to A100 GPUs)
|
|
107
|
+
* `cray-mpich/8.1.30` (High-Speed Network Library)
|
|
108
|
+
* `python/3.11` (System Python)
|
|
109
|
+
* `conda/Miniforge3-24.7.1-0` (Access to mamba)
|
|
110
|
+
|
|
111
|
+
Other modules (should be loaded by default):
|
|
112
|
+
* `craype-x86-milan`
|
|
113
|
+
* `libfabric/1.22.0`
|
|
114
|
+
* `craype-network-ofi`
|
|
115
|
+
* `xpmem/2.9.7...`
|
|
116
|
+
* `cray-dsmml/0.3.0 `
|
|
117
|
+
* `cray-libsci/24.07.0`
|
|
118
|
+
* `craype/2.7.32`
|
|
119
|
+
* `gcc-native/13.2`
|
|
120
|
+
* `perftools-base/24.07.0`
|
|
121
|
+
* `cpe/24.07`
|
|
122
|
+
* `gpu/1.0`
|
|
123
|
+
* `sqs/2.0`
|
|
124
|
+
* `darshan/default`
|
|
125
|
+
|
|
126
|
+
Create base environment:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
# set mamba cache at scratch
|
|
130
|
+
mamba config --set pkgs_dirs $SCRATCH/.cache/conda
|
|
131
|
+
|
|
132
|
+
# Enter idev to get a GPU node
|
|
133
|
+
salloc --nodes 1 --qos interactive --time 04:00:00 --constraint gpu --gpus 4 --account YOUR_ALLOCATION
|
|
134
|
+
|
|
135
|
+
# Create base environment
|
|
136
|
+
mamba create -p $SCRATCH/envs/executorlib -c conda-forge python=3.12 \
|
|
137
|
+
flux-core flux-sched executorlib "libhwloc=*=cuda*" # warnings about cuda, ucx, nccl, etc. are ok
|
|
138
|
+
conda activate executorlib
|
|
139
|
+
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**(Optional) MPI Support:**
|
|
143
|
+
If `mpi4py` is required, use the following to compile it from source with Cray wrappers (experimental):
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
MPICC="cc -shared" pip install --force-reinstall --no-cache-dir --no-binary=mpi4py mpi4py
|
|
147
|
+
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
#### *In-house CPU cluster*
|
|
151
|
+
|
|
152
|
+
CPU-only (no GPU/CUDA), for VASP runs — no `torch`/`fairchem-core`. Create the env as `saddlemill` directly (no later clone):
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
conda create -n saddlemill -c conda-forge python=3.12 flux-core flux-sched "openmpi=5.0.5" executorlib
|
|
156
|
+
conda activate saddlemill
|
|
157
|
+
|
|
158
|
+
find $CONDA_PREFIX -name "sched-fluxion-*.so" -path "*feedstock_root*" -exec cp {} $CONDA_PREFIX/lib/flux/modules/ \;
|
|
159
|
+
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### 2. Verify Flux Resources
|
|
163
|
+
|
|
164
|
+
Check if flux detects all resources correctly:
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
# For the GH200 HPC and the 4 A100 per node HPC
|
|
168
|
+
srun -n 2 flux start flux resource list
|
|
169
|
+
# For the 3 A100 per node HPC
|
|
170
|
+
srun -n 2 --mpi=pmi2 flux start flux resource list
|
|
171
|
+
# For the in-house CPU cluster (grab nodes first, e.g. salloc --nodes 2 --qos debug)
|
|
172
|
+
srun -n 2 --mpi=pmi2 flux start flux resource list
|
|
173
|
+
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### 3. Application Specifics
|
|
177
|
+
|
|
178
|
+
Clone the environment and install specific machine learning libraries.
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
conda create --prefix $WORK/conda_libraries/saddlemill --clone executorlib
|
|
182
|
+
|
|
183
|
+
pip config set global.cache-dir "/path/to/your/cache/directory" # like $SCRATCH/.cache/pip
|
|
184
|
+
|
|
185
|
+
pip install "fairchem-core>=2.19.0" "ase>=3.26.0" scipy==1.16
|
|
186
|
+
# If you want Vasp inpute files to be created like in omat/oc20:
|
|
187
|
+
pip install fairchem-data-omat
|
|
188
|
+
# If you will need some of the catsunami functionality or create Vasp input files for oc20
|
|
189
|
+
pip install fairchem-data-oc
|
|
190
|
+
# If you will need VaspInteractive
|
|
191
|
+
pip install git+https://github.com/tiangroup-uofa/vasp-interactive.git
|
|
192
|
+
# If you will need method = Sella (P-RFO saddle search) — see the note below
|
|
193
|
+
pip install sella
|
|
194
|
+
|
|
195
|
+
# This part below is only necessary for the GH200 HPC (not for the A100-based HPCs)
|
|
196
|
+
pip install "torch==2.9.0+cu128" --index-url https://download.pytorch.org/whl/cu128
|
|
197
|
+
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
**Sella (`method = Sella`):** the P-RFO saddle optimiser is an *optional* dependency — SaddleMill only imports it inside `sellaopt.py`, so every other method works without it. Install it into the application env (the one holding `fairchem-core`/`ase`):
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
conda activate /path/to/conda_envs/tsearch # your SaddleMill application env
|
|
204
|
+
pip freeze > /tmp/env_before.txt # snapshot, so any change is reversible
|
|
205
|
+
pip install --constraint <(grep -E "^[A-Za-z0-9_.-]+==[0-9]" /tmp/env_before.txt) sella
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
The `--constraint` file pins every package already installed, so pip physically cannot upgrade `ase`, `numpy`, `scipy` or `torch` out from under `fairchem-core`. On Python 3.12 / x86-64, Sella 2.5.0 installs from a prebuilt `manylinux` wheel and adds only `sella`, `jax`, `jaxlib` and `ml_dtypes` — no compilation and no version changes. Verify with:
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
python -c "import ase, torch, fairchem.core, sella; print(ase.__version__, torch.__version__)"
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
If pip ever falls back to building Sella from source (older Python, non-x86, no wheel), the build needs a GNU compiler — NVIDIA's `nvc` rejects Sella's `-fno-strict-overflow`:
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
CC=gcc CXX=g++ pip install --no-binary sella sella
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
**VTST + VASP (manual ASE patch required for now):** ASE ≤ 3.28 writes the integer INCAR tag `DROTMAX` as a float (`10.000000`), which VASP's VTST dimer silently ignores (falls back to `RotMax 4`). Until fixed upstream, patch the installed ASE — in `ase/calculators/vasp/create_input.py`, move `'drotmax'` from `float_keys` to `int_keys`. The `ase` version pin will be bumped to the fixed release once available. (Guarded by `tests/test_ase_vasp_incar.py`.)
|
|
221
|
+
|
|
222
|
+
**In-house CPU cluster (VASP-only):** the `saddlemill` env is already the app env (no `--clone`); VASP needs no `fairchem-core`/`torch`. Install only:
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
pip install "ase>=3.26.0" scipy==1.16 fairchem-data-omat fairchem-data-oc
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Add `fairchem-core>=2.19.0` only for the FAIRChem calculator or `.aselmdb` I/O.
|
|
229
|
+
|
|
230
|
+
## Configuration
|
|
231
|
+
|
|
232
|
+
### Hugging Face Login
|
|
233
|
+
|
|
234
|
+
The CLI login may encounter issues. Use the Python interface instead:
|
|
235
|
+
|
|
236
|
+
```bash
|
|
237
|
+
python -c "import huggingface_hub; huggingface_hub.login()"
|
|
238
|
+
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Alternatively, export the token directly:
|
|
242
|
+
|
|
243
|
+
```bash
|
|
244
|
+
export HF_TOKEN="***"
|
|
245
|
+
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### Environment Variables
|
|
249
|
+
|
|
250
|
+
Add these to your `.bashrc` or run before execution to manage cache and paths:
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
# Move cache to scratch to save home directory space
|
|
254
|
+
export FAIRCHEM_CACHE_DIR="$SCRATCH/.cache/fairchem"
|
|
255
|
+
export PYTHONPATH=<saddlemill_path>:$PYTHONPATH
|
|
256
|
+
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## Usage
|
|
260
|
+
|
|
261
|
+
### Interactive Node
|
|
262
|
+
|
|
263
|
+
Request an interactive node:
|
|
264
|
+
|
|
265
|
+
```bash
|
|
266
|
+
idev -p gh-dev -N 1 -m 120 -A YOUR_ALLOCATION
|
|
267
|
+
# or for the 3 A100 per node HPC:
|
|
268
|
+
idev -p gpu-a100-dev -N 1 -m 120 -A YOUR_ALLOCATION
|
|
269
|
+
# or for the in-house CPU cluster:
|
|
270
|
+
salloc --nodes 2 --qos debug
|
|
271
|
+
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Runtime Requirements
|
|
275
|
+
|
|
276
|
+
Ensure you run these commands (or add to `.bashrc`) every time you log in or start a job:
|
|
277
|
+
|
|
278
|
+
```bash
|
|
279
|
+
# On the GH200 HPC:
|
|
280
|
+
export PYTHONPATH=<saddlemill_path>:$PYTHONPATH
|
|
281
|
+
export FAIRCHEM_CACHE_DIR="$SCRATCH/.cache/fairchem"
|
|
282
|
+
export LD_LIBRARY_PATH=/opt/apps/cuda/12.4/targets/sbsa-linux/lib/:$LD_LIBRARY_PATH
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
# On the 3 A100 per node HPC:
|
|
286
|
+
export PYTHONPATH=<saddlemill_path>:$PYTHONPATH
|
|
287
|
+
export FAIRCHEM_CACHE_DIR="$SCRATCH/.cache/fairchem"
|
|
288
|
+
module unload impi python3
|
|
289
|
+
module load cuda/12.8
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
# On the 4 A100 per node HPC
|
|
293
|
+
export PYTHONPATH=<saddlemill_path>:$PYTHONPATH
|
|
294
|
+
export FAIRCHEM_CACHE_DIR="$SCRATCH/.cache/fairchem"
|
|
295
|
+
# --- START: FIX LIBRARY PATHS ---
|
|
296
|
+
export PY_SITE_PKGS=$(python -c "import site; print(site.getsitepackages()[0])")
|
|
297
|
+
export NVIDIA_DIR="${PY_SITE_PKGS}/nvidia"
|
|
298
|
+
# Prepend ALL Nvidia libraries to the load path
|
|
299
|
+
for lib in cuda_runtime nvjitlink cusparse cublas cufft cudnn curand cusolver nccl; do
|
|
300
|
+
export LD_LIBRARY_PATH="${NVIDIA_DIR}/${lib}/lib:${LD_LIBRARY_PATH}"
|
|
301
|
+
done
|
|
302
|
+
# --- END: FIX LIBRARY PATHS ---
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
# On the in-house CPU cluster (VASP-only; no CUDA/FAIRChem):
|
|
306
|
+
export PYTHONPATH=<saddlemill_path>:$PYTHONPATH
|
|
307
|
+
export VASP_PP_PATH=/path/to/vasp/potcars # directory holding potpaw_PBE.<version>/
|
|
308
|
+
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### Running SaddleMill
|
|
312
|
+
|
|
313
|
+
Create a `config.ini` in your working directory (see `CLAUDE.md` for full reference), place your input `.traj` files in `dir_path` (subdirectories are scanned recursively), then launch:
|
|
314
|
+
|
|
315
|
+
```bash
|
|
316
|
+
# Distributed (multi-node, multi-GPU)
|
|
317
|
+
srun -N $SLURM_NNODES -n $SLURM_NNODES --gpus-per-node=4 flux start python -u -m saddlemill
|
|
318
|
+
|
|
319
|
+
# Distributed on the in-house CPU cluster (no --gpus-per-node; --mpi=pmi2 + clear inherited
|
|
320
|
+
# PMI vars so the inner Flux/MPI doesn't clash with the outer srun):
|
|
321
|
+
srun -N $SLURM_NNODES -n $SLURM_NNODES --mpi=pmi2 flux start \
|
|
322
|
+
env -u PMI2_FD -u PMI_FD -u PMI2_RANK -u PMI_RANK -u PMI2_SIZE -u PMI_SIZE -u PMI2_SPROUTE \
|
|
323
|
+
python -u -m saddlemill
|
|
324
|
+
|
|
325
|
+
# Serial (single GPU, useful for debugging)
|
|
326
|
+
# Set executorlib = False in config.ini, then:
|
|
327
|
+
python -u -m saddlemill
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### Resume and Continuation
|
|
331
|
+
|
|
332
|
+
SaddleMill automatically handles resume if a job times out or is interrupted:
|
|
333
|
+
|
|
334
|
+
- **Resume unfinished jobs**: Just resubmit with the same `config.ini`. By default (`run_jobs = remaining`), only jobs that never ran are picked up. Already-completed jobs are skipped.
|
|
335
|
+
|
|
336
|
+
- **Redo specific categories**: Set `run_jobs` to target specific outcomes. Selection is per-line: a job with mixed results (e.g., 3 converged + 3 not_converged Dimer attempts) is selectable by both categories. Only matching entries are redone; the rest stay.
|
|
337
|
+
```ini
|
|
338
|
+
run_jobs = not_converged # Redo unconverged attempts/sub-bands/sides
|
|
339
|
+
run_jobs = converged # Redo converged entries (e.g., refine with VASP)
|
|
340
|
+
run_jobs = errored # Retry errored entries
|
|
341
|
+
run_jobs = all # Redo everything
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
- **Continue from previous result** (`continue_from_result = True`, default): When re-running, SaddleMill extracts the last result from output trajs and continues optimization from there. For Dimer, each matching attempt continues with its eigenmode. For NEB, matching sub-bands continue from their extracted images. For DoubleMinimization, unconverged sides continue from their last state. Errored entries fall back to fresh generation. Set `continue_from_result = False` to generate fresh replacements (same reaction types / same sub-band endpoints).
|
|
345
|
+
|
|
346
|
+
- **Archiving**: Old files are fully backed up as `previous_{N}.zip`. Only matching entries are removed from active CSVs and output trajectories (per-attempt for Dimer, per-sub-band for NEB, per-side for DoubleMinimization).
|
|
347
|
+
|
|
348
|
+
- **Fresh start**: Delete `traj_files_ordered.json` and the output directories to start completely from scratch.
|
|
349
|
+
|
|
350
|
+
### Dimer: `initial_guess` Reaction Type
|
|
351
|
+
|
|
352
|
+
The `initial_guess` reaction type is for running the dimer method on a **pre-prepared TS guess** from an external source (another code, a database, or a different SaddleMill method like NEB). It starts the dimer from the input geometry as-is with no displacement and no supercell expansion (even if `supercell = True`). If the input structure carries a mode, it seeds the dimer instead of a random guess: the exact Hessian mode `atoms.info['eigenmodes3'][0]` (from a SinglePoint `compute_eigenmodes` pass) is used first, else a saddle search's `atoms.info['approx_eigenmode']`.
|
|
353
|
+
|
|
354
|
+
```ini
|
|
355
|
+
[ourDimer]
|
|
356
|
+
dataset_type = bulk # or oc
|
|
357
|
+
reaction_types = initial_guess
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
This is different from `continue_from_result`, which is an automatic mechanism for continuing a previous SaddleMill run. Use `initial_guess` when bringing a TS from outside SaddleMill; `continue_from_result` handles the "pick up where I left off" case internally.
|
|
361
|
+
|
|
362
|
+
### VASP input generation (`input_generator`)
|
|
363
|
+
|
|
364
|
+
By default the `[Vasp]` section *is* your INCAR — every tag you list there is written to the calculator. When you want a standard input recipe (e.g. OMat24 or OC20) instead of hand-writing every tag, set `input_generator` in the `[ourVasp]` section. (`[ourVasp]` holds SaddleMill's VASP-input knobs; `[Vasp]` stays a pure ASE pass-through.) It works for **all methods** (NEB, Dimer, Minimization, DoubleMinimization, SinglePoint).
|
|
365
|
+
|
|
366
|
+
```ini
|
|
367
|
+
[ourVasp]
|
|
368
|
+
input_generator = omat24_static # omat24_static | omat24_relax | cheap_omat
|
|
369
|
+
# | oc20 | cheap_oc20 | oc22 | cheap_oc22
|
|
370
|
+
# ...or 'my_pkg.my_module:my_func'
|
|
371
|
+
# ...or '/path/to/my_inputs.py:my_func'
|
|
372
|
+
|
|
373
|
+
[Vasp]
|
|
374
|
+
encut = 600 # any [Vasp] tag overrides the generator
|
|
375
|
+
pp = PBE
|
|
376
|
+
xc = PBE
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
Per-tag precedence is **`[Vasp]` key → `input_generator` output → ASE/VASP default**: a tag you set in `[Vasp]` always wins; a tag the generator supplies is used when `[Vasp]` is silent; a tag in neither falls back to ASE's default. The generator contributes only electronic/accuracy settings — ionic-driver tags (`IBRION`/`NSW`/`POTIM`/`EDIFFG`) are stripped, since SaddleMill drives the geometry through ASE.
|
|
380
|
+
|
|
381
|
+
A custom generator is any function `generator(atoms) -> dict` returning ASE-`Vasp` kwargs (lowercased INCAR tags plus `kpts`/`gamma`/`setups`/`magmom`); point `input_generator` at it as `module:func` or `file.py:func`. The `omat24_*`/`cheap_omat` built-ins need `fairchem-data-omat` and `oc20`/`cheap_oc20` need `fairchem-data-oc` installed (see the HPC installation above); `oc22`/`cheap_oc22` are self-contained (the exact Meta OC22 settings are frozen inside SaddleMill, since the upstream `OC22_dataset` code is deprecated and today's pymatgen has drifted from what Meta ran).
|
|
382
|
+
|
|
383
|
+
The `oc22` built-in reproduces the OC22 dataset's level of theory exactly (arXiv:2206.08917): PBE + Materials Project Hubbard U, spin-polarized with MP-default initial moments (magmoms already on the atoms win), ENCUT 500, Γ-centered `ceil(30/a) × ceil(30/b) × 1` meshes, 2022-era MPRelaxSet POTCAR setups, and dipole correction only when the structure has tag=2 adsorbate atoms (Meta applied it to adsorbate+slabs only). Meta used the PBE 5.4 POTCAR library — add `pp_version = 54` in `[Vasp]` if your `$VASP_PP_PATH` has versioned `potpaw_PBE.54` folders. The `cheap_oc20`/`cheap_oc22` variants follow the `cheap_omat` recipe: the in-plane k-point multiplier is halved (40→20 / 30→15) and POTCARs are lightened (`minimal` base, soft `_s` O/C/N, f-in-core lanthanides kept) so ENCUT can drop to ~300 via `[Vasp]` — spin, U and the functional are untouched, so the cheap pass stays on the same PES; reconverge with the exact preset afterwards.
|
|
384
|
+
|
|
385
|
+
### Extra input files (`extra_input_files`), e.g. VTST MODECAR
|
|
386
|
+
|
|
387
|
+
`input_generator` only sets INCAR/k-points/POTCAR. To drop **extra files** into the VASP working directory — most usefully a VTST `MODECAR` (initial dimer mode) — use `extra_input_files`:
|
|
388
|
+
|
|
389
|
+
```ini
|
|
390
|
+
[ourVasp]
|
|
391
|
+
extra_input_files = modecar # built-in | 'module:func' | 'file.py:func' | space-separated list
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
The built-in `modecar` builds the file from the frame's lowest mode, reordered to POSCAR order: `atoms.info['eigenmodes3'][0]` (exact, from a SinglePoint `compute_eigenmodes` pass) if present, else `atoms.info['approx_eigenmode']` (the mode NEB/Dimer/Sella stamp on their output frames). A DoubleMinimization TS frame carries the mode it displaced along, as `eigenmodes3` when that mode was an exact input mode left unrefined and as `approx_eigenmode` otherwise. A custom writer is any `writer(calc, atoms, directory) -> None` (it receives the calculator, so it can use `calc.sort`).
|
|
395
|
+
|
|
396
|
+
Symmetrically, **`extra_outputs`** parses files back *out* of the VASP directory after the run and merges the result into the output frame's `.info`. A parser is any `parser(calc, atoms, directory) -> dict`; the built-in `vtst_dimer` returns `approx_eigenmode` (from `NEWMODECAR`, mapped back to atoms order via `calc.resort`) and `approx_curvature` (from `DIMCAR`).
|
|
397
|
+
|
|
398
|
+
Together these enable a **VASP-internal VTST dimer driven by SaddleMill as a pure launcher**:
|
|
399
|
+
|
|
400
|
+
```ini
|
|
401
|
+
[ourVasp]
|
|
402
|
+
input_generator = omat24_static # base INCAR recipe
|
|
403
|
+
extra_input_files = modecar # write MODECAR from each frame's lowest mode
|
|
404
|
+
extra_outputs = vtst_dimer # read NEWMODECAR/DIMCAR -> approx_eigenmode/approx_curvature onto output
|
|
405
|
+
|
|
406
|
+
[Vasp] # VTST dimer driver tags (plain pass-through)
|
|
407
|
+
ichain = 2
|
|
408
|
+
ibrion = 3
|
|
409
|
+
potim = 0
|
|
410
|
+
iopt = 3 # QuickMin: force-driven step, no fixed jump
|
|
411
|
+
maxmove = 0.1
|
|
412
|
+
ddr = 0.005
|
|
413
|
+
nsw = 300
|
|
414
|
+
ediffg = -0.03
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
with `method = SinglePoint`, `Calculator = Vasp`. SaddleMill runs one VASP call per structure, VASP runs the whole dimer internally, and the converged saddle (geometry + E/F) **plus** the refined `approx_eigenmode`/`approx_curvature` are written to the output traj. Use plain `Vasp` (not `VaspInteractive`, which forces `ibrion = -1`). The input frames must carry `atoms.info['approx_eigenmode']` (e.g. NEB-CI / Dimer / Sella outputs) or `atoms.info['eigenmodes3']` (a SinglePoint `compute_eigenmodes` pass), and a DoubleMinimization TS frame carries one of the two (see `modecar` above).
|
|
418
|
+
|
|
419
|
+
## Known limitations
|
|
420
|
+
|
|
421
|
+
These edge cases fall outside the usual Dimer → DoubleMinimization workflow and are not fixed yet.
|
|
422
|
+
|
|
423
|
+
- **Fresh Dimer/Sella attempts reuse a stored mode.** When an input frame already carries a mode (`eigenmodes3` or `approx_eigenmode`, as on a Dimer, Sella, NEB-CI or SinglePoint `compute_eigenmodes` output), every fresh displacement attempt is seeded with that mode, although it was computed for the input geometry and the attempt starts from a displaced one. On bulk inputs the supercell or vacancy step changes the atom count, so the attempt fails with a shape error such as `operands could not be broadcast together with shapes (31,3) (4,3)`. `initial_guess` attempts and continuations are meant to reuse the mode and work as intended.
|
|
424
|
+
- **SinglePoint `.traj` output hides the input's mode.** Mode readers check the top level of `.info` and one `orig_info` level down, and a SinglePoint output frame stores only its own results at the top level. A DoubleMinimization run on the SinglePoint output of a Dimer run therefore stops with "Input structure missing 'eigenmodes3' or 'approx_eigenmode' in info." unless that SinglePoint pass wrote its own mode (`compute_eigenmodes = True`). Dimer and Sella outputs already carry energies and forces, so DoubleMinimization can read them directly.
|
|
425
|
+
- **`orig_info` history grows without limit.** Each job wraps the whole previous `.info` in a new `orig_info` level and none is ever removed, so a Dimer → DoubleMinimization cycle adds 2 levels. The usual 2 to 4 levels are harmless, but a long chain such as an AKMC loop would grow the stored frames at every step, and ASE's traj/aselmdb writers fail with a `RecursionError` between 950 and 1000 levels (about 475 Dimer → DoubleMinimization cycles, with Python's default recursion limit of 1000). Trimming old levels would also have to keep the provenance levels that `scripts/` join on.
|
|
426
|
+
- **Dimer runs are not bit-reproducible.** ASE's `MinModeAtoms` seeds its own random generator from the clock when no `random_seed` is given, and SaddleMill passes none, so two Dimer runs on the same input can differ even with fixed `random` and NumPy seeds.
|
|
427
|
+
|
|
428
|
+
## Testing
|
|
429
|
+
|
|
430
|
+
Install test dependencies:
|
|
431
|
+
|
|
432
|
+
```bash
|
|
433
|
+
pip install pytest pytest-timeout
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
Run CPU-only unit tests (no GPU needed, works on login nodes):
|
|
437
|
+
|
|
438
|
+
```bash
|
|
439
|
+
pytest -m "not gpu and not flux" -v
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
Run all tests (requires a GPU node):
|
|
443
|
+
|
|
444
|
+
```bash
|
|
445
|
+
pytest -v --timeout=600
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
Run only GPU integration tests:
|
|
449
|
+
|
|
450
|
+
```bash
|
|
451
|
+
pytest -m gpu -v
|
|
452
|
+
```
|