cuRDF 0.5.0__tar.gz → 0.5.2__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.
- {curdf-0.5.0 → curdf-0.5.2}/PKG-INFO +26 -5
- {curdf-0.5.0 → curdf-0.5.2}/README.md +25 -4
- {curdf-0.5.0 → curdf-0.5.2}/cuRDF.egg-info/PKG-INFO +26 -5
- {curdf-0.5.0 → curdf-0.5.2}/cuRDF.egg-info/SOURCES.txt +2 -1
- {curdf-0.5.0 → curdf-0.5.2}/curdf/adapters.py +64 -22
- {curdf-0.5.0 → curdf-0.5.2}/curdf/rdf.py +50 -14
- {curdf-0.5.0 → curdf-0.5.2}/pyproject.toml +1 -1
- curdf-0.5.2/tests/test_water_rdfs.py +55 -0
- {curdf-0.5.0 → curdf-0.5.2}/LICENSE +0 -0
- {curdf-0.5.0 → curdf-0.5.2}/cuRDF.egg-info/dependency_links.txt +0 -0
- {curdf-0.5.0 → curdf-0.5.2}/cuRDF.egg-info/requires.txt +0 -0
- {curdf-0.5.0 → curdf-0.5.2}/cuRDF.egg-info/top_level.txt +0 -0
- {curdf-0.5.0 → curdf-0.5.2}/curdf/__init__.py +0 -0
- {curdf-0.5.0 → curdf-0.5.2}/curdf/cell.py +0 -0
- {curdf-0.5.0 → curdf-0.5.2}/curdf/neighbor.py +0 -0
- {curdf-0.5.0 → curdf-0.5.2}/curdf/plotting.py +0 -0
- {curdf-0.5.0 → curdf-0.5.2}/setup.cfg +0 -0
- {curdf-0.5.0 → curdf-0.5.2}/tests/test_rdf_core.py +0 -0
- {curdf-0.5.0 → curdf-0.5.2}/tests/test_rdf_cuda.py +0 -0
- {curdf-0.5.0 → curdf-0.5.2}/tests/test_rdf_sources.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cuRDF
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.2
|
|
4
4
|
Summary: GPU-accelerated radial distribution functions with NVIDIA ALCHEMI Toolkit-Ops neighbor lists and PyTorch.
|
|
5
5
|
Author: Joseph Hart
|
|
6
6
|
License-Expression: MIT
|
|
@@ -45,7 +45,9 @@ Requires-Dist: sphinx-rtd-theme; extra == "docs"
|
|
|
45
45
|
Requires-Dist: myst-parser; extra == "docs"
|
|
46
46
|
Dynamic: license-file
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+

|
|
49
|
+
|
|
50
|
+
---
|
|
49
51
|
|
|
50
52
|
[](https://doi.org/10.5281/zenodo.1085332119) [](https://pypi.org/project/cuRDF/)
|
|
51
53
|
|
|
@@ -56,7 +58,8 @@ CUDA-accelerated radial distribution functions using [NVIDIA ALCHEMI Toolkit-Ops
|
|
|
56
58
|
|
|
57
59
|

|
|
58
60
|
|
|
59
|
-
cuRDF is benchmarked against RDF (MDAnalysis) and neighbour list implementations on CPU (AMD Ryzen 9 9950X, 32 threads) and GPU (NVIDIA RTX 5090) for systems of varying sizes at a density of 0.05 atoms/ų over 1000 frames.
|
|
61
|
+
cuRDF is benchmarked against other RDF (MDAnalysis) and neighbour list implementations on CPU (AMD Ryzen 9 9950X, 32 threads) and GPU (NVIDIA RTX 5090) for systems of varying sizes at a density of 0.05 atoms/ų over 1000 frames.
|
|
62
|
+
Benchmarks use random positions in cubic cells sized to maintain a fixed number density (orthorhombic boxes, periodic in all directions).
|
|
60
63
|
|
|
61
64
|
## Install
|
|
62
65
|
Latest release:
|
|
@@ -114,9 +117,27 @@ bins, gr = rdf(
|
|
|
114
117
|
|
|
115
118
|
If the topology lacks atom names (only numeric types), supply a mapping:
|
|
116
119
|
```python
|
|
117
|
-
bins, gr = rdf(
|
|
120
|
+
bins, gr = rdf(
|
|
121
|
+
u,
|
|
122
|
+
species_a="C",
|
|
123
|
+
species_b="O",
|
|
124
|
+
atom_types_map={1: "C", 2: "H"}
|
|
125
|
+
)
|
|
118
126
|
```
|
|
119
127
|
|
|
128
|
+
## Validation
|
|
129
|
+
|
|
130
|
+
RDFs for liquid water (64 atoms, 1 ns) computed using a trajectory from [Lim et al. (2026)](https://arxiv.org/abs/2601.20134) match reference curves for all pairs:
|
|
131
|
+
|
|
132
|
+
<table>
|
|
133
|
+
<tr>
|
|
134
|
+
<td><img src="tests/cuRDF_results/rdf_OO_comparison.svg" alt="O–O RDF" width="250"></td>
|
|
135
|
+
<td><img src="tests/cuRDF_results/rdf_HH_comparison.svg" alt="H–H RDF" width="250"></td>
|
|
136
|
+
<td><img src="tests/cuRDF_results/rdf_OH_comparison.svg" alt="O–H RDF" width="250"></td>
|
|
137
|
+
</tr>
|
|
138
|
+
</table>
|
|
139
|
+
|
|
140
|
+
|
|
120
141
|
## Citation
|
|
121
142
|
If you use cuRDF in your work, please cite:
|
|
122
143
|
```
|
|
@@ -126,7 +147,7 @@ If you use cuRDF in your work, please cite:
|
|
|
126
147
|
month = dec,
|
|
127
148
|
year = 2025,
|
|
128
149
|
publisher = {Zenodo},
|
|
129
|
-
version = {0.
|
|
150
|
+
version = {0.5.0},
|
|
130
151
|
doi = {10.5281/zenodo.1085332119},
|
|
131
152
|
url = {https://doi.org/10.5281/zenodo.1085332119}
|
|
132
153
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
---
|
|
2
4
|
|
|
3
5
|
[](https://doi.org/10.5281/zenodo.1085332119) [](https://pypi.org/project/cuRDF/)
|
|
4
6
|
|
|
@@ -9,7 +11,8 @@ CUDA-accelerated radial distribution functions using [NVIDIA ALCHEMI Toolkit-Ops
|
|
|
9
11
|
|
|
10
12
|

|
|
11
13
|
|
|
12
|
-
cuRDF is benchmarked against RDF (MDAnalysis) and neighbour list implementations on CPU (AMD Ryzen 9 9950X, 32 threads) and GPU (NVIDIA RTX 5090) for systems of varying sizes at a density of 0.05 atoms/ų over 1000 frames.
|
|
14
|
+
cuRDF is benchmarked against other RDF (MDAnalysis) and neighbour list implementations on CPU (AMD Ryzen 9 9950X, 32 threads) and GPU (NVIDIA RTX 5090) for systems of varying sizes at a density of 0.05 atoms/ų over 1000 frames.
|
|
15
|
+
Benchmarks use random positions in cubic cells sized to maintain a fixed number density (orthorhombic boxes, periodic in all directions).
|
|
13
16
|
|
|
14
17
|
## Install
|
|
15
18
|
Latest release:
|
|
@@ -67,9 +70,27 @@ bins, gr = rdf(
|
|
|
67
70
|
|
|
68
71
|
If the topology lacks atom names (only numeric types), supply a mapping:
|
|
69
72
|
```python
|
|
70
|
-
bins, gr = rdf(
|
|
73
|
+
bins, gr = rdf(
|
|
74
|
+
u,
|
|
75
|
+
species_a="C",
|
|
76
|
+
species_b="O",
|
|
77
|
+
atom_types_map={1: "C", 2: "H"}
|
|
78
|
+
)
|
|
71
79
|
```
|
|
72
80
|
|
|
81
|
+
## Validation
|
|
82
|
+
|
|
83
|
+
RDFs for liquid water (64 atoms, 1 ns) computed using a trajectory from [Lim et al. (2026)](https://arxiv.org/abs/2601.20134) match reference curves for all pairs:
|
|
84
|
+
|
|
85
|
+
<table>
|
|
86
|
+
<tr>
|
|
87
|
+
<td><img src="tests/cuRDF_results/rdf_OO_comparison.svg" alt="O–O RDF" width="250"></td>
|
|
88
|
+
<td><img src="tests/cuRDF_results/rdf_HH_comparison.svg" alt="H–H RDF" width="250"></td>
|
|
89
|
+
<td><img src="tests/cuRDF_results/rdf_OH_comparison.svg" alt="O–H RDF" width="250"></td>
|
|
90
|
+
</tr>
|
|
91
|
+
</table>
|
|
92
|
+
|
|
93
|
+
|
|
73
94
|
## Citation
|
|
74
95
|
If you use cuRDF in your work, please cite:
|
|
75
96
|
```
|
|
@@ -79,7 +100,7 @@ If you use cuRDF in your work, please cite:
|
|
|
79
100
|
month = dec,
|
|
80
101
|
year = 2025,
|
|
81
102
|
publisher = {Zenodo},
|
|
82
|
-
version = {0.
|
|
103
|
+
version = {0.5.0},
|
|
83
104
|
doi = {10.5281/zenodo.1085332119},
|
|
84
105
|
url = {https://doi.org/10.5281/zenodo.1085332119}
|
|
85
106
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cuRDF
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.2
|
|
4
4
|
Summary: GPU-accelerated radial distribution functions with NVIDIA ALCHEMI Toolkit-Ops neighbor lists and PyTorch.
|
|
5
5
|
Author: Joseph Hart
|
|
6
6
|
License-Expression: MIT
|
|
@@ -45,7 +45,9 @@ Requires-Dist: sphinx-rtd-theme; extra == "docs"
|
|
|
45
45
|
Requires-Dist: myst-parser; extra == "docs"
|
|
46
46
|
Dynamic: license-file
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+

|
|
49
|
+
|
|
50
|
+
---
|
|
49
51
|
|
|
50
52
|
[](https://doi.org/10.5281/zenodo.1085332119) [](https://pypi.org/project/cuRDF/)
|
|
51
53
|
|
|
@@ -56,7 +58,8 @@ CUDA-accelerated radial distribution functions using [NVIDIA ALCHEMI Toolkit-Ops
|
|
|
56
58
|
|
|
57
59
|

|
|
58
60
|
|
|
59
|
-
cuRDF is benchmarked against RDF (MDAnalysis) and neighbour list implementations on CPU (AMD Ryzen 9 9950X, 32 threads) and GPU (NVIDIA RTX 5090) for systems of varying sizes at a density of 0.05 atoms/ų over 1000 frames.
|
|
61
|
+
cuRDF is benchmarked against other RDF (MDAnalysis) and neighbour list implementations on CPU (AMD Ryzen 9 9950X, 32 threads) and GPU (NVIDIA RTX 5090) for systems of varying sizes at a density of 0.05 atoms/ų over 1000 frames.
|
|
62
|
+
Benchmarks use random positions in cubic cells sized to maintain a fixed number density (orthorhombic boxes, periodic in all directions).
|
|
60
63
|
|
|
61
64
|
## Install
|
|
62
65
|
Latest release:
|
|
@@ -114,9 +117,27 @@ bins, gr = rdf(
|
|
|
114
117
|
|
|
115
118
|
If the topology lacks atom names (only numeric types), supply a mapping:
|
|
116
119
|
```python
|
|
117
|
-
bins, gr = rdf(
|
|
120
|
+
bins, gr = rdf(
|
|
121
|
+
u,
|
|
122
|
+
species_a="C",
|
|
123
|
+
species_b="O",
|
|
124
|
+
atom_types_map={1: "C", 2: "H"}
|
|
125
|
+
)
|
|
118
126
|
```
|
|
119
127
|
|
|
128
|
+
## Validation
|
|
129
|
+
|
|
130
|
+
RDFs for liquid water (64 atoms, 1 ns) computed using a trajectory from [Lim et al. (2026)](https://arxiv.org/abs/2601.20134) match reference curves for all pairs:
|
|
131
|
+
|
|
132
|
+
<table>
|
|
133
|
+
<tr>
|
|
134
|
+
<td><img src="tests/cuRDF_results/rdf_OO_comparison.svg" alt="O–O RDF" width="250"></td>
|
|
135
|
+
<td><img src="tests/cuRDF_results/rdf_HH_comparison.svg" alt="H–H RDF" width="250"></td>
|
|
136
|
+
<td><img src="tests/cuRDF_results/rdf_OH_comparison.svg" alt="O–H RDF" width="250"></td>
|
|
137
|
+
</tr>
|
|
138
|
+
</table>
|
|
139
|
+
|
|
140
|
+
|
|
120
141
|
## Citation
|
|
121
142
|
If you use cuRDF in your work, please cite:
|
|
122
143
|
```
|
|
@@ -126,7 +147,7 @@ If you use cuRDF in your work, please cite:
|
|
|
126
147
|
month = dec,
|
|
127
148
|
year = 2025,
|
|
128
149
|
publisher = {Zenodo},
|
|
129
|
-
version = {0.
|
|
150
|
+
version = {0.5.0},
|
|
130
151
|
doi = {10.5281/zenodo.1085332119},
|
|
131
152
|
url = {https://doi.org/10.5281/zenodo.1085332119}
|
|
132
153
|
}
|
|
@@ -50,9 +50,14 @@ def rdf_from_mdanalysis(
|
|
|
50
50
|
selection_b: str | None = None,
|
|
51
51
|
atom_types_map: dict | None = None,
|
|
52
52
|
index=None,
|
|
53
|
+
start: int | None = None,
|
|
54
|
+
stop: int | None = None,
|
|
55
|
+
step: int | None = None,
|
|
53
56
|
r_min: float = 1.0,
|
|
54
57
|
r_max: float = 6.0,
|
|
55
58
|
nbins: int = 100,
|
|
59
|
+
r_min_floor: float = 1e-6,
|
|
60
|
+
pbc: tuple[bool, bool, bool] = (True, True, True),
|
|
56
61
|
device="cuda",
|
|
57
62
|
torch_dtype=None,
|
|
58
63
|
half_fill: bool = True,
|
|
@@ -77,12 +82,22 @@ def rdf_from_mdanalysis(
|
|
|
77
82
|
Optional mapping for numeric atom types to element names (e.g., ``{1: "C", 2: "H"}``).
|
|
78
83
|
index
|
|
79
84
|
Optional trajectory index/selector.
|
|
85
|
+
start
|
|
86
|
+
Optional starting frame (inclusive) when iterating the trajectory.
|
|
87
|
+
stop
|
|
88
|
+
Optional stopping frame (exclusive) when iterating the trajectory.
|
|
89
|
+
step
|
|
90
|
+
Optional frame stride when iterating the trajectory.
|
|
80
91
|
r_min
|
|
81
92
|
Minimum distance included in the histogram.
|
|
82
93
|
r_max
|
|
83
94
|
Maximum distance included in the histogram.
|
|
84
95
|
nbins
|
|
85
96
|
Number of histogram bins.
|
|
97
|
+
r_min_floor
|
|
98
|
+
Small lower bound to exclude pathological self/near-zero distances when ``r_min`` is zero.
|
|
99
|
+
pbc
|
|
100
|
+
Tuple of periodic boundary flags for (a, b, c) directions.
|
|
86
101
|
device
|
|
87
102
|
Torch device string or object used for computation.
|
|
88
103
|
torch_dtype
|
|
@@ -149,21 +164,26 @@ def rdf_from_mdanalysis(
|
|
|
149
164
|
raise ValueError(f"No atoms found for species_a='{species_a}' (check atom names or --atom-types mapping)")
|
|
150
165
|
if len(ag_b) == 0:
|
|
151
166
|
raise ValueError(f"No atoms found for species_b='{species_b or species_a}' (check atom names or --atom-types mapping)")
|
|
152
|
-
if wrap_positions and mda_wrap is not None:
|
|
167
|
+
if wrap_positions and mda_wrap is not None and all(pbc):
|
|
153
168
|
ag_wrap = ag_a if selection_b is None else (ag_a | ag_b)
|
|
154
169
|
universe.trajectory.add_transformations(mda_wrap(ag_wrap, compound="atoms"))
|
|
155
170
|
|
|
156
171
|
same_species = len(ag_a) == len(ag_b) and ag_a is ag_b
|
|
157
172
|
|
|
158
173
|
def frames():
|
|
159
|
-
|
|
174
|
+
if index is not None:
|
|
175
|
+
traj = universe.trajectory[index]
|
|
176
|
+
elif start is not None or stop is not None or step is not None:
|
|
177
|
+
traj = universe.trajectory[start:stop:step]
|
|
178
|
+
else:
|
|
179
|
+
traj = universe.trajectory
|
|
160
180
|
for ts in tqdm(traj, desc="Frames (MDAnalysis)", unit="frame"):
|
|
161
181
|
cell = _mdanalysis_cell_matrix(ts.dimensions)
|
|
162
182
|
if same_species:
|
|
163
183
|
yield {
|
|
164
184
|
"positions": ag_a.positions.astype(np.float32, copy=False),
|
|
165
185
|
"cell": cell,
|
|
166
|
-
"pbc": (
|
|
186
|
+
"pbc": tuple(pbc),
|
|
167
187
|
}
|
|
168
188
|
else:
|
|
169
189
|
pos_a = ag_a.positions.astype(np.float32, copy=False)
|
|
@@ -176,7 +196,7 @@ def rdf_from_mdanalysis(
|
|
|
176
196
|
yield {
|
|
177
197
|
"positions": pos,
|
|
178
198
|
"cell": cell,
|
|
179
|
-
"pbc": (
|
|
199
|
+
"pbc": tuple(pbc),
|
|
180
200
|
"group_a_mask": group_a_mask,
|
|
181
201
|
"group_b_mask": group_b_mask,
|
|
182
202
|
}
|
|
@@ -189,6 +209,7 @@ def rdf_from_mdanalysis(
|
|
|
189
209
|
r_min=r_min,
|
|
190
210
|
r_max=r_max,
|
|
191
211
|
nbins=nbins,
|
|
212
|
+
r_min_floor=r_min_floor,
|
|
192
213
|
device=device,
|
|
193
214
|
torch_dtype=torch_dtype,
|
|
194
215
|
half_fill=half_fill,
|
|
@@ -234,6 +255,8 @@ def rdf_from_ase(
|
|
|
234
255
|
r_min: float = 1.0,
|
|
235
256
|
r_max: float = 6.0,
|
|
236
257
|
nbins: int = 100,
|
|
258
|
+
r_min_floor: float = 1e-6,
|
|
259
|
+
pbc: tuple[bool, bool, bool] = (True, True, True),
|
|
237
260
|
device="cuda",
|
|
238
261
|
torch_dtype=None,
|
|
239
262
|
half_fill: bool = True,
|
|
@@ -266,6 +289,10 @@ def rdf_from_ase(
|
|
|
266
289
|
Maximum distance included in the histogram.
|
|
267
290
|
nbins
|
|
268
291
|
Number of histogram bins.
|
|
292
|
+
r_min_floor
|
|
293
|
+
Small lower bound to exclude pathological self/near-zero distances when ``r_min`` is zero.
|
|
294
|
+
pbc
|
|
295
|
+
Tuple of periodic boundary flags for (a, b, c) directions.
|
|
269
296
|
device
|
|
270
297
|
Torch device string or object used for computation.
|
|
271
298
|
torch_dtype
|
|
@@ -308,7 +335,7 @@ def rdf_from_ase(
|
|
|
308
335
|
|
|
309
336
|
if atom_types_map:
|
|
310
337
|
# map numeric types to element names if provided
|
|
311
|
-
types = frame.get_array("numbers",
|
|
338
|
+
types = frame.get_array("numbers", False)
|
|
312
339
|
name_list = []
|
|
313
340
|
for t in types:
|
|
314
341
|
name = atom_types_map.get(t) or atom_types_map.get(str(t))
|
|
@@ -340,24 +367,38 @@ def rdf_from_ase(
|
|
|
340
367
|
"Check element symbols or index selection."
|
|
341
368
|
)
|
|
342
369
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
370
|
+
if isinstance(pbc, Iterable):
|
|
371
|
+
pbc_tuple = tuple(bool(x) for x in pbc)
|
|
372
|
+
else:
|
|
373
|
+
pbc_tuple = (bool(pbc), bool(pbc), bool(pbc))
|
|
374
|
+
wrap_flag = wrap_positions and all(pbc_tuple)
|
|
375
|
+
pos_all = frame.get_positions(wrap=wrap_flag)
|
|
347
376
|
cell = np.array(frame.get_cell().array, dtype=np.float32)
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
377
|
+
|
|
378
|
+
if same_species:
|
|
379
|
+
# Same-species: keep one group so half_fill + pair_factor=2 normalisation stays correct
|
|
380
|
+
yield {
|
|
381
|
+
"positions": pos_all[idx_a].astype(np.float32, copy=False),
|
|
382
|
+
"cell": cell,
|
|
383
|
+
"pbc": pbc_tuple,
|
|
384
|
+
}
|
|
385
|
+
else:
|
|
386
|
+
# Cross-species: concatenate and mask groups for ordered pairs
|
|
387
|
+
pos_a = pos_all[idx_a]
|
|
388
|
+
pos_b = pos_all[idx_b]
|
|
389
|
+
pos = np.concatenate([pos_a, pos_b], axis=0)
|
|
390
|
+
group_a_mask = np.zeros(len(pos), dtype=bool)
|
|
391
|
+
group_b_mask = np.zeros(len(pos), dtype=bool)
|
|
392
|
+
group_a_mask[: len(pos_a)] = True
|
|
393
|
+
group_b_mask[len(pos_a) :] = True
|
|
394
|
+
|
|
395
|
+
yield {
|
|
396
|
+
"positions": pos.astype(np.float32, copy=False),
|
|
397
|
+
"cell": cell,
|
|
398
|
+
"pbc": pbc_tuple,
|
|
399
|
+
"group_a_mask": group_a_mask,
|
|
400
|
+
"group_b_mask": group_b_mask,
|
|
401
|
+
}
|
|
361
402
|
|
|
362
403
|
same_species = (
|
|
363
404
|
(species_b is None or species_b == species_a)
|
|
@@ -371,6 +412,7 @@ def rdf_from_ase(
|
|
|
371
412
|
r_min=r_min,
|
|
372
413
|
r_max=r_max,
|
|
373
414
|
nbins=nbins,
|
|
415
|
+
r_min_floor=r_min_floor,
|
|
374
416
|
device=device,
|
|
375
417
|
torch_dtype=torch_dtype,
|
|
376
418
|
half_fill=half_fill,
|
|
@@ -10,6 +10,8 @@ from torch import Tensor
|
|
|
10
10
|
from .cell import cell_tensor, cell_volume, pbc_tensor
|
|
11
11
|
from .neighbor import build_neighbor_list
|
|
12
12
|
|
|
13
|
+
DEFAULT_R_MIN_FLOOR = 1e-6
|
|
14
|
+
|
|
13
15
|
|
|
14
16
|
def _update_counts(
|
|
15
17
|
counts: Tensor,
|
|
@@ -22,6 +24,7 @@ def _update_counts(
|
|
|
22
24
|
half_fill: bool,
|
|
23
25
|
max_neighbors: int | None,
|
|
24
26
|
method: str,
|
|
27
|
+
r_min_floor: float = DEFAULT_R_MIN_FLOOR,
|
|
25
28
|
group_a_mask: Tensor | None = None,
|
|
26
29
|
group_b_mask: Tensor | None = None,
|
|
27
30
|
) -> float:
|
|
@@ -44,6 +47,8 @@ def _update_counts(
|
|
|
44
47
|
Minimum distance included in the histogram.
|
|
45
48
|
r_max
|
|
46
49
|
Maximum distance included in the histogram.
|
|
50
|
+
r_min_floor
|
|
51
|
+
Small lower bound to exclude pathological self/near-zero distances when ``r_min`` is zero.
|
|
47
52
|
half_fill
|
|
48
53
|
Whether to build unique pairs (same-species mode).
|
|
49
54
|
max_neighbors
|
|
@@ -79,7 +84,10 @@ def _update_counts(
|
|
|
79
84
|
dr_vec = (positions[tgt] + shift_cart) - positions[src]
|
|
80
85
|
dist = torch.linalg.norm(dr_vec, dim=1)
|
|
81
86
|
|
|
82
|
-
|
|
87
|
+
r_min_eff = max(r_min, r_min_floor)
|
|
88
|
+
valid = (dist >= r_min_eff) & (dist < r_max)
|
|
89
|
+
# Drop any self-pairs that may appear in the neighbor list (important when r_min == 0).
|
|
90
|
+
valid = valid & (src != tgt)
|
|
83
91
|
if group_a_mask is not None and group_b_mask is not None:
|
|
84
92
|
src_mask = group_a_mask[src]
|
|
85
93
|
tgt_mask = group_b_mask[tgt]
|
|
@@ -91,13 +99,19 @@ def _update_counts(
|
|
|
91
99
|
counts.scatter_add_(0, bin_idx, torch.ones_like(bin_idx, dtype=torch.int64))
|
|
92
100
|
|
|
93
101
|
volume = cell_volume(cell)
|
|
102
|
+
|
|
94
103
|
if group_a_mask is not None and group_b_mask is not None:
|
|
95
104
|
n_a = group_a_mask.sum().item()
|
|
96
105
|
n_b = group_b_mask.sum().item()
|
|
97
|
-
|
|
106
|
+
# Check if same species
|
|
107
|
+
if torch.equal(group_a_mask, group_b_mask):
|
|
108
|
+
norm_factor = n_a * ((n_b - 1) / volume) # Exclude self-pairs
|
|
109
|
+
else:
|
|
110
|
+
norm_factor = n_a * (n_b / volume) # Different species
|
|
98
111
|
else:
|
|
99
112
|
n_atoms = positions.shape[0]
|
|
100
|
-
norm_factor = n_atoms * (n_atoms / volume) #
|
|
113
|
+
norm_factor = n_atoms * ((n_atoms - 1) / volume) # Exclude self-pairs
|
|
114
|
+
|
|
101
115
|
return norm_factor
|
|
102
116
|
|
|
103
117
|
|
|
@@ -149,6 +163,7 @@ def compute_rdf(
|
|
|
149
163
|
r_min: float = 1.0,
|
|
150
164
|
r_max: float = 6.0,
|
|
151
165
|
nbins: int = 100,
|
|
166
|
+
r_min_floor: float = DEFAULT_R_MIN_FLOOR,
|
|
152
167
|
device: str | torch.device = "cuda",
|
|
153
168
|
torch_dtype: torch.dtype = torch.float32,
|
|
154
169
|
half_fill: bool = True,
|
|
@@ -174,6 +189,8 @@ def compute_rdf(
|
|
|
174
189
|
Maximum distance included in the histogram.
|
|
175
190
|
nbins
|
|
176
191
|
Number of histogram bins.
|
|
192
|
+
r_min_floor
|
|
193
|
+
Small lower bound to exclude pathological self/near-zero distances when ``r_min`` is zero.
|
|
177
194
|
device
|
|
178
195
|
Torch device string or object used for computation.
|
|
179
196
|
torch_dtype
|
|
@@ -202,7 +219,8 @@ def compute_rdf(
|
|
|
202
219
|
cell_t = cell_tensor(cell, device=device, dtype=torch_dtype)
|
|
203
220
|
pbc_t = pbc_tensor(pbc, device=device)
|
|
204
221
|
|
|
205
|
-
|
|
222
|
+
r_min_eff = max(r_min, r_min_floor)
|
|
223
|
+
edges = torch.linspace(r_min_eff, r_max, nbins + 1, device=device, dtype=torch_dtype)
|
|
206
224
|
counts = torch.zeros(nbins, device=device, dtype=torch.int64)
|
|
207
225
|
|
|
208
226
|
group_a_mask = group_b_mask = None
|
|
@@ -215,26 +233,29 @@ def compute_rdf(
|
|
|
215
233
|
elif group_a_mask is not None:
|
|
216
234
|
group_b_mask = group_a_mask
|
|
217
235
|
|
|
236
|
+
cross_mode = group_a_mask is not None and group_b_mask is not None and not torch.equal(
|
|
237
|
+
group_a_mask, group_b_mask
|
|
238
|
+
)
|
|
239
|
+
half_fill_eff = False if cross_mode else half_fill
|
|
240
|
+
|
|
218
241
|
total_norm = _update_counts(
|
|
219
242
|
counts,
|
|
220
243
|
pos_t,
|
|
221
244
|
cell=cell_t,
|
|
222
245
|
pbc=pbc_t,
|
|
223
246
|
edges=edges,
|
|
224
|
-
r_min=
|
|
247
|
+
r_min=r_min_eff,
|
|
225
248
|
r_max=r_max,
|
|
226
|
-
half_fill=
|
|
249
|
+
half_fill=half_fill_eff,
|
|
227
250
|
max_neighbors=max_neighbors,
|
|
228
251
|
method=method,
|
|
252
|
+
r_min_floor=r_min_floor,
|
|
229
253
|
group_a_mask=group_a_mask,
|
|
230
254
|
group_b_mask=group_b_mask,
|
|
231
255
|
)
|
|
232
256
|
|
|
233
|
-
cross_mode = group_a_mask is not None and group_b_mask is not None and not torch.equal(
|
|
234
|
-
group_a_mask, group_b_mask
|
|
235
|
-
)
|
|
236
257
|
centers, g_r = _finalize_gr(
|
|
237
|
-
counts, edges, total_norm, half_fill=
|
|
258
|
+
counts, edges, total_norm, half_fill=half_fill_eff, cross_mode=cross_mode
|
|
238
259
|
)
|
|
239
260
|
return centers.cpu().numpy(), g_r.cpu().numpy()
|
|
240
261
|
|
|
@@ -245,6 +266,7 @@ def accumulate_rdf(
|
|
|
245
266
|
r_min: float,
|
|
246
267
|
r_max: float,
|
|
247
268
|
nbins: int,
|
|
269
|
+
r_min_floor: float,
|
|
248
270
|
device: str | torch.device,
|
|
249
271
|
torch_dtype: torch.dtype,
|
|
250
272
|
half_fill: bool,
|
|
@@ -264,6 +286,8 @@ def accumulate_rdf(
|
|
|
264
286
|
Maximum distance included in the histogram.
|
|
265
287
|
nbins
|
|
266
288
|
Number of histogram bins.
|
|
289
|
+
r_min_floor
|
|
290
|
+
Small lower bound to exclude pathological self/near-zero distances when ``r_min`` is zero.
|
|
267
291
|
device
|
|
268
292
|
Torch device string or object used for computation.
|
|
269
293
|
torch_dtype
|
|
@@ -281,7 +305,8 @@ def accumulate_rdf(
|
|
|
281
305
|
Bin centers and g(r) arrays on CPU.
|
|
282
306
|
"""
|
|
283
307
|
device = torch.device(device)
|
|
284
|
-
|
|
308
|
+
r_min_eff = max(r_min, r_min_floor)
|
|
309
|
+
edges = torch.linspace(r_min_eff, r_max, nbins + 1, device=device, dtype=torch_dtype)
|
|
285
310
|
counts = torch.zeros(nbins, device=device, dtype=torch.int64)
|
|
286
311
|
total_norm = 0.0
|
|
287
312
|
|
|
@@ -303,6 +328,9 @@ def accumulate_rdf(
|
|
|
303
328
|
group_a_mask, group_b_mask
|
|
304
329
|
):
|
|
305
330
|
cross_flag = True
|
|
331
|
+
half_fill_frame = False
|
|
332
|
+
else:
|
|
333
|
+
half_fill_frame = half_fill
|
|
306
334
|
|
|
307
335
|
norm = _update_counts(
|
|
308
336
|
counts,
|
|
@@ -310,21 +338,23 @@ def accumulate_rdf(
|
|
|
310
338
|
cell=cell_t,
|
|
311
339
|
pbc=pbc_t,
|
|
312
340
|
edges=edges,
|
|
313
|
-
r_min=
|
|
341
|
+
r_min=r_min_eff,
|
|
314
342
|
r_max=r_max,
|
|
315
|
-
half_fill=
|
|
343
|
+
half_fill=half_fill_frame,
|
|
316
344
|
max_neighbors=max_neighbors,
|
|
317
345
|
method=method,
|
|
346
|
+
r_min_floor=r_min_floor,
|
|
318
347
|
group_a_mask=group_a_mask,
|
|
319
348
|
group_b_mask=group_b_mask,
|
|
320
349
|
)
|
|
321
350
|
total_norm += norm
|
|
322
351
|
|
|
352
|
+
half_fill_eff = False if cross_flag else half_fill
|
|
323
353
|
centers, g_r = _finalize_gr(
|
|
324
354
|
counts,
|
|
325
355
|
edges,
|
|
326
356
|
total_norm,
|
|
327
|
-
half_fill=
|
|
357
|
+
half_fill=half_fill_eff,
|
|
328
358
|
cross_mode=cross_flag,
|
|
329
359
|
)
|
|
330
360
|
return centers.cpu().numpy(), g_r.cpu().numpy()
|
|
@@ -336,6 +366,7 @@ def rdf(
|
|
|
336
366
|
species_b: str | None = None,
|
|
337
367
|
index=None,
|
|
338
368
|
atom_types_map: dict | None = None,
|
|
369
|
+
pbc: tuple[bool, bool, bool] | None = None,
|
|
339
370
|
method: str = "cell_list",
|
|
340
371
|
outdir=None,
|
|
341
372
|
output: str | None = None,
|
|
@@ -356,6 +387,8 @@ def rdf(
|
|
|
356
387
|
Optional index/selector forwarded to source-specific readers.
|
|
357
388
|
atom_types_map
|
|
358
389
|
Optional mapping for numeric atom types to element names.
|
|
390
|
+
pbc
|
|
391
|
+
Optional periodic boundary flags; defaults to (True, True, True) when omitted.
|
|
359
392
|
method
|
|
360
393
|
Neighbor-list method name (e.g., ``"cell_list"`` or ``"naive"``).
|
|
361
394
|
outdir
|
|
@@ -380,6 +413,7 @@ def rdf(
|
|
|
380
413
|
species_b=species_b,
|
|
381
414
|
index=index,
|
|
382
415
|
atom_types_map=atom_types_map,
|
|
416
|
+
pbc=pbc if pbc is not None else (True, True, True),
|
|
383
417
|
method=method,
|
|
384
418
|
**kwargs,
|
|
385
419
|
)
|
|
@@ -392,6 +426,7 @@ def rdf(
|
|
|
392
426
|
species_b=species_b,
|
|
393
427
|
index=index,
|
|
394
428
|
atom_types_map=atom_types_map,
|
|
429
|
+
pbc=pbc if pbc is not None else (True, True, True),
|
|
395
430
|
method=method,
|
|
396
431
|
**kwargs,
|
|
397
432
|
)
|
|
@@ -405,6 +440,7 @@ def rdf(
|
|
|
405
440
|
species_b=species_b,
|
|
406
441
|
index=index,
|
|
407
442
|
atom_types_map=atom_types_map,
|
|
443
|
+
pbc=pbc if pbc is not None else (True, True, True),
|
|
408
444
|
method=method,
|
|
409
445
|
**kwargs,
|
|
410
446
|
)
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "cuRDF"
|
|
7
|
-
version = "0.5.
|
|
7
|
+
version = "0.5.2"
|
|
8
8
|
description = "GPU-accelerated radial distribution functions with NVIDIA ALCHEMI Toolkit-Ops neighbor lists and PyTorch."
|
|
9
9
|
authors = [{ name = "Joseph Hart" }]
|
|
10
10
|
readme = { file = "README.md", "content-type" = "text/markdown" }
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pytest
|
|
3
|
+
|
|
4
|
+
from curdf.rdf import rdf
|
|
5
|
+
from ase.io import read
|
|
6
|
+
import matplotlib.pyplot as plt
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
DATAPATH = Path("darren_data/rdf-mace-nvt")
|
|
10
|
+
RMIN = 0.0
|
|
11
|
+
RMAX = 6.0
|
|
12
|
+
N_BINS = 200
|
|
13
|
+
|
|
14
|
+
def test_compute_rdf_cuda_runs():
|
|
15
|
+
torch_mod = pytest.importorskip("torch")
|
|
16
|
+
if not torch_mod.cuda.is_available():
|
|
17
|
+
pytest.skip("CUDA not available")
|
|
18
|
+
|
|
19
|
+
water_traj_list = read(DATAPATH / f'nvt.xyz', index=':')
|
|
20
|
+
|
|
21
|
+
for species_a, species_b in [("O", "O"), ("H", "H"), ("O", "H")]:
|
|
22
|
+
data = np.loadtxt(DATAPATH / f"rdf_{species_a}{species_b}_mace.dat")
|
|
23
|
+
r_ref, gr_ref = data[:,0], data[:,1]
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
bins, gr = rdf(
|
|
27
|
+
water_traj_list,
|
|
28
|
+
species_a=species_a,
|
|
29
|
+
species_b=species_b,
|
|
30
|
+
r_min=RMIN,
|
|
31
|
+
r_max=RMAX,
|
|
32
|
+
nbins=N_BINS,
|
|
33
|
+
output=f"cuRDF_results/rdf_{species_a}{species_b}.csv"
|
|
34
|
+
)
|
|
35
|
+
plt.plot(bins, gr, label=f"cuRDF - {species_a}-{species_b}")
|
|
36
|
+
plt.plot(r_ref, gr_ref, label=f"Reference - {species_a}-{species_b}")
|
|
37
|
+
plt.xlabel("r (Å)")
|
|
38
|
+
plt.ylabel("g(r)")
|
|
39
|
+
plt.title(f"RDF {species_a}-{species_b}")
|
|
40
|
+
plt.ylim(0,)
|
|
41
|
+
plt.xlim(0, RMAX)
|
|
42
|
+
plt.legend()
|
|
43
|
+
plt.tight_layout()
|
|
44
|
+
plt.savefig(f"cuRDF_results/rdf_{species_a}{species_b}_comparison.png", dpi=300)
|
|
45
|
+
plt.savefig(f"cuRDF_results/rdf_{species_a}{species_b}_comparison.pdf")
|
|
46
|
+
plt.savefig(f"cuRDF_results/rdf_{species_a}{species_b}_comparison.svg")
|
|
47
|
+
plt.close()
|
|
48
|
+
# Numerical check: bins should align and g(r) should match reference within 1% relative.
|
|
49
|
+
assert bins.shape == r_ref.shape
|
|
50
|
+
assert gr.shape == gr_ref.shape
|
|
51
|
+
np.testing.assert_allclose(bins, r_ref, rtol=1e-6, atol=1e-6)
|
|
52
|
+
np.testing.assert_allclose(gr, gr_ref, rtol=1e-2, atol=1e-3)
|
|
53
|
+
except RuntimeError as err:
|
|
54
|
+
pytest.skip(f"CUDA neighbor list unavailable: {err}")
|
|
55
|
+
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|