From a6f133387ce0b7dbf823fad54bfe5725b90228ce Mon Sep 17 00:00:00 2001 From: Jinzhe Zeng Date: Mon, 11 Dec 2023 00:21:01 -0500 Subject: [PATCH] build neighbor list with external Python program (#3046) Fix #2877 --------- Signed-off-by: Jinzhe Zeng --- deepmd/calculator.py | 5 +- deepmd/infer/__init__.py | 7 +++ deepmd/infer/deep_dipole.py | 4 ++ deepmd/infer/deep_eval.py | 95 +++++++++++++++++++++++++++++++++ deepmd/infer/deep_polar.py | 13 ++++- deepmd/infer/deep_pot.py | 58 +++++++++++++++++--- deepmd/infer/deep_tensor.py | 85 +++++++++++++++++++++++++---- source/tests/test_deepdipole.py | 32 +++++++++-- source/tests/test_deeppolar.py | 34 +++++++++--- source/tests/test_deeppot_a.py | 23 ++++++++ 10 files changed, 326 insertions(+), 30 deletions(-) diff --git a/deepmd/calculator.py b/deepmd/calculator.py index 8636ff30d2..b9c0a81006 100644 --- a/deepmd/calculator.py +++ b/deepmd/calculator.py @@ -45,6 +45,8 @@ class DP(Calculator): type_dict : Dict[str, int], optional mapping of element types and their numbers, best left None and the calculator will infer this information from model, by default None + neighbor_list : ase.neighborlist.NeighborList, optional + The neighbor list object. If None, then build the native neighbor list. Examples -------- @@ -83,10 +85,11 @@ def __init__( model: Union[str, "Path"], label: str = "DP", type_dict: Optional[Dict[str, int]] = None, + neighbor_list=None, **kwargs, ) -> None: Calculator.__init__(self, label=label, **kwargs) - self.dp = DeepPotential(str(Path(model).resolve())) + self.dp = DeepPotential(str(Path(model).resolve()), neighbor_list=neighbor_list) if type_dict: self.type_dict = type_dict else: diff --git a/deepmd/infer/__init__.py b/deepmd/infer/__init__.py index 14d75d0c44..c1071af35c 100644 --- a/deepmd/infer/__init__.py +++ b/deepmd/infer/__init__.py @@ -58,6 +58,7 @@ def DeepPotential( load_prefix: str = "load", default_tf_graph: bool = False, input_map: Optional[dict] = None, + neighbor_list=None, ) -> Union[DeepDipole, DeepGlobalPolar, DeepPolar, DeepPot, DeepDOS, DeepWFC]: """Factory function that will inialize appropriate potential read from `model_file`. @@ -71,6 +72,8 @@ def DeepPotential( If uses the default tf graph, otherwise build a new tf graph for evaluation input_map : dict, optional The input map for tf.import_graph_def. Only work with default tf graph + neighbor_list : ase.neighborlist.NeighborList, optional + The neighbor list object. If None, then build the native neighbor list. Returns ------- @@ -97,6 +100,7 @@ def DeepPotential( load_prefix=load_prefix, default_tf_graph=default_tf_graph, input_map=input_map, + neighbor_list=neighbor_list, ) elif model_type == "dos": dp = DeepDOS( @@ -111,6 +115,7 @@ def DeepPotential( load_prefix=load_prefix, default_tf_graph=default_tf_graph, input_map=input_map, + neighbor_list=neighbor_list, ) elif model_type == "polar": dp = DeepPolar( @@ -118,6 +123,7 @@ def DeepPotential( load_prefix=load_prefix, default_tf_graph=default_tf_graph, input_map=input_map, + neighbor_list=neighbor_list, ) elif model_type == "global_polar": dp = DeepGlobalPolar( @@ -125,6 +131,7 @@ def DeepPotential( load_prefix=load_prefix, default_tf_graph=default_tf_graph, input_map=input_map, + neighbor_list=neighbor_list, ) elif model_type == "wfc": dp = DeepWFC( diff --git a/deepmd/infer/deep_dipole.py b/deepmd/infer/deep_dipole.py index 6020118135..aba098a9f3 100644 --- a/deepmd/infer/deep_dipole.py +++ b/deepmd/infer/deep_dipole.py @@ -27,6 +27,8 @@ class DeepDipole(DeepTensor): If uses the default tf graph, otherwise build a new tf graph for evaluation input_map : dict, optional The input map for tf.import_graph_def. Only work with default tf graph + neighbor_list : ase.neighborlist.NeighborList, optional + The neighbor list object. If None, then build the native neighbor list. Warnings -------- @@ -41,6 +43,7 @@ def __init__( load_prefix: str = "load", default_tf_graph: bool = False, input_map: Optional[dict] = None, + neighbor_list=None, ) -> None: # use this in favor of dict update to move attribute from class to # instance namespace @@ -58,6 +61,7 @@ def __init__( load_prefix=load_prefix, default_tf_graph=default_tf_graph, input_map=input_map, + neighbor_list=neighbor_list, ) def get_dim_fparam(self) -> int: diff --git a/deepmd/infer/deep_eval.py b/deepmd/infer/deep_eval.py index 3f5dede1ad..0ca9f21a77 100644 --- a/deepmd/infer/deep_eval.py +++ b/deepmd/infer/deep_eval.py @@ -45,6 +45,9 @@ class DeepEval: as the initial batch size. input_map : dict, optional The input map for tf.import_graph_def. Only work with default tf graph + neighbor_list : ase.neighborlist.NewPrimitiveNeighborList, optional + The ASE neighbor list class to produce the neighbor list. If None, the + neighbor list will be built natively in the model. """ load_prefix: str # set by subclass @@ -56,6 +59,7 @@ def __init__( default_tf_graph: bool = False, auto_batch_size: Union[bool, int, AutoBatchSize] = False, input_map: Optional[dict] = None, + neighbor_list=None, ): self.graph = self._load_graph( model_file, @@ -86,6 +90,8 @@ def __init__( else: raise TypeError("auto_batch_size should be bool, int, or AutoBatchSize") + self.neighbor_list = neighbor_list + @property @lru_cache(maxsize=None) def model_type(self) -> str: @@ -360,3 +366,92 @@ def eval_typeebd(self) -> np.ndarray: t_typeebd = self._get_tensor("t_typeebd:0") [typeebd] = run_sess(self.sess, [t_typeebd], feed_dict={}) return typeebd + + def build_neighbor_list( + self, + coords: np.ndarray, + cell: Optional[np.ndarray], + atype: np.ndarray, + imap: np.ndarray, + neighbor_list, + ): + """Make the mesh with neighbor list for a single frame. + + Parameters + ---------- + coords : np.ndarray + The coordinates of atoms. Should be of shape [natoms, 3] + cell : Optional[np.ndarray] + The cell of the system. Should be of shape [3, 3] + atype : np.ndarray + The type of atoms. Should be of shape [natoms] + imap : np.ndarray + The index map of atoms. Should be of shape [natoms] + neighbor_list : ase.neighborlist.NewPrimitiveNeighborList + ASE neighbor list. The following method or attribute will be + used/set: bothways, self_interaction, update, build, first_neigh, + pair_second, offset_vec. + + Returns + ------- + natoms_vec : np.ndarray + The number of atoms. This tensor has the length of Ntypes + 2 + natoms[0]: nloc + natoms[1]: nall + natoms[i]: 2 <= i < Ntypes+2, number of type i atoms for nloc + coords : np.ndarray + The coordinates of atoms, including ghost atoms. Should be of + shape [nframes, nall, 3] + atype : np.ndarray + The type of atoms, including ghost atoms. Should be of shape [nall] + mesh : np.ndarray + The mesh in nei_mode=4. + imap : np.ndarray + The index map of atoms. Should be of shape [nall] + ghost_map : np.ndarray + The index map of ghost atoms. Should be of shape [nghost] + """ + pbc = np.repeat(cell is not None, 3) + cell = cell.reshape(3, 3) + positions = coords.reshape(-1, 3) + neighbor_list.bothways = True + neighbor_list.self_interaction = False + if neighbor_list.update(pbc, cell, positions): + neighbor_list.build(pbc, cell, positions) + first_neigh = neighbor_list.first_neigh.copy() + pair_second = neighbor_list.pair_second.copy() + offset_vec = neighbor_list.offset_vec.copy() + # get out-of-box neighbors + out_mask = np.any(offset_vec != 0, axis=1) + out_idx = pair_second[out_mask] + out_offset = offset_vec[out_mask] + out_coords = positions[out_idx] + out_offset.dot(cell) + atype = np.array(atype, dtype=int) + out_atype = atype[out_idx] + + nloc = positions.shape[0] + nghost = out_idx.size + all_coords = np.concatenate((positions, out_coords), axis=0) + all_atype = np.concatenate((atype, out_atype), axis=0) + # convert neighbor indexes + ghost_map = pair_second[out_mask] + pair_second[out_mask] = np.arange(nloc, nloc + nghost) + # get the mesh + mesh = np.zeros(16 + nloc * 2 + pair_second.size, dtype=int) + mesh[0] = nloc + # ilist + mesh[16 : 16 + nloc] = np.arange(nloc) + # numnei + mesh[16 + nloc : 16 + nloc * 2] = first_neigh[1:] - first_neigh[:-1] + # jlist + mesh[16 + nloc * 2 :] = pair_second + + # natoms_vec + natoms_vec = np.zeros(self.ntypes + 2).astype(int) + natoms_vec[0] = nloc + natoms_vec[1] = nloc + nghost + for ii in range(self.ntypes): + natoms_vec[ii + 2] = np.count_nonzero(atype == ii) + # imap append ghost atoms + imap = np.concatenate((imap, np.arange(nloc, nloc + nghost))) + return natoms_vec, all_coords, all_atype, mesh, imap, ghost_map diff --git a/deepmd/infer/deep_polar.py b/deepmd/infer/deep_polar.py index 118f8c98a7..c1f981ef86 100644 --- a/deepmd/infer/deep_polar.py +++ b/deepmd/infer/deep_polar.py @@ -30,6 +30,8 @@ class DeepPolar(DeepTensor): If uses the default tf graph, otherwise build a new tf graph for evaluation input_map : dict, optional The input map for tf.import_graph_def. Only work with default tf graph + neighbor_list : ase.neighborlist.NeighborList, optional + The neighbor list object. If None, then build the native neighbor list. Warnings -------- @@ -44,6 +46,7 @@ def __init__( load_prefix: str = "load", default_tf_graph: bool = False, input_map: Optional[dict] = None, + neighbor_list=None, ) -> None: # use this in favor of dict update to move attribute from class to # instance namespace @@ -61,6 +64,7 @@ def __init__( load_prefix=load_prefix, default_tf_graph=default_tf_graph, input_map=input_map, + neighbor_list=neighbor_list, ) def get_dim_fparam(self) -> int: @@ -83,10 +87,16 @@ class DeepGlobalPolar(DeepTensor): The prefix in the load computational graph default_tf_graph : bool If uses the default tf graph, otherwise build a new tf graph for evaluation + neighbor_list : ase.neighborlist.NeighborList, optional + The neighbor list object. If None, then build the native neighbor list. """ def __init__( - self, model_file: str, load_prefix: str = "load", default_tf_graph: bool = False + self, + model_file: str, + load_prefix: str = "load", + default_tf_graph: bool = False, + neighbor_list=None, ) -> None: self.tensors.update( { @@ -101,6 +111,7 @@ def __init__( model_file, load_prefix=load_prefix, default_tf_graph=default_tf_graph, + neighbor_list=None, ) def eval( diff --git a/deepmd/infer/deep_pot.py b/deepmd/infer/deep_pot.py index fc9a6a76ed..81cfdde7a8 100644 --- a/deepmd/infer/deep_pot.py +++ b/deepmd/infer/deep_pot.py @@ -51,6 +51,9 @@ class DeepPot(DeepEval): as the initial batch size. input_map : dict, optional The input map for tf.import_graph_def. Only work with default tf graph + neighbor_list : ase.neighborlist.NewPrimitiveNeighborList, optional + The ASE neighbor list class to produce the neighbor list. If None, the + neighbor list will be built natively in the model. Examples -------- @@ -78,6 +81,7 @@ def __init__( default_tf_graph: bool = False, auto_batch_size: Union[bool, int, AutoBatchSize] = True, input_map: Optional[dict] = None, + neighbor_list=None, ) -> None: # add these tensors on top of what is defined by DeepTensor Class # use this in favor of dict update to move attribute from class to @@ -112,6 +116,7 @@ def __init__( default_tf_graph=default_tf_graph, auto_batch_size=auto_batch_size, input_map=input_map, + neighbor_list=neighbor_list, ) # load optional tensors @@ -479,8 +484,30 @@ def _prepare_feed_dict( aparam = np.reshape(aparam, [nframes, natoms * fdim]) # make natoms_vec and default_mesh - natoms_vec = self.make_natoms_vec(atom_types, mixed_type=mixed_type) - assert natoms_vec[0] == natoms + if self.neighbor_list is None: + natoms_vec = self.make_natoms_vec(atom_types, mixed_type=mixed_type) + assert natoms_vec[0] == natoms + mesh = make_default_mesh(pbc, mixed_type) + ghost_map = None + else: + if nframes > 1: + raise NotImplementedError( + "neighbor_list does not support multiple frames" + ) + ( + natoms_vec, + coords, + atom_types, + mesh, + imap, + ghost_map, + ) = self.build_neighbor_list( + coords, + cells if cells is not None else None, + atom_types, + imap, + self.neighbor_list, + ) # evaluate feed_dict_test = {} @@ -501,12 +528,12 @@ def _prepare_feed_dict( raise RuntimeError if self.has_efield: feed_dict_test[self.t_efield] = np.reshape(efield, [-1]) - feed_dict_test[self.t_mesh] = make_default_mesh(pbc, mixed_type) + feed_dict_test[self.t_mesh] = mesh if self.has_fparam: feed_dict_test[self.t_fparam] = np.reshape(fparam, [-1]) if self.has_aparam: feed_dict_test[self.t_aparam] = np.reshape(aparam, [-1]) - return feed_dict_test, imap, natoms_vec + return feed_dict_test, imap, natoms_vec, ghost_map def _eval_inner( self, @@ -522,10 +549,13 @@ def _eval_inner( natoms, nframes = self._get_natoms_and_nframes( coords, atom_types, mixed_type=mixed_type ) - feed_dict_test, imap, natoms_vec = self._prepare_feed_dict( + feed_dict_test, imap, natoms_vec, ghost_map = self._prepare_feed_dict( coords, cells, atom_types, fparam, aparam, efield, mixed_type=mixed_type ) + nloc = natoms_vec[0] + nall = natoms_vec[1] + t_out = [self.t_energy, self.t_force, self.t_virial] if atomic: t_out += [self.t_ae, self.t_av] @@ -548,6 +578,13 @@ def _eval_inner( ) else: natoms_real = natoms + if ghost_map is not None: + # add the value of ghost atoms to real atoms + force = np.reshape(force, [nframes, -1, 3]) + np.add.at(force[0], ghost_map, force[0, nloc:]) + if atomic: + av = np.reshape(av, [nframes, -1, 9]) + np.add.at(av[0], ghost_map, av[0, nloc:]) # reverse map of the outputs force = self.reverse_map(np.reshape(force, [nframes, -1, 3]), imap) @@ -556,11 +593,15 @@ def _eval_inner( av = self.reverse_map(np.reshape(av, [nframes, -1, 9]), imap) energy = np.reshape(energy, [nframes, 1]) - force = np.reshape(force, [nframes, natoms, 3]) + force = np.reshape(force, [nframes, nall, 3]) + if nloc < nall: + force = force[:, :nloc, :] virial = np.reshape(virial, [nframes, 9]) if atomic: ae = np.reshape(ae, [nframes, natoms_real, 1]) - av = np.reshape(av, [nframes, natoms, 9]) + av = np.reshape(av, [nframes, nall, 9]) + if nloc < nall: + av = av[:, :nloc, :] return energy, force, virial, ae, av else: return energy, force, virial @@ -640,10 +681,11 @@ def _eval_descriptor_inner( natoms, nframes = self._get_natoms_and_nframes( coords, atom_types, mixed_type=mixed_type ) - feed_dict_test, imap, natoms_vec = self._prepare_feed_dict( + feed_dict_test, imap, natoms_vec, ghost_map = self._prepare_feed_dict( coords, cells, atom_types, fparam, aparam, efield, mixed_type=mixed_type ) (descriptor,) = run_sess( self.sess, [self.t_descriptor], feed_dict=feed_dict_test ) + imap = imap[:natoms] return self.reverse_map(np.reshape(descriptor, [nframes, natoms, -1]), imap) diff --git a/deepmd/infer/deep_tensor.py b/deepmd/infer/deep_tensor.py index 268523e959..a803eb0c6b 100644 --- a/deepmd/infer/deep_tensor.py +++ b/deepmd/infer/deep_tensor.py @@ -39,6 +39,8 @@ class DeepTensor(DeepEval): If uses the default tf graph, otherwise build a new tf graph for evaluation input_map : dict, optional The input map for tf.import_graph_def. Only work with default tf graph + neighbor_list : ase.neighborlist.NeighborList, optional + The neighbor list object. If None, then build the native neighbor list. """ tensors: ClassVar[Dict[str, str]] = { @@ -63,6 +65,7 @@ def __init__( load_prefix: str = "load", default_tf_graph: bool = False, input_map: Optional[dict] = None, + neighbor_list=None, ) -> None: """Constructor.""" DeepEval.__init__( @@ -71,6 +74,7 @@ def __init__( load_prefix=load_prefix, default_tf_graph=default_tf_graph, input_map=input_map, + neighbor_list=neighbor_list, ) # check model type model_type = self.tensors["t_tensor"][2:-2] @@ -209,8 +213,29 @@ def eval( ) # make natoms_vec and default_mesh - natoms_vec = self.make_natoms_vec(atom_types, mixed_type=mixed_type) - assert natoms_vec[0] == natoms + if self.neighbor_list is None: + natoms_vec = self.make_natoms_vec(atom_types, mixed_type=mixed_type) + assert natoms_vec[0] == natoms + mesh = make_default_mesh(pbc, mixed_type) + else: + if nframes > 1: + raise NotImplementedError( + "neighbor_list does not support multiple frames" + ) + ( + natoms_vec, + coords, + atom_types, + mesh, + imap, + _, + ) = self.build_neighbor_list( + coords, + cells if cells is not None else None, + atom_types, + imap, + self.neighbor_list, + ) # evaluate feed_dict_test = {} @@ -223,7 +248,7 @@ def eval( ) feed_dict_test[self.t_coord] = np.reshape(coords, [-1]) feed_dict_test[self.t_box] = np.reshape(cells, [-1]) - feed_dict_test[self.t_mesh] = make_default_mesh(pbc, mixed_type) + feed_dict_test[self.t_mesh] = mesh if atomic: assert ( @@ -333,8 +358,30 @@ def eval_full( ) # make natoms_vec and default_mesh - natoms_vec = self.make_natoms_vec(atom_types, mixed_type=mixed_type) - assert natoms_vec[0] == natoms + if self.neighbor_list is None: + natoms_vec = self.make_natoms_vec(atom_types, mixed_type=mixed_type) + assert natoms_vec[0] == natoms + mesh = make_default_mesh(pbc, mixed_type) + ghost_map = None + else: + if nframes > 1: + raise NotImplementedError( + "neighbor_list does not support multiple frames" + ) + ( + natoms_vec, + coords, + atom_types, + mesh, + imap, + ghost_map, + ) = self.build_neighbor_list( + coords, + cells if cells is not None else None, + atom_types, + imap, + self.neighbor_list, + ) # evaluate feed_dict_test = {} @@ -347,7 +394,7 @@ def eval_full( ) feed_dict_test[self.t_coord] = np.reshape(coords, [-1]) feed_dict_test[self.t_box] = np.reshape(cells, [-1]) - feed_dict_test[self.t_mesh] = make_default_mesh(pbc, mixed_type) + feed_dict_test[self.t_mesh] = mesh t_out = [self.t_global_tensor, self.t_force, self.t_virial] if atomic: @@ -361,21 +408,39 @@ def eval_full( at = v_out[3] # atom tensor av = v_out[4] # atom virial + nloc = natoms_vec[0] + nall = natoms_vec[1] + + if ghost_map is not None: + # add the value of ghost atoms to real atoms + force = np.reshape(force, [nframes * nout, -1, 3]) + # TODO: is there some way not to use for loop? + for ii in range(nframes * nout): + np.add.at(force[ii], ghost_map, force[ii, nloc:]) + if atomic: + av = np.reshape(av, [nframes * nout, -1, 9]) + for ii in range(nframes * nout): + np.add.at(av[ii], ghost_map, av[ii, nloc:]) + # please note here the shape are wrong! - force = self.reverse_map(np.reshape(force, [nframes * nout, natoms, 3]), imap) + force = self.reverse_map(np.reshape(force, [nframes * nout, nall, 3]), imap) if atomic: at = self.reverse_map( np.reshape(at, [nframes, len(sel_at), nout]), sel_imap ) - av = self.reverse_map(np.reshape(av, [nframes * nout, natoms, 9]), imap) + av = self.reverse_map(np.reshape(av, [nframes * nout, nall, 9]), imap) # make sure the shapes are correct here gt = np.reshape(gt, [nframes, nout]) - force = np.reshape(force, [nframes, nout, natoms, 3]) + force = np.reshape(force, [nframes, nout, nall, 3]) + if nloc < nall: + force = force[:, :, :nloc, :] virial = np.reshape(virial, [nframes, nout, 9]) if atomic: at = np.reshape(at, [nframes, len(sel_at), self.output_dim]) - av = np.reshape(av, [nframes, nout, natoms, 9]) + av = np.reshape(av, [nframes, nout, nall, 9]) + if nloc < nall: + av = av[:, :, :nloc, :] return gt, force, virial, at, av else: return gt, force, virial diff --git a/source/tests/test_deepdipole.py b/source/tests/test_deepdipole.py index e26ad84a55..1d06b5fe92 100644 --- a/source/tests/test_deepdipole.py +++ b/source/tests/test_deepdipole.py @@ -2,6 +2,7 @@ import os import unittest +import ase.neighborlist import numpy as np from common import ( finite_difference, @@ -964,10 +965,6 @@ def test_1frame_full_atm(self): gt, ff, vv, at, av = self.dp.eval_full( self.coords, self.box, self.atype, atomic=True ) - for dd in at, ff, av: - print("\n\n") - print(", ".join(f"{ii:.18e}" for ii in dd.reshape(-1))) - print("\n\n") # check shape of the returns nframes = 1 natoms = len(self.atype) @@ -1035,3 +1032,30 @@ def test_1frame_full_atm_shuffle(self): np.testing.assert_almost_equal( vv.reshape([-1]), self.expected_gv.reshape([-1]), decimal=default_places ) + + +@unittest.skipIf( + parse_version(tf.__version__) < parse_version("1.15"), + f"The current tf version {tf.__version__} is too low to run the new testing model.", +) +class TestDeepDipoleNewPBCNeighborList(TestDeepDipoleNewPBC): + @classmethod + def setUpClass(cls): + convert_pbtxt_to_pb( + str(tests_path / os.path.join("infer", "deepdipole_new.pbtxt")), + "deepdipole_new.pb", + ) + cls.dp = DeepDipole( + "deepdipole_new.pb", + neighbor_list=ase.neighborlist.NewPrimitiveNeighborList( + cutoffs=6, bothways=True + ), + ) + + @unittest.skip("multiple frames not supported") + def test_2frame_full_atm(self): + pass + + @unittest.skip("multiple frames not supported") + def test_2frame_old_atm(self): + pass diff --git a/source/tests/test_deeppolar.py b/source/tests/test_deeppolar.py index 271d1650c0..9627851de4 100644 --- a/source/tests/test_deeppolar.py +++ b/source/tests/test_deeppolar.py @@ -2,6 +2,7 @@ import os import unittest +import ase.neighborlist import numpy as np from common import ( tests_path, @@ -980,12 +981,6 @@ def test_1frame_full_atm(self): self.coords, self.box, self.atype, atomic=True ) - # print the values - for dd in (at, ff, av): - print("\n\n") - print(", ".join(f"{i:.18e}" for i in dd.reshape(-1))) - print("\n\n") - # check shape of the returns nframes = 1 natoms = len(self.atype) @@ -1088,3 +1083,30 @@ def test_2frame_full_atm(self): np.testing.assert_almost_equal( vv.reshape([-1]), expected_gv.reshape([-1]), decimal=default_places ) + + +@unittest.skipIf( + parse_version(tf.__version__) < parse_version("1.15"), + f"The current tf version {tf.__version__} is too low to run the new testing model.", +) +class TestDeepPolarNewPBCNeighborList(unittest.TestCase): + @classmethod + def setUpClass(cls): + convert_pbtxt_to_pb( + str(tests_path / os.path.join("infer", "deeppolar_new.pbtxt")), + "deeppolar_new.pb", + ) + cls.dp = DeepPolar( + "deeppolar_new.pb", + neighbor_list=ase.neighborlist.NewPrimitiveNeighborList( + cutoffs=6, bothways=True + ), + ) + + @unittest.skip("multiple frames not supported") + def test_2frame_full_atm(self): + pass + + @unittest.skip("multiple frames not supported") + def test_2frame_old_atm(self): + pass diff --git a/source/tests/test_deeppot_a.py b/source/tests/test_deeppot_a.py index 1c6cdc4afc..c229b4302c 100644 --- a/source/tests/test_deeppot_a.py +++ b/source/tests/test_deeppot_a.py @@ -3,6 +3,7 @@ import shutil import unittest +import ase.neighborlist import numpy as np from common import ( run_dp, @@ -1096,3 +1097,25 @@ def test_2frame_atm_all_param(self): np.testing.assert_almost_equal(ee.ravel(), expected_se.ravel(), default_places) expected_sv = np.sum(expected_v.reshape([nframes, -1, 9]), axis=1) np.testing.assert_almost_equal(vv.ravel(), expected_sv.ravel(), default_places) + + +class TestDeepPotAPBCNeighborList(TestDeepPotAPBC): + @classmethod + def setUpClass(cls): + convert_pbtxt_to_pb( + str(tests_path / os.path.join("infer", "deeppot.pbtxt")), "deeppot.pb" + ) + cls.dp = DeepPot( + "deeppot.pb", + neighbor_list=ase.neighborlist.NewPrimitiveNeighborList( + cutoffs=6, bothways=True + ), + ) + + @unittest.skip("multiple frames not supported") + def test_2frame_atm(self): + pass + + @unittest.skip("Zero atoms not supported") + def test_zero_input(self): + pass