Skip to content

Commit

Permalink
add mask to atomic model output when an atomic type exclusion presents.
Browse files Browse the repository at this point in the history
  • Loading branch information
Han Wang committed Mar 2, 2024
1 parent f4abe12 commit 17d7128
Show file tree
Hide file tree
Showing 11 changed files with 178 additions and 17 deletions.
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 @@ def reinit_pair_exclude(
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 @@ def forward_common_atomic(
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 @@ class BAM(ABC):

@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 @@ def __init__(
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 @@ def cal_g(
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 @@ def __init__(
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 @@ def __getitem__(self, key):
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 __init__(

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 @@ def call_lower(
)
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 reinit_pair_exclude(
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 @@ def forward_common_atomic(
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 @@ def make_hessian_model(T_Model):
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 @@ def __init__(
*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 @@ def requires_hessian(
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 @@ def forward_common(
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 @@ def _cal_hessian_all(
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 __init__(

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 @@ def forward_common_lower(
)
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

0 comments on commit 17d7128

Please sign in to comment.