torchnep 1.0.0__py3-none-any.whl
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.
- torchnep/__init__.py +22 -0
- torchnep/ase_calculator.py +196 -0
- torchnep/constants.py +218 -0
- torchnep/data.py +461 -0
- torchnep/model.py +661 -0
- torchnep/neighbor.py +203 -0
- torchnep/nep.py +734 -0
- torchnep/ops.py +1211 -0
- torchnep/predict.py +693 -0
- torchnep/train.py +1669 -0
- torchnep/train_sharded.py +1028 -0
- torchnep-1.0.0.dist-info/METADATA +398 -0
- torchnep-1.0.0.dist-info/RECORD +15 -0
- torchnep-1.0.0.dist-info/WHEEL +4 -0
- torchnep-1.0.0.dist-info/licenses/LICENSE +674 -0
torchnep/__init__.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Copyright 2025 Yongchao Wu and the GPUMD development team
|
|
2
|
+
# This file is part of GPUMD (Torchnep project).
|
|
3
|
+
# GPUMD is free software: you can redistribute it and/or modify
|
|
4
|
+
# it under the terms of the GNU General Public License as published by
|
|
5
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
6
|
+
# (at your option) any later version.
|
|
7
|
+
# GPUMD is distributed in the hope that it will be useful,
|
|
8
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
9
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
10
|
+
# GNU General Public License for more details.
|
|
11
|
+
# You should have received a copy of the GNU General Public License
|
|
12
|
+
# along with GPUMD. If not, see <http://www.gnu.org/licenses/>.
|
|
13
|
+
|
|
14
|
+
__version__ = "1.0.0"
|
|
15
|
+
|
|
16
|
+
from .predict import predict_dataset
|
|
17
|
+
from .train import train_nep
|
|
18
|
+
from .train_sharded import train_nep_sharded
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"predict_dataset", "train_nep", "train_nep_sharded",
|
|
22
|
+
]
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# Copyright 2025 Yongchao Wu and the GPUMD development team
|
|
2
|
+
# This file is part of GPUMD (Torchnep project).
|
|
3
|
+
# GPUMD is free software: you can redistribute it and/or modify
|
|
4
|
+
# it under the terms of the GNU General Public License as published by
|
|
5
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
6
|
+
# (at your option) any later version.
|
|
7
|
+
# GPUMD is distributed in the hope that it will be useful,
|
|
8
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
9
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
10
|
+
# GNU General Public License for more details.
|
|
11
|
+
# You should have received a copy of the GNU General Public License
|
|
12
|
+
# along with GPUMD. If not, see <http://www.gnu.org/licenses/>.
|
|
13
|
+
|
|
14
|
+
"""ASE interface for torchnep NEP4 models.
|
|
15
|
+
|
|
16
|
+
This module provides :class:`NEP`, a standard ``ase.calculators.calculator.
|
|
17
|
+
Calculator`` so that any ASE workflow (relaxation, MD, EOS, phonons, ...) can
|
|
18
|
+
drive a trained NEP4 potential::
|
|
19
|
+
|
|
20
|
+
from ase.io import read
|
|
21
|
+
from torchnep.ase_calculator import NEP
|
|
22
|
+
|
|
23
|
+
atoms = read("POSCAR")
|
|
24
|
+
atoms.calc = NEP("nep.txt")
|
|
25
|
+
print(atoms.get_potential_energy())
|
|
26
|
+
print(atoms.get_forces())
|
|
27
|
+
print(atoms.get_stress()) # only for periodic cells
|
|
28
|
+
|
|
29
|
+
``ase`` is an OPTIONAL dependency of torchnep — the core package (training and
|
|
30
|
+
prediction) never imports it. Importing *this* module requires ase to be
|
|
31
|
+
installed; if it is not, a clear ImportError is raised here rather than deep
|
|
32
|
+
inside ASE. This is also why ``NEP`` is intentionally not re-exported from
|
|
33
|
+
``torchnep/__init__.py``.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
import numpy as np
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
from ase.calculators.calculator import Calculator, all_changes
|
|
40
|
+
from ase.stress import full_3x3_to_voigt_6_stress
|
|
41
|
+
except ModuleNotFoundError as exc: # pragma: no cover - exercised only sans ase
|
|
42
|
+
raise ModuleNotFoundError(
|
|
43
|
+
"torchnep.ase_calculator requires the optional dependency 'ase'. "
|
|
44
|
+
"Install it with `pip install ase`."
|
|
45
|
+
) from exc
|
|
46
|
+
|
|
47
|
+
from .nep import NEPCalculator as _NEPCore
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _resolve_dtype(dtype):
|
|
51
|
+
import torch
|
|
52
|
+
if isinstance(dtype, torch.dtype):
|
|
53
|
+
return dtype
|
|
54
|
+
return {"float64": torch.float64, "float32": torch.float32,
|
|
55
|
+
"double": torch.float64, "single": torch.float32}[str(dtype)]
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class NEP(Calculator):
|
|
59
|
+
"""ASE calculator backed by a torchnep NEP4 model (``nep.txt``).
|
|
60
|
+
|
|
61
|
+
Parameters
|
|
62
|
+
----------
|
|
63
|
+
model_file : str
|
|
64
|
+
Path to a GPUMD-format ``nep.txt`` (NEP4).
|
|
65
|
+
dtype : str or torch.dtype
|
|
66
|
+
Compute precision; ``"float64"`` (default) reproduces GPUMD to
|
|
67
|
+
round-off, ``"float32"`` is faster.
|
|
68
|
+
device : str or torch.device
|
|
69
|
+
Torch device, e.g. ``"cpu"`` (default) or ``"cuda"``.
|
|
70
|
+
**kwargs
|
|
71
|
+
Forwarded to ``ase.calculators.calculator.Calculator`` (e.g. ``label``).
|
|
72
|
+
|
|
73
|
+
Notes
|
|
74
|
+
-----
|
|
75
|
+
``stress`` is only reported for fully periodic cells (a finite volume is
|
|
76
|
+
required); for molecules / clusters it is omitted. Non-periodic systems
|
|
77
|
+
are handled by embedding the atoms in a vacuum box large enough that no
|
|
78
|
+
atom sees a periodic image within the model cutoff.
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
implemented_properties = ["energy", "energies", "free_energy",
|
|
82
|
+
"forces", "stress"]
|
|
83
|
+
|
|
84
|
+
def __init__(self, model_file, dtype="float64", device="cpu",
|
|
85
|
+
tiled="auto", block_size="auto", compile=False, **kwargs):
|
|
86
|
+
super().__init__(**kwargs)
|
|
87
|
+
self.nep = _NEPCore(model_file, dtype=_resolve_dtype(dtype),
|
|
88
|
+
device=device)
|
|
89
|
+
# tiled: memory-bounded analytical inference for large cells.
|
|
90
|
+
# True -> always tile; False -> never (autograd path);
|
|
91
|
+
# "auto" -> tile once the system exceeds ``tiled_threshold`` atoms.
|
|
92
|
+
self.tiled = tiled
|
|
93
|
+
# block_size: "auto" sizes each tile from free memory; or an int override.
|
|
94
|
+
self.block_size = block_size
|
|
95
|
+
self.tiled_threshold = 50000
|
|
96
|
+
# compile: torch.compile the tiled kernels (CPU/CUDA only; one-time
|
|
97
|
+
# warm-up, then a dynamic graph that survives MD pair-count changes).
|
|
98
|
+
self.compile = bool(compile)
|
|
99
|
+
|
|
100
|
+
# -- helpers --------------------------------------------------------------
|
|
101
|
+
def _cell_for_neighbors(self, atoms):
|
|
102
|
+
"""Return (cell_3x3, periodic) for the neighbor-list builder.
|
|
103
|
+
|
|
104
|
+
Fully periodic cells are passed through. Otherwise we wrap the atoms in
|
|
105
|
+
an orthorhombic vacuum box padded by ``2*rc`` so the builder's PBC
|
|
106
|
+
replicas never fall within the cutoff (emulating an isolated system).
|
|
107
|
+
"""
|
|
108
|
+
if atoms.cell.rank == 3 and atoms.pbc.all():
|
|
109
|
+
return np.asarray(atoms.get_cell()[:], dtype=float), True
|
|
110
|
+
pos = atoms.get_positions()
|
|
111
|
+
rc = max(self.nep.rc_radial, self.nep.rc_angular)
|
|
112
|
+
span = pos.max(axis=0) - pos.min(axis=0) if len(pos) else np.zeros(3)
|
|
113
|
+
box = np.diag(span + 4.0 * rc + 1.0)
|
|
114
|
+
return box, False
|
|
115
|
+
|
|
116
|
+
# -- ASE entry point ------------------------------------------------------
|
|
117
|
+
def calculate(self, atoms=None, properties=("energy",),
|
|
118
|
+
system_changes=all_changes):
|
|
119
|
+
super().calculate(atoms, properties, system_changes)
|
|
120
|
+
atoms = self.atoms # set by super().calculate
|
|
121
|
+
|
|
122
|
+
cell, periodic = self._cell_for_neighbors(atoms)
|
|
123
|
+
species = atoms.get_chemical_symbols()
|
|
124
|
+
use_tiled = (self.tiled is True or
|
|
125
|
+
(self.tiled == "auto" and len(species) >= self.tiled_threshold))
|
|
126
|
+
if use_tiled:
|
|
127
|
+
res = self.nep.compute_tiled(
|
|
128
|
+
species=species, positions=atoms.get_positions(),
|
|
129
|
+
cell=cell, block_size=self.block_size, compile=self.compile)
|
|
130
|
+
else:
|
|
131
|
+
res = self.nep.compute(
|
|
132
|
+
species=species, positions=atoms.get_positions(), cell=cell)
|
|
133
|
+
energies = res["energy"].detach().cpu().numpy()
|
|
134
|
+
forces = res["forces"].detach().cpu().numpy()
|
|
135
|
+
|
|
136
|
+
self.results["energies"] = energies
|
|
137
|
+
self.results["energy"] = float(energies.sum())
|
|
138
|
+
self.results["free_energy"] = self.results["energy"]
|
|
139
|
+
self.results["forces"] = forces
|
|
140
|
+
|
|
141
|
+
if periodic:
|
|
142
|
+
self.results["stress"] = self._stress_from_virial(
|
|
143
|
+
res["virial"], atoms.get_volume())
|
|
144
|
+
|
|
145
|
+
@staticmethod
|
|
146
|
+
def _stress_from_virial(virial, volume):
|
|
147
|
+
# Per-atom virial is (N, 9) row-major; W_ab = Σ rij_a * F_b.
|
|
148
|
+
# ASE stress σ = -W / V (Voigt order xx, yy, zz, yz, xz, xy).
|
|
149
|
+
w = virial.detach().cpu().numpy().sum(0).reshape(3, 3)
|
|
150
|
+
return full_3x3_to_voigt_6_stress(-w / volume)
|
|
151
|
+
|
|
152
|
+
def get_energy_components(self, atoms=None):
|
|
153
|
+
"""Return the NEP / ZBL / total potential-energy split (eV).
|
|
154
|
+
|
|
155
|
+
Useful to inspect how much of the energy comes from the ZBL repulsive
|
|
156
|
+
baseline versus the neural-network NEP part::
|
|
157
|
+
|
|
158
|
+
{'nep': ..., 'zbl': ..., 'total': ...}
|
|
159
|
+
|
|
160
|
+
For a model trained without ZBL, ``'zbl'`` is 0 and ``'nep'`` equals
|
|
161
|
+
``'total'``. See :meth:`get_components` for forces and stress too.
|
|
162
|
+
"""
|
|
163
|
+
return {k: v["energy"] for k, v in self.get_components(atoms).items()}
|
|
164
|
+
|
|
165
|
+
def get_components(self, atoms=None):
|
|
166
|
+
"""Full NEP / ZBL / total breakdown of energy, forces, and stress.
|
|
167
|
+
|
|
168
|
+
Returns ``{'nep': {...}, 'zbl': {...}, 'total': {...}}`` where each
|
|
169
|
+
inner dict has ``'energy'`` (float, eV), ``'forces'`` ((N, 3) eV/Å) and,
|
|
170
|
+
for periodic cells, ``'stress'`` (6-vector Voigt, eV/ų). The ``total``
|
|
171
|
+
block equals what ``get_potential_energy`` / ``get_forces`` /
|
|
172
|
+
``get_stress`` return.
|
|
173
|
+
"""
|
|
174
|
+
if atoms is None:
|
|
175
|
+
atoms = self.atoms
|
|
176
|
+
if atoms is None:
|
|
177
|
+
raise ValueError("No atoms supplied and none attached to the calculator.")
|
|
178
|
+
|
|
179
|
+
cell, periodic = self._cell_for_neighbors(atoms)
|
|
180
|
+
res = self.nep.compute(
|
|
181
|
+
species=atoms.get_chemical_symbols(),
|
|
182
|
+
positions=atoms.get_positions(),
|
|
183
|
+
cell=cell,
|
|
184
|
+
return_components=True,
|
|
185
|
+
)
|
|
186
|
+
vol = atoms.get_volume() if periodic else None
|
|
187
|
+
out = {}
|
|
188
|
+
for name, esuffix in (("nep", "_nep"), ("zbl", "_zbl"), ("total", "")):
|
|
189
|
+
e = res["energy" + esuffix].detach().cpu().numpy()
|
|
190
|
+
block = {"energy": float(e.sum()),
|
|
191
|
+
"forces": res["forces" + esuffix].detach().cpu().numpy()}
|
|
192
|
+
if periodic:
|
|
193
|
+
block["stress"] = self._stress_from_virial(
|
|
194
|
+
res["virial" + esuffix], vol)
|
|
195
|
+
out[name] = block
|
|
196
|
+
return out
|
torchnep/constants.py
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# Copyright 2025 Yongchao Wu and the GPUMD development team
|
|
2
|
+
# This file is part of GPUMD (Torchnep project).
|
|
3
|
+
# GPUMD is free software: you can redistribute it and/or modify
|
|
4
|
+
# it under the terms of the GNU General Public License as published by
|
|
5
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
6
|
+
# (at your option) any later version.
|
|
7
|
+
# GPUMD is distributed in the hope that it will be useful,
|
|
8
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
9
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
10
|
+
# GNU General Public License for more details.
|
|
11
|
+
# You should have received a copy of the GNU General Public License
|
|
12
|
+
# along with GPUMD. If not, see <http://www.gnu.org/licenses/>.
|
|
13
|
+
|
|
14
|
+
"""Shared constants for NEP implementation."""
|
|
15
|
+
|
|
16
|
+
PI = 3.141592653589793
|
|
17
|
+
|
|
18
|
+
ELEMENTS = [
|
|
19
|
+
"H", "He", "Li", "Be", "B", "C", "N", "O", "F", "Ne", "Na", "Mg",
|
|
20
|
+
"Al", "Si", "P", "S", "Cl", "Ar", "K", "Ca", "Sc", "Ti", "V", "Cr",
|
|
21
|
+
"Mn", "Fe", "Co", "Ni", "Cu", "Zn", "Ga", "Ge", "As", "Se", "Br", "Kr",
|
|
22
|
+
"Rb", "Sr", "Y", "Zr", "Nb", "Mo", "Tc", "Ru", "Rh", "Pd", "Ag", "Cd",
|
|
23
|
+
"In", "Sn", "Sb", "Te", "I", "Xe", "Cs", "Ba", "La", "Ce", "Pr", "Nd",
|
|
24
|
+
"Pm", "Sm", "Eu", "Gd", "Tb", "Dy", "Ho", "Er", "Tm", "Yb", "Lu", "Hf",
|
|
25
|
+
"Ta", "W", "Re", "Os", "Ir", "Pt", "Au", "Hg", "Tl", "Pb", "Bi", "Po",
|
|
26
|
+
"At", "Rn", "Fr", "Ra", "Ac", "Th", "Pa", "U", "Np", "Pu",
|
|
27
|
+
# Transuranics + transactinides (Z = 95–118).
|
|
28
|
+
"Am", "Cm", "Bk", "Cf", "Es", "Fm", "Md", "No", "Lr",
|
|
29
|
+
"Rf", "Db", "Sg", "Bh", "Hs", "Mt", "Ds", "Rg", "Cn",
|
|
30
|
+
"Nh", "Fl", "Mc", "Lv", "Ts", "Og",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
# Maximum 3-body angular order supported by the implementation.
|
|
34
|
+
MAX_L3B = 8
|
|
35
|
+
|
|
36
|
+
# C3B coefficients for 3-body angular descriptors (from GPUMD).
|
|
37
|
+
# Indexed [L*L - 1 + k] for L in 1..8, k in 0..2L; total = 3+5+7+9+11+13+15+17 = 80.
|
|
38
|
+
C3B = [
|
|
39
|
+
0.238732414637843, 0.119366207318922, 0.119366207318922, 0.099471839432435,
|
|
40
|
+
0.596831036594608, 0.596831036594608, 0.149207759148652, 0.149207759148652,
|
|
41
|
+
0.139260575205408, 0.104445431404056, 0.104445431404056, 1.044454314040563,
|
|
42
|
+
1.044454314040563, 0.174075719006761, 0.174075719006761, 0.011190581936149,
|
|
43
|
+
0.223811638722978, 0.223811638722978, 0.111905819361489, 0.111905819361489,
|
|
44
|
+
1.566681471060845, 1.566681471060845, 0.195835183882606, 0.195835183882606,
|
|
45
|
+
0.013677377921960, 0.102580334414698, 0.102580334414698, 2.872249363611549,
|
|
46
|
+
2.872249363611549, 0.119677056817148, 0.119677056817148, 2.154187022708661,
|
|
47
|
+
2.154187022708661, 0.215418702270866, 0.215418702270866, 0.004041043476943,
|
|
48
|
+
0.169723826031592, 0.169723826031592, 0.106077391269745, 0.106077391269745,
|
|
49
|
+
0.424309565078979, 0.424309565078979, 0.127292869523694, 0.127292869523694,
|
|
50
|
+
2.800443129521260, 2.800443129521260, 0.233370260793438, 0.233370260793438,
|
|
51
|
+
0.004662742473395, 0.004079899664221, 0.004079899664221, 0.024479397985326,
|
|
52
|
+
0.024479397985326, 0.012239698992663, 0.012239698992663, 0.538546755677165,
|
|
53
|
+
0.538546755677165, 0.134636688919291, 0.134636688919291, 3.500553911901575,
|
|
54
|
+
3.500553911901575, 0.250039565135827, 0.250039565135827, 0.000082569397966,
|
|
55
|
+
0.005944996653579, 0.005944996653579, 0.104037441437634, 0.104037441437634,
|
|
56
|
+
0.762941237209318, 0.762941237209318, 0.114441185581398, 0.114441185581398,
|
|
57
|
+
5.950941650232678, 5.950941650232678, 0.141689086910302, 0.141689086910302,
|
|
58
|
+
4.250672607309055, 4.250672607309055, 0.265667037956816, 0.265667037956816,
|
|
59
|
+
]
|
|
60
|
+
assert len(C3B) == sum(2 * L + 1 for L in range(1, MAX_L3B + 1)) # 80
|
|
61
|
+
|
|
62
|
+
# Z_COEFFICIENT[L][n1][n2]: polynomial coefficients such that the (L, n1)
|
|
63
|
+
# angular basis element equals z_factor(z) * (x+iy)^n1, where
|
|
64
|
+
# z_factor(z) = sum_{n2} Z_COEFFICIENT[L][n1][n2] * z^n2
|
|
65
|
+
# (only n2 with (L+n1) parity match the sum; others are stored as 0).
|
|
66
|
+
# Matches NEP_CPU's Z_COEFFICIENT_1..8 tables; rows/cols padded to (L+1) * (L+1).
|
|
67
|
+
Z_COEFFICIENT = [
|
|
68
|
+
None, # L=0 unused
|
|
69
|
+
[[0.0, 1.0], [1.0, 0.0]],
|
|
70
|
+
[[-1.0, 0.0, 3.0], [0.0, 1.0, 0.0], [1.0, 0.0, 0.0]],
|
|
71
|
+
[[0.0, -3.0, 0.0, 5.0], [-1.0, 0.0, 5.0, 0.0],
|
|
72
|
+
[0.0, 1.0, 0.0, 0.0], [1.0, 0.0, 0.0, 0.0]],
|
|
73
|
+
[[3.0, 0.0, -30.0, 0.0, 35.0], [0.0, -3.0, 0.0, 7.0, 0.0],
|
|
74
|
+
[-1.0, 0.0, 7.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0, 0.0],
|
|
75
|
+
[1.0, 0.0, 0.0, 0.0, 0.0]],
|
|
76
|
+
[[0.0, 15.0, 0.0, -70.0, 0.0, 63.0], [1.0, 0.0, -14.0, 0.0, 21.0, 0.0],
|
|
77
|
+
[0.0, -1.0, 0.0, 3.0, 0.0, 0.0], [-1.0, 0.0, 9.0, 0.0, 0.0, 0.0],
|
|
78
|
+
[0.0, 1.0, 0.0, 0.0, 0.0, 0.0], [1.0, 0.0, 0.0, 0.0, 0.0, 0.0]],
|
|
79
|
+
[[-5.0, 0.0, 105.0, 0.0, -315.0, 0.0, 231.0],
|
|
80
|
+
[0.0, 5.0, 0.0, -30.0, 0.0, 33.0, 0.0],
|
|
81
|
+
[1.0, 0.0, -18.0, 0.0, 33.0, 0.0, 0.0],
|
|
82
|
+
[0.0, -3.0, 0.0, 11.0, 0.0, 0.0, 0.0],
|
|
83
|
+
[-1.0, 0.0, 11.0, 0.0, 0.0, 0.0, 0.0],
|
|
84
|
+
[0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0],
|
|
85
|
+
[1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]],
|
|
86
|
+
[[0.0, -35.0, 0.0, 315.0, 0.0, -693.0, 0.0, 429.0],
|
|
87
|
+
[-5.0, 0.0, 135.0, 0.0, -495.0, 0.0, 429.0, 0.0],
|
|
88
|
+
[0.0, 15.0, 0.0, -110.0, 0.0, 143.0, 0.0, 0.0],
|
|
89
|
+
[3.0, 0.0, -66.0, 0.0, 143.0, 0.0, 0.0, 0.0],
|
|
90
|
+
[0.0, -3.0, 0.0, 13.0, 0.0, 0.0, 0.0, 0.0],
|
|
91
|
+
[-1.0, 0.0, 13.0, 0.0, 0.0, 0.0, 0.0, 0.0],
|
|
92
|
+
[0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
|
|
93
|
+
[1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]],
|
|
94
|
+
[[35.0, 0.0, -1260.0, 0.0, 6930.0, 0.0, -12012.0, 0.0, 6435.0],
|
|
95
|
+
[0.0, -35.0, 0.0, 385.0, 0.0, -1001.0, 0.0, 715.0, 0.0],
|
|
96
|
+
[-1.0, 0.0, 33.0, 0.0, -143.0, 0.0, 143.0, 0.0, 0.0],
|
|
97
|
+
[0.0, 3.0, 0.0, -26.0, 0.0, 39.0, 0.0, 0.0, 0.0],
|
|
98
|
+
[1.0, 0.0, -26.0, 0.0, 65.0, 0.0, 0.0, 0.0, 0.0],
|
|
99
|
+
[0.0, -1.0, 0.0, 5.0, 0.0, 0.0, 0.0, 0.0, 0.0],
|
|
100
|
+
[-1.0, 0.0, 15.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
|
|
101
|
+
[0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
|
|
102
|
+
[1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]],
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
C4B = [-0.007499480826664, -0.134990654879954, 0.067495327439977,
|
|
106
|
+
0.404971964639861, -0.809943929279723]
|
|
107
|
+
|
|
108
|
+
C5B = [0.026596810706114, 0.053193621412227, 0.026596810706114]
|
|
109
|
+
|
|
110
|
+
# Mixed-L invariant (GPUMD NEP feature `has_q_112`).
|
|
111
|
+
C4B2 = [0.027493550848847,
|
|
112
|
+
0.164961305093080,
|
|
113
|
+
-0.013746775424423,
|
|
114
|
+
0.041240326273270,
|
|
115
|
+
0.082480652546540]
|
|
116
|
+
|
|
117
|
+
# Higher-L 4-body bispectrum invariants (GPUMD: has_q_123 / has_q_233 / has_q_134).
|
|
118
|
+
C4B_123 = [-0.008418146349617, -0.016836292699234, -0.033672585398469,
|
|
119
|
+
-0.042090731748086, -0.067345170796937, -0.084181463496172,
|
|
120
|
+
-0.168362926992344]
|
|
121
|
+
|
|
122
|
+
C4B_233 = [0.008572620635186, 0.009644198214584, 0.019288396429168,
|
|
123
|
+
0.025717861905558, 0.026789439484956, 0.032147327381947,
|
|
124
|
+
0.038576792858337, 0.128589309527790, 0.192883964291685,
|
|
125
|
+
0.321473273819474]
|
|
126
|
+
|
|
127
|
+
# q_134 = B(1,3,4): couples L=1, L=3, L=4 moments (GPUMD: has_q_134).
|
|
128
|
+
# Uses L=4 s-moments (lm indices 15..23), so it requires l_max_3b >= 4.
|
|
129
|
+
C4B_134 = [0.003645164295772, 0.004860219061029, 0.006075273826286,
|
|
130
|
+
0.018225821478859, 0.024301095305146, 0.036451642957719,
|
|
131
|
+
0.042526916784005, 0.072903285915437, 0.085053833568010,
|
|
132
|
+
0.255161500704030]
|
|
133
|
+
|
|
134
|
+
# Each group is (C4B_*[k] index, [(sign, (lm_idx, lm_idx, lm_idx)), ...]),
|
|
135
|
+
# mirroring GPUMD's find_q line by line for easy auditing. Expanded below to
|
|
136
|
+
# the flat (coeff, idx) form consumed by ops._eval_extra / _extra_grad.
|
|
137
|
+
_Q123_GROUPS = [
|
|
138
|
+
(6, [(+1, (12, 2, 4)), (-1, (11, 2, 5)), (+1, (1, 11, 4)), (+1, (1, 12, 5))]),
|
|
139
|
+
(5, [(+1, (0, 11, 6)), (+1, (0, 12, 7))]),
|
|
140
|
+
(3, [(+1, (14, 2, 6)), (-1, (13, 2, 7)), (+1, (1, 13, 6)), (+1, (1, 14, 7))]),
|
|
141
|
+
(4, [(+1, (10, 0, 5)), (+1, (0, 4, 9))]),
|
|
142
|
+
(1, [(+1, (10, 2, 3)), (+1, (0, 3, 8)), (+1, (1, 3, 9))]),
|
|
143
|
+
(0, [(+1, (10, 2, 6)), (-1, (10, 1, 7)), (-1, (2, 7, 9)), (-1, (1, 6, 9))]),
|
|
144
|
+
(2, [(-1, (2, 5, 8)), (-1, (1, 4, 8))]),
|
|
145
|
+
]
|
|
146
|
+
_Q233_GROUPS = [
|
|
147
|
+
(0, [(+1, (3, 8, 8))]),
|
|
148
|
+
(1, [(+1, (10, 10, 3)), (+1, (3, 9, 9))]),
|
|
149
|
+
(2, [(-1, (10, 10, 6)), (+1, (6, 9, 9))]),
|
|
150
|
+
(3, [(+1, (4, 8, 9)), (+1, (10, 5, 8))]),
|
|
151
|
+
(4, [(-1, (13, 13, 3)), (-1, (14, 14, 3))]),
|
|
152
|
+
(5, [(-1, (14, 7, 9)), (-1, (13, 6, 9)), (-1, (10, 14, 6)), (+1, (10, 13, 7))]),
|
|
153
|
+
(6, [(+1, (10, 7, 9))]),
|
|
154
|
+
(7, [(-1, (11, 6, 8)), (-1, (12, 7, 8))]),
|
|
155
|
+
(8, [(+1, (11, 4, 9)), (+1, (12, 5, 9)), (+1, (10, 12, 4)), (-1, (10, 11, 5))]),
|
|
156
|
+
(9, [(+1, (12, 14, 4)), (+1, (11, 14, 5)), (+1, (13, 11, 4)), (-1, (13, 12, 5))]),
|
|
157
|
+
]
|
|
158
|
+
_Q134_GROUPS = [
|
|
159
|
+
(0, [(-1, (10, 15, 2)), (-1, (1, 15, 9))]),
|
|
160
|
+
(1, [(+1, (0, 15, 8))]),
|
|
161
|
+
(2, [(-1, (1, 13, 18)), (-1, (1, 14, 19)), (-1, (2, 14, 18)), (+1, (2, 13, 19))]),
|
|
162
|
+
(3, [(-1, (10, 18, 2)), (+1, (1, 10, 19)), (+1, (1, 18, 9)), (+1, (2, 19, 9))]),
|
|
163
|
+
(4, [(+1, (1, 16, 8)), (+1, (2, 17, 8))]),
|
|
164
|
+
(5, [(+1, (0, 10, 17)), (+1, (0, 16, 9)), (-1, (1, 11, 16)), (-1, (1, 12, 17)),
|
|
165
|
+
(-1, (2, 12, 16)), (+1, (2, 11, 17))]),
|
|
166
|
+
(6, [(+1, (1, 13, 22)), (+1, (1, 14, 23)), (-1, (2, 14, 22)), (+1, (2, 13, 23))]),
|
|
167
|
+
(7, [(+1, (0, 11, 18)), (+1, (0, 12, 19))]),
|
|
168
|
+
(8, [(+1, (0, 13, 20)), (+1, (0, 14, 21))]),
|
|
169
|
+
(9, [(+1, (1, 11, 20)), (+1, (1, 12, 21)), (-1, (2, 12, 20)), (+1, (2, 11, 21))]),
|
|
170
|
+
]
|
|
171
|
+
Q123_TERMS = [(sign * C4B_123[k], idx)
|
|
172
|
+
for k, grp in _Q123_GROUPS for sign, idx in grp]
|
|
173
|
+
Q233_TERMS = [(sign * C4B_233[k], idx)
|
|
174
|
+
for k, grp in _Q233_GROUPS for sign, idx in grp]
|
|
175
|
+
Q134_TERMS = [(sign * C4B_134[k], idx)
|
|
176
|
+
for k, grp in _Q134_GROUPS for sign, idx in grp]
|
|
177
|
+
|
|
178
|
+
K_C_SP = 14.399645 # Coulomb constant (eV*Angstrom)
|
|
179
|
+
|
|
180
|
+
# Unit conversion: 1 eV/A**3 = 160.21766208 GPa (CODATA 2018 e = 1.602176634e-19 C)
|
|
181
|
+
EV_PER_A3_TO_GPa = 160.21766208
|
|
182
|
+
|
|
183
|
+
ZBL_PARA = [0.18175, 3.1998, 0.50986, 0.94229, 0.28022, 0.4029, 0.02817, 0.20162]
|
|
184
|
+
|
|
185
|
+
# Covalent radii in Angstrom, indexed by Z-1.
|
|
186
|
+
#
|
|
187
|
+
# Z = 1–94: from GPUMD (src/utilities/nep_utilities.cuh::COVALENT_RADIUS),
|
|
188
|
+
# which stores Grimme's DFT-D3 covalent radii * 4/3 (k2 scaling).
|
|
189
|
+
# Reference: Grimme, Antony, Ehrlich, Krieg, J. Chem. Phys. 132,
|
|
190
|
+
# 154104 (2010), Table I (itself derived from Pyykkö & Atsumi
|
|
191
|
+
# 2009 with Li/Be/B/etc. empirical adjustments). GPUMD hard-codes
|
|
192
|
+
# these * 4/3 for ZBL typewise cutoffs; we reproduce them exactly
|
|
193
|
+
# for bit-compatibility.
|
|
194
|
+
# Z = 95–118: Pyykkö, "Additive Covalent Radii for Single-, Double-, and
|
|
195
|
+
# Triple-Bonded Molecules and Tetrahedrally Bonded Crystals: A
|
|
196
|
+
# Summary", J. Phys. Chem. A 119, 2326 (2015), single-bond
|
|
197
|
+
# radii. Grimme D3 only tabulates up to Z=94 (Pu), so this block
|
|
198
|
+
# fills the gap from a different reference — there is a minor
|
|
199
|
+
# scale discontinuity at the Z=94 -> 95 boundary.
|
|
200
|
+
COVALENT_RADIUS = [
|
|
201
|
+
0.426667, 0.613333, 1.6, 1.25333, 1.02667, 1.0, 0.946667, 0.84,
|
|
202
|
+
0.853333, 0.893333, 1.86667, 1.66667, 1.50667, 1.38667, 1.46667, 1.36,
|
|
203
|
+
1.32, 1.28, 2.34667, 2.05333, 1.77333, 1.62667, 1.61333, 1.46667,
|
|
204
|
+
1.42667, 1.38667, 1.33333, 1.32, 1.34667, 1.45333, 1.49333, 1.45333,
|
|
205
|
+
1.53333, 1.46667, 1.52, 1.56, 2.52, 2.22667, 1.96, 1.85333,
|
|
206
|
+
1.76, 1.65333, 1.53333, 1.50667, 1.50667, 1.44, 1.53333, 1.64,
|
|
207
|
+
1.70667, 1.68, 1.68, 1.64, 1.76, 1.74667, 2.78667, 2.34667,
|
|
208
|
+
2.16, 1.96, 2.10667, 2.09333, 2.08, 2.06667, 2.01333, 2.02667,
|
|
209
|
+
2.01333, 2.0, 1.98667, 1.98667, 1.97333, 2.04, 1.94667, 1.82667,
|
|
210
|
+
1.74667, 1.64, 1.57333, 1.54667, 1.48, 1.49333, 1.50667, 1.76,
|
|
211
|
+
1.73333, 1.73333, 1.81333, 1.74667, 1.84, 1.89333, 2.68, 2.41333,
|
|
212
|
+
2.22667, 2.10667, 2.02667, 2.04, 2.05333, 2.06667,
|
|
213
|
+
# Pyykkö 2015 single-bond values (see note above the table).
|
|
214
|
+
1.80, 1.69, 1.68, 1.68, 1.65, 1.67, 1.73, 1.76, 1.61, # Am-Lr
|
|
215
|
+
1.57, 1.49, 1.43, 1.41, 1.34, 1.29, 1.28, 1.21, 1.22, # Rf-Cn
|
|
216
|
+
1.36, 1.43, 1.62, 1.75, 1.65, 1.57, # Nh-Og
|
|
217
|
+
]
|
|
218
|
+
assert len(COVALENT_RADIUS) == 118
|