Skip to content

Commit 4ae2726

Browse files
author
Han Wang
committed
feat: full energy model (but not exportable)
1 parent e76b702 commit 4ae2726

11 files changed

Lines changed: 862 additions & 40 deletions

File tree

deepmd/dpmodel/model/make_model.py

Lines changed: 42 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
PRECISION_DICT,
2222
RESERVED_PRECISION_DICT,
2323
NativeOP,
24+
get_xp_precision,
2425
)
2526
from deepmd.dpmodel.model.base_model import (
2627
BaseModel,
@@ -103,7 +104,8 @@ def model_call_from_call_lower(
103104
bb.reshape(nframes, 3, 3),
104105
)
105106
else:
106-
coord_normalized = cc.copy()
107+
xp = array_api_compat.array_namespace(cc)
108+
coord_normalized = xp.reshape(cc, (nframes, nloc, 3))
107109
extended_coord, extended_atype, mapping = extend_coord_with_ghosts(
108110
coord_normalized, atype, bb, rcut
109111
)
@@ -371,53 +373,73 @@ def input_type_cast(
371373
box: Array | None = None,
372374
fparam: Array | None = None,
373375
aparam: Array | None = None,
374-
) -> tuple[Array, Array, np.ndarray | None, np.ndarray | None, str]:
376+
) -> tuple[Array, Array | None, Array | None, Array | None, Any]:
375377
"""Cast the input data to global float type."""
376-
input_prec = RESERVED_PRECISION_DICT[self.precision_dict[coord.dtype.name]]
378+
xp = array_api_compat.array_namespace(coord)
379+
input_dtype = coord.dtype
380+
global_dtype = get_xp_precision(
381+
xp, RESERVED_PRECISION_DICT[self.global_np_float_precision]
382+
)
377383
###
378384
### type checking would not pass jit, convert to coord prec anyway
379385
###
380-
_lst: list[np.ndarray | None] = [
381-
vv.astype(coord.dtype) if vv is not None else None
386+
_lst: list[Array | None] = [
387+
xp.astype(vv, input_dtype) if vv is not None else None
382388
for vv in [box, fparam, aparam]
383389
]
384390
box, fparam, aparam = _lst
385-
if input_prec == RESERVED_PRECISION_DICT[self.global_np_float_precision]:
386-
return coord, box, fparam, aparam, input_prec
391+
if input_dtype == global_dtype:
392+
return coord, box, fparam, aparam, input_dtype
387393
else:
388-
pp = self.global_np_float_precision
389394
return (
390-
coord.astype(pp),
391-
box.astype(pp) if box is not None else None,
392-
fparam.astype(pp) if fparam is not None else None,
393-
aparam.astype(pp) if aparam is not None else None,
394-
input_prec,
395+
xp.astype(coord, global_dtype),
396+
xp.astype(box, global_dtype) if box is not None else None,
397+
xp.astype(fparam, global_dtype) if fparam is not None else None,
398+
xp.astype(aparam, global_dtype) if aparam is not None else None,
399+
input_dtype,
395400
)
396401

397402
def output_type_cast(
398403
self,
399404
model_ret: dict[str, Array],
400-
input_prec: str,
405+
input_prec: Any,
401406
) -> dict[str, Array]:
402-
"""Convert the model output to the input prec."""
403-
do_cast = (
404-
input_prec != RESERVED_PRECISION_DICT[self.global_np_float_precision]
407+
"""Convert the model output to the input prec.
408+
409+
Parameters
410+
----------
411+
model_ret
412+
The model output.
413+
input_prec
414+
The input dtype returned by ``input_type_cast``.
415+
"""
416+
model_ret_not_none = [vv for vv in model_ret.values() if vv is not None]
417+
if not model_ret_not_none:
418+
return model_ret
419+
xp = array_api_compat.array_namespace(model_ret_not_none[0])
420+
global_dtype = get_xp_precision(
421+
xp, RESERVED_PRECISION_DICT[self.global_np_float_precision]
422+
)
423+
ener_dtype = get_xp_precision(
424+
xp, RESERVED_PRECISION_DICT[self.global_ener_float_precision]
405425
)
406-
pp = self.precision_dict[input_prec]
426+
do_cast = input_prec != global_dtype
407427
odef = self.model_output_def()
408428
for kk in odef.keys():
409429
if kk not in model_ret.keys():
410430
# do not return energy_derv_c if not do_atomic_virial
411431
continue
412432
if check_operation_applied(odef[kk], OutputVariableOperation.REDU):
413433
model_ret[kk] = (
414-
model_ret[kk].astype(self.global_ener_float_precision)
434+
xp.astype(model_ret[kk], ener_dtype)
415435
if model_ret[kk] is not None
416436
else None
417437
)
418438
elif do_cast:
419439
model_ret[kk] = (
420-
model_ret[kk].astype(pp) if model_ret[kk] is not None else None
440+
xp.astype(model_ret[kk], input_prec)
441+
if model_ret[kk] is not None
442+
else None
421443
)
422444
return model_ret
423445

deepmd/dpmodel/model/transform_output.py

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ def communicate_extended_output(
9898
9999
"""
100100
xp = array_api_compat.get_namespace(mapping)
101+
device = array_api_compat.device(mapping)
101102
mapping_ = mapping
102103
new_ret = {}
103104
for kk in model_output_def.keys_outp():
@@ -117,7 +118,9 @@ def communicate_extended_output(
117118
mapping, tuple(mldims + [1] * len(derv_r_ext_dims))
118119
)
119120
mapping = xp.tile(mapping, [1] * len(mldims) + derv_r_ext_dims)
120-
force = xp.zeros(vldims + derv_r_ext_dims, dtype=vv.dtype)
121+
force = xp.zeros(
122+
vldims + derv_r_ext_dims, dtype=vv.dtype, device=device
123+
)
121124
force = xp_scatter_sum(
122125
force,
123126
1,
@@ -149,7 +152,9 @@ def communicate_extended_output(
149152
nall = hess_1.shape[1]
150153
# (1) -> [nf, nloc1, nall2, *def, 3(1), 3(2)]
151154
hessian1 = xp.zeros(
152-
[*vldims, nall, *vdef.shape, 3, 3], dtype=vv.dtype
155+
[*vldims, nall, *vdef.shape, 3, 3],
156+
dtype=vv.dtype,
157+
device=device,
153158
)
154159
mapping_hess = xp.reshape(
155160
mapping_, (mldims + [1] * (len(vdef.shape) + 3))
@@ -172,7 +177,9 @@ def communicate_extended_output(
172177
nloc = hessian1.shape[2]
173178
# (2) -> [nf, nloc2, nloc1, *def, 3(1), 3(2)]
174179
hessian = xp.zeros(
175-
[*vldims, nloc, *vdef.shape, 3, 3], dtype=vv.dtype
180+
[*vldims, nloc, *vdef.shape, 3, 3],
181+
dtype=vv.dtype,
182+
device=device,
176183
)
177184
mapping_hess = xp.reshape(
178185
mapping_, (mldims + [1] * (len(vdef.shape) + 3))
@@ -218,21 +225,14 @@ def communicate_extended_output(
218225
virial = xp.zeros(
219226
vldims + derv_c_ext_dims,
220227
dtype=vv.dtype,
228+
device=device,
229+
)
230+
virial = xp_scatter_sum(
231+
virial,
232+
1,
233+
mapping,
234+
model_ret[kk_derv_c],
221235
)
222-
# jax only
223-
if array_api_compat.is_jax_array(virial):
224-
from deepmd.jax.common import (
225-
scatter_sum,
226-
)
227-
228-
virial = scatter_sum(
229-
virial,
230-
1,
231-
mapping,
232-
model_ret[kk_derv_c],
233-
)
234-
else:
235-
raise NotImplementedError("Only JAX arrays are supported.")
236236
new_ret[kk_derv_c] = virial
237237
new_ret[kk_derv_c + "_redu"] = xp.sum(new_ret[kk_derv_c], axis=1)
238238
else:

deepmd/dpmodel/utils/network.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -286,11 +286,11 @@ def call(self, x): # noqa: ANN001, ANN201
286286
y = xp.astype(y, x.dtype)
287287
y = fn(y)
288288
if self.idt is not None:
289-
y *= self.idt
289+
y = y * self.idt
290290
if self.resnet and self.w.shape[1] == self.w.shape[0]:
291-
y += x
291+
y = y + x
292292
elif self.resnet and self.w.shape[1] == 2 * self.w.shape[0]:
293-
y += xp.concat([x, x], axis=-1)
293+
y = y + xp.concat([x, x], axis=-1)
294294
return y
295295

296296

deepmd/pt_expt/model/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# SPDX-License-Identifier: LGPL-3.0-or-later
2+
from .ener_model import (
3+
EnergyModel,
4+
)
5+
6+
__all__ = [
7+
"EnergyModel",
8+
]

deepmd/pt_expt/model/ener_model.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# SPDX-License-Identifier: LGPL-3.0-or-later
2+
from typing import (
3+
Any,
4+
)
5+
6+
import torch
7+
8+
from deepmd.dpmodel.model.dp_model import (
9+
DPModelCommon,
10+
)
11+
from deepmd.pt_expt.atomic_model import (
12+
DPEnergyAtomicModel,
13+
)
14+
15+
from .make_model import (
16+
make_model,
17+
)
18+
19+
DPEnergyModel_ = make_model(DPEnergyAtomicModel)
20+
21+
22+
class EnergyModel(DPModelCommon, DPEnergyModel_):
23+
model_type = "ener"
24+
25+
def __init__(
26+
self,
27+
*args: Any,
28+
**kwargs: Any,
29+
) -> None:
30+
DPModelCommon.__init__(self)
31+
DPEnergyModel_.__init__(self, *args, **kwargs)
32+
33+
def forward(
34+
self,
35+
coord: torch.Tensor,
36+
atype: torch.Tensor,
37+
box: torch.Tensor | None = None,
38+
fparam: torch.Tensor | None = None,
39+
aparam: torch.Tensor | None = None,
40+
do_atomic_virial: bool = False,
41+
) -> dict[str, torch.Tensor]:
42+
model_ret = self.call(
43+
coord,
44+
atype,
45+
box,
46+
fparam=fparam,
47+
aparam=aparam,
48+
do_atomic_virial=do_atomic_virial,
49+
)
50+
model_predict = {}
51+
model_predict["atom_energy"] = model_ret["energy"]
52+
model_predict["energy"] = model_ret["energy_redu"]
53+
if self.do_grad_r("energy"):
54+
model_predict["force"] = model_ret["energy_derv_r"].squeeze(-2)
55+
if self.do_grad_c("energy"):
56+
model_predict["virial"] = model_ret["energy_derv_c_redu"].squeeze(-2)
57+
if do_atomic_virial:
58+
model_predict["atom_virial"] = model_ret["energy_derv_c"].squeeze(-3)
59+
if "mask" in model_ret:
60+
model_predict["mask"] = model_ret["mask"]
61+
return model_predict

deepmd/pt_expt/model/make_model.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# SPDX-License-Identifier: LGPL-3.0-or-later
2+
from typing import (
3+
Any,
4+
)
5+
6+
import torch
7+
8+
from deepmd.dpmodel.atomic_model.base_atomic_model import (
9+
BaseAtomicModel,
10+
)
11+
from deepmd.dpmodel.model.make_model import make_model as make_model_dp
12+
from deepmd.pt_expt.common import (
13+
dpmodel_setattr,
14+
)
15+
16+
from .transform_output import (
17+
fit_output_to_model_output,
18+
)
19+
20+
21+
def make_model(T_AtomicModel: type[BaseAtomicModel]) -> type:
22+
"""Make a model as a derived class of an atomic model.
23+
24+
Wraps dpmodel's make_model with torch.nn.Module and overrides
25+
forward_common_atomic to use autograd-based derivatives.
26+
27+
Parameters
28+
----------
29+
T_AtomicModel
30+
The atomic model.
31+
32+
Returns
33+
-------
34+
CM
35+
The model.
36+
37+
"""
38+
DPModel = make_model_dp(T_AtomicModel)
39+
40+
class CM(DPModel, torch.nn.Module):
41+
def __init__(
42+
self,
43+
*args: Any,
44+
**kwargs: Any,
45+
) -> None:
46+
torch.nn.Module.__init__(self)
47+
DPModel.__init__(self, *args, **kwargs)
48+
49+
def __call__(self, *args: Any, **kwargs: Any) -> Any:
50+
# Ensure torch.nn.Module.__call__ drives forward() for export/tracing.
51+
return torch.nn.Module.__call__(self, *args, **kwargs)
52+
53+
def __setattr__(self, name: str, value: Any) -> None:
54+
handled, value = dpmodel_setattr(self, name, value)
55+
if not handled:
56+
super().__setattr__(name, value)
57+
58+
def forward(self, *args: Any, **kwargs: Any) -> dict[str, torch.Tensor]:
59+
"""Default forward delegates to call().
60+
61+
Subclasses (e.g. EnergyModel) override this with output translation.
62+
"""
63+
return self.call(*args, **kwargs)
64+
65+
def forward_common_atomic(
66+
self,
67+
extended_coord: torch.Tensor,
68+
extended_atype: torch.Tensor,
69+
nlist: torch.Tensor,
70+
mapping: torch.Tensor | None = None,
71+
fparam: torch.Tensor | None = None,
72+
aparam: torch.Tensor | None = None,
73+
do_atomic_virial: bool = False,
74+
) -> dict[str, torch.Tensor]:
75+
atomic_ret = self.atomic_model.forward_common_atomic(
76+
extended_coord,
77+
extended_atype,
78+
nlist,
79+
mapping=mapping,
80+
fparam=fparam,
81+
aparam=aparam,
82+
)
83+
return fit_output_to_model_output(
84+
atomic_ret,
85+
self.atomic_output_def(),
86+
extended_coord,
87+
do_atomic_virial=do_atomic_virial,
88+
create_graph=self.training,
89+
mask=atomic_ret.get("mask"),
90+
)
91+
92+
return CM

0 commit comments

Comments
 (0)