Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add mask to atomic model output when an atomic type exclusion presents. #3389

Merged
merged 2 commits into from
Mar 2, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions deepmd/dpmodel/atomic_model/base_atomic_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@

import numpy as np

from deepmd.dpmodel.output_def import (
FittingOutputDef,
OutputVariableDef,
)
from deepmd.dpmodel.utils import (
AtomExcludeMask,
PairExcludeMask,
Expand Down Expand Up @@ -50,6 +54,24 @@
else:
self.pair_excl = PairExcludeMask(self.get_ntypes(), self.pair_exclude_types)

def atomic_output_def(self) -> FittingOutputDef:
old_def = self.fitting_output_def()
if self.atom_excl is None:
return old_def

Check warning on line 60 in deepmd/dpmodel/atomic_model/base_atomic_model.py

View check run for this annotation

Codecov / codecov/patch

deepmd/dpmodel/atomic_model/base_atomic_model.py#L58-L60

Added lines #L58 - L60 were not covered by tests
else:
old_list = list(old_def.get_data().values())
return FittingOutputDef(

Check warning on line 63 in deepmd/dpmodel/atomic_model/base_atomic_model.py

View check run for this annotation

Codecov / codecov/patch

deepmd/dpmodel/atomic_model/base_atomic_model.py#L62-L63

Added lines #L62 - L63 were not covered by tests
old_list.append(
OutputVariableDef(
name="mask",
shape=[1],
reduciable=False,
r_differentiable=False,
c_differentiable=False,
)
)
)

def forward_common_atomic(
self,
extended_coord: np.ndarray,
Expand Down Expand Up @@ -79,6 +101,7 @@
atom_mask = self.atom_excl.build_type_exclude_mask(atype)
for kk in ret_dict.keys():
ret_dict[kk] = ret_dict[kk] * atom_mask[:, :, None]
ret_dict["mask"] = atom_mask

Check warning on line 104 in deepmd/dpmodel/atomic_model/base_atomic_model.py

View check run for this annotation

Codecov / codecov/patch

deepmd/dpmodel/atomic_model/base_atomic_model.py#L104

Added line #L104 was not covered by tests

return ret_dict

Expand Down
11 changes: 10 additions & 1 deletion deepmd/dpmodel/atomic_model/make_base_atomic_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,18 @@

@abstractmethod
def fitting_output_def(self) -> FittingOutputDef:
"""Get the fitting output def."""
"""Get the output def of developer implemented atomic models."""
pass

def atomic_output_def(self) -> FittingOutputDef:
"""Get the output def of the atomic model.

By default it is the same as FittingOutputDef, but it
allows model level wrapper of the output defined by the developer.

"""
return self.fitting_output_def()

Check warning on line 49 in deepmd/dpmodel/atomic_model/make_base_atomic_model.py

View check run for this annotation

Codecov / codecov/patch

deepmd/dpmodel/atomic_model/make_base_atomic_model.py#L49

Added line #L49 was not covered by tests

@abstractmethod
def get_rcut(self) -> float:
"""Get the cut-off radius."""
Expand Down
12 changes: 10 additions & 2 deletions deepmd/dpmodel/descriptor/se_e2_a.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
Any,
List,
Optional,
Tuple,
)

from deepmd.dpmodel import (
Expand Down Expand Up @@ -168,12 +169,12 @@
self.resnet_dt = resnet_dt
self.trainable = trainable
self.type_one_side = type_one_side
self.exclude_types = exclude_types
self.set_davg_zero = set_davg_zero
self.activation_function = activation_function
self.precision = precision
self.spin = spin
self.emask = PairExcludeMask(self.ntypes, self.exclude_types)
# order matters, placed after the assignment of self.ntypes
self.reinit_exclude(exclude_types)

Check warning on line 177 in deepmd/dpmodel/descriptor/se_e2_a.py

View check run for this annotation

Codecov / codecov/patch

deepmd/dpmodel/descriptor/se_e2_a.py#L177

Added line #L177 was not covered by tests

in_dim = 1 # not considiering type embedding
self.embeddings = NetworkCollection(
Expand Down Expand Up @@ -271,6 +272,13 @@
gg = self.embeddings[embedding_idx].call(ss)
return gg

def reinit_exclude(
self,
exclude_types: List[Tuple[int, int]] = [],
):
self.exclude_types = exclude_types
self.emask = PairExcludeMask(self.ntypes, exclude_types=exclude_types)

Check warning on line 280 in deepmd/dpmodel/descriptor/se_e2_a.py

View check run for this annotation

Codecov / codecov/patch

deepmd/dpmodel/descriptor/se_e2_a.py#L279-L280

Added lines #L279 - L280 were not covered by tests

def call(
self,
coord_ext,
Expand Down
12 changes: 9 additions & 3 deletions deepmd/dpmodel/fitting/general_fitting.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,13 +120,12 @@
self.use_aparam_as_mask = use_aparam_as_mask
self.spin = spin
self.mixed_types = mixed_types
self.exclude_types = exclude_types
# order matters, should be place after the assignment of ntypes
self.reinit_exclude(exclude_types)

Check warning on line 124 in deepmd/dpmodel/fitting/general_fitting.py

View check run for this annotation

Codecov / codecov/patch

deepmd/dpmodel/fitting/general_fitting.py#L124

Added line #L124 was not covered by tests
if self.spin is not None:
raise NotImplementedError("spin is not supported")
self.remove_vaccum_contribution = remove_vaccum_contribution

self.emask = AtomExcludeMask(self.ntypes, self.exclude_types)

net_dim_out = self._net_out_dim()
# init constants
self.bias_atom_e = np.zeros([self.ntypes, net_dim_out])
Expand Down Expand Up @@ -214,6 +213,13 @@
else:
raise KeyError(key)

def reinit_exclude(
self,
exclude_types: List[int] = [],
):
self.exclude_types = exclude_types
self.emask = AtomExcludeMask(self.ntypes, self.exclude_types)

Check warning on line 221 in deepmd/dpmodel/fitting/general_fitting.py

View check run for this annotation

Codecov / codecov/patch

deepmd/dpmodel/fitting/general_fitting.py#L220-L221

Added lines #L220 - L221 were not covered by tests

def serialize(self) -> dict:
"""Serialize the fitting to dict."""
return {
Expand Down
4 changes: 2 additions & 2 deletions deepmd/dpmodel/model/make_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@

def model_output_def(self):
"""Get the output def for the model."""
return ModelOutputDef(self.fitting_output_def())
return ModelOutputDef(self.atomic_output_def())

Check warning on line 77 in deepmd/dpmodel/model/make_model.py

View check run for this annotation

Codecov / codecov/patch

deepmd/dpmodel/model/make_model.py#L77

Added line #L77 was not covered by tests

def model_output_type(self) -> str:
"""Get the output type for the model."""
Expand Down Expand Up @@ -223,7 +223,7 @@
)
model_predict = fit_output_to_model_output(
atomic_ret,
self.fitting_output_def(),
self.atomic_output_def(),
cc_ext,
do_atomic_virial=do_atomic_virial,
)
Expand Down
23 changes: 23 additions & 0 deletions deepmd/pt/model/atomic_model/base_atomic_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
from deepmd.dpmodel.atomic_model import (
make_base_atomic_model,
)
from deepmd.dpmodel.output_def import (

Check warning on line 16 in deepmd/pt/model/atomic_model/base_atomic_model.py

View check run for this annotation

Codecov / codecov/patch

deepmd/pt/model/atomic_model/base_atomic_model.py#L16

Added line #L16 was not covered by tests
FittingOutputDef,
OutputVariableDef,
)
from deepmd.pt.utils import (
AtomExcludeMask,
PairExcludeMask,
Expand Down Expand Up @@ -60,6 +64,24 @@
def get_model_def_script(self) -> str:
return self.model_def_script

def atomic_output_def(self) -> FittingOutputDef:
old_def = self.fitting_output_def()
if self.atom_excl is None:
return old_def

Check warning on line 70 in deepmd/pt/model/atomic_model/base_atomic_model.py

View check run for this annotation

Codecov / codecov/patch

deepmd/pt/model/atomic_model/base_atomic_model.py#L67-L70

Added lines #L67 - L70 were not covered by tests
else:
old_list = list(old_def.get_data().values())
return FittingOutputDef(

Check warning on line 73 in deepmd/pt/model/atomic_model/base_atomic_model.py

View check run for this annotation

Codecov / codecov/patch

deepmd/pt/model/atomic_model/base_atomic_model.py#L72-L73

Added lines #L72 - L73 were not covered by tests
old_list.append(
OutputVariableDef(
name="mask",
shape=[1],
reduciable=False,
r_differentiable=False,
c_differentiable=False,
)
)
)

def forward_common_atomic(
self,
extended_coord: torch.Tensor,
Expand Down Expand Up @@ -90,6 +112,7 @@
atom_mask = self.atom_excl(atype)
for kk in ret_dict.keys():
ret_dict[kk] = ret_dict[kk] * atom_mask[:, :, None]
ret_dict["mask"] = atom_mask

Check warning on line 115 in deepmd/pt/model/atomic_model/base_atomic_model.py

View check run for this annotation

Codecov / codecov/patch

deepmd/pt/model/atomic_model/base_atomic_model.py#L115

Added line #L115 was not covered by tests

return ret_dict

Expand Down
10 changes: 5 additions & 5 deletions deepmd/pt/model/model/make_hessian_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
Parameters
----------
T_Model
The model. Should provide the `forward_common` and `fitting_output_def` methods
The model. Should provide the `forward_common` and `atomic_output_def` methods

Returns
-------
Expand All @@ -43,7 +43,7 @@
*args,
**kwargs,
)
self.hess_fitting_def = copy.deepcopy(super().fitting_output_def())
self.hess_fitting_def = copy.deepcopy(super().atomic_output_def())

Check warning on line 46 in deepmd/pt/model/model/make_hessian_model.py

View check run for this annotation

Codecov / codecov/patch

deepmd/pt/model/model/make_hessian_model.py#L46

Added line #L46 was not covered by tests

def requires_hessian(
self,
Expand All @@ -56,7 +56,7 @@
if kk in keys:
self.hess_fitting_def[kk].r_hessian = True

def fitting_output_def(self):
def atomic_output_def(self):

Check warning on line 59 in deepmd/pt/model/model/make_hessian_model.py

View check run for this annotation

Codecov / codecov/patch

deepmd/pt/model/model/make_hessian_model.py#L59

Added line #L59 was not covered by tests
"""Get the fitting output def."""
return self.hess_fitting_def

Expand Down Expand Up @@ -102,7 +102,7 @@
aparam=aparam,
do_atomic_virial=do_atomic_virial,
)
vdef = self.fitting_output_def()
vdef = self.atomic_output_def()

Check warning on line 105 in deepmd/pt/model/model/make_hessian_model.py

View check run for this annotation

Codecov / codecov/patch

deepmd/pt/model/model/make_hessian_model.py#L105

Added line #L105 was not covered by tests
hess_yes = [vdef[kk].r_hessian for kk in vdef.keys()]
if any(hess_yes):
hess = self._cal_hessian_all(
Expand All @@ -128,7 +128,7 @@
box = box.view([nf, 9]) if box is not None else None
fparam = fparam.view([nf, -1]) if fparam is not None else None
aparam = aparam.view([nf, nloc, -1]) if aparam is not None else None
fdef = self.fitting_output_def()
fdef = self.atomic_output_def()

Check warning on line 131 in deepmd/pt/model/model/make_hessian_model.py

View check run for this annotation

Codecov / codecov/patch

deepmd/pt/model/model/make_hessian_model.py#L131

Added line #L131 was not covered by tests
# keys of values that require hessian
hess_keys: List[str] = []
for kk in fdef.keys():
Expand Down
4 changes: 2 additions & 2 deletions deepmd/pt/model/model/make_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@

def model_output_def(self):
"""Get the output def for the model."""
return ModelOutputDef(self.fitting_output_def())
return ModelOutputDef(self.atomic_output_def())

Check warning on line 75 in deepmd/pt/model/model/make_model.py

View check run for this annotation

Codecov / codecov/patch

deepmd/pt/model/model/make_model.py#L75

Added line #L75 was not covered by tests

@torch.jit.export
def model_output_type(self) -> str:
Expand Down Expand Up @@ -218,7 +218,7 @@
)
model_predict = fit_output_to_model_output(
atomic_ret,
self.fitting_output_def(),
self.atomic_output_def(),
cc_ext,
do_atomic_virial=do_atomic_virial,
)
Expand Down
65 changes: 65 additions & 0 deletions source/tests/common/dpmodel/test_dp_atomic_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,68 @@ def test_self_consistency(
ret1 = md1.forward_common_atomic(self.coord_ext, self.atype_ext, self.nlist)

np.testing.assert_allclose(ret0["energy"], ret1["energy"])

def test_excl_consistency(self):
type_map = ["foo", "bar"]

# test the case of exclusion
for atom_excl, pair_excl in itertools.product([[], [1]], [[], [[0, 1]]]):
ds = DescrptSeA(
self.rcut,
self.rcut_smth,
self.sel,
)
ft = InvarFitting(
"energy",
self.nt,
ds.get_dim_out(),
1,
mixed_types=ds.mixed_types(),
)
md0 = DPAtomicModel(
ds,
ft,
type_map=type_map,
)
md1 = DPAtomicModel.deserialize(md0.serialize())

md0.reinit_atom_exclude(atom_excl)
md0.reinit_pair_exclude(pair_excl)
# hacking!
md1.descriptor.reinit_exclude(pair_excl)
md1.fitting.reinit_exclude(atom_excl)

# check energy consistency
args = [self.coord_ext, self.atype_ext, self.nlist]
ret0 = md0.forward_common_atomic(*args)
ret1 = md1.forward_common_atomic(*args)
np.testing.assert_allclose(
ret0["energy"],
ret1["energy"],
)

# check output def
out_names = [vv.name for vv in md0.atomic_output_def().get_data().values()]
if atom_excl == []:
self.assertEqual(out_names, ["energy"])
else:
self.assertEqual(out_names, ["energy", "mask"])
for ii in md0.atomic_output_def().get_data().values():
if ii.name == "mask":
self.assertEqual(ii.shape, [1])
self.assertFalse(ii.reduciable)
self.assertFalse(ii.r_differentiable)
self.assertFalse(ii.c_differentiable)

# check mask
if atom_excl == []:
pass
elif atom_excl == [1]:
self.assertIn("mask", ret0.keys())
expected = np.array([1, 1, 0], dtype=int)
expected = np.concatenate(
[expected, expected[self.perm[: self.nloc]]]
).reshape(2, 3)
np.testing.assert_array_equal(ret0["mask"], expected)
else:
raise ValueError(f"not expected atom_excl {atom_excl}")
27 changes: 27 additions & 0 deletions source/tests/pt/model/test_dp_atomic_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ def test_excl_consistency(self):
md1.descriptor.reinit_exclude(pair_excl)
md1.fitting_net.reinit_exclude(atom_excl)

# check energy consistency
args = [
to_torch_tensor(ii)
for ii in [self.coord_ext, self.atype_ext, self.nlist]
Expand All @@ -162,3 +163,29 @@ def test_excl_consistency(self):
to_numpy_array(ret0["energy"]),
to_numpy_array(ret1["energy"]),
)

# check output def
out_names = [vv.name for vv in md0.atomic_output_def().get_data().values()]
if atom_excl == []:
self.assertEqual(out_names, ["energy"])
else:
self.assertEqual(out_names, ["energy", "mask"])
for ii in md0.atomic_output_def().get_data().values():
if ii.name == "mask":
self.assertEqual(ii.shape, [1])
self.assertFalse(ii.reduciable)
self.assertFalse(ii.r_differentiable)
self.assertFalse(ii.c_differentiable)

# check mask
if atom_excl == []:
pass
elif atom_excl == [1]:
self.assertIn("mask", ret0.keys())
expected = np.array([1, 1, 0], dtype=int)
expected = np.concatenate(
[expected, expected[self.perm[: self.nloc]]]
).reshape(2, 3)
np.testing.assert_array_equal(to_numpy_array(ret0["mask"]), expected)
else:
raise ValueError(f"not expected atom_excl {atom_excl}")
4 changes: 2 additions & 2 deletions source/tests/pt/model/test_make_hessian_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,8 +166,8 @@ def setUp(self):
self.model_hess.requires_hessian("energy")

def test_output_def(self):
self.assertTrue(self.model_hess.fitting_output_def()["energy"].r_hessian)
self.assertFalse(self.model_valu.fitting_output_def()["energy"].r_hessian)
self.assertTrue(self.model_hess.atomic_output_def()["energy"].r_hessian)
self.assertFalse(self.model_valu.atomic_output_def()["energy"].r_hessian)
self.assertTrue(self.model_hess.model_output_def()["energy"].r_hessian)
self.assertEqual(
self.model_hess.model_output_def()["energy_derv_r_derv_r"].category,
Expand Down
Loading